bump image cropper

Daniel Gultsch created

Change summary

build.gradle                                                                        |   2 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java                   |  43 
src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java |   5 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java                    |   4 
src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java |  50 
src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java          | 135 
src/main/res/values/strings.xml                                                     |   2 
7 files changed, 115 insertions(+), 126 deletions(-)

Detailed changes

build.gradle πŸ”—

@@ -58,7 +58,7 @@ dependencies {
     conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
     quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.2.0'
     implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
-    implementation("com.github.CanHub:Android-Image-Cropper:2.0.0")
+    implementation("com.vanniktech:android-image-cropper:4.6.0")
     implementation "androidx.sharetarget:sharetarget:1.2.0"
 
     implementation 'androidx.appcompat:appcompat:1.7.0'

src/main/java/eu/siacs/conversations/persistance/FileBackend.java πŸ”—

@@ -1395,34 +1395,36 @@ public class FileBackend {
         return Uri.fromFile(getAvatarFile(avatar));
     }
 
-    public Bitmap cropCenterSquare(Uri image, int size) {
+    public Bitmap cropCenterSquare(final Uri image, final int size) {
         if (image == null) {
             return null;
         }
-        InputStream is = null;
+        final BitmapFactory.Options options = new BitmapFactory.Options();
         try {
-            BitmapFactory.Options options = new BitmapFactory.Options();
             options.inSampleSize = calcSampleSize(image, size);
-            is = mXmppConnectionService.getContentResolver().openInputStream(image);
+        } catch (final IOException | SecurityException e) {
+            Log.d(Config.LOGTAG, "unable to calculate sample size for " + image, e);
+            return null;
+        }
+        try (final InputStream is =
+                mXmppConnectionService.getContentResolver().openInputStream(image)) {
             if (is == null) {
                 return null;
             }
-            Bitmap input = BitmapFactory.decodeStream(is, null, options);
-            if (input == null) {
+            final var originalBitmap = BitmapFactory.decodeStream(is, null, options);
+            if (originalBitmap == null) {
                 return null;
             } else {
-                input = rotate(input, getRotation(image));
-                return cropCenterSquare(input, size);
+                final var bitmap = rotate(originalBitmap, getRotation(image));
+                return cropCenterSquare(bitmap, size);
             }
-        } catch (FileNotFoundException | SecurityException e) {
-            Log.d(Config.LOGTAG, "unable to open file " + image.toString(), e);
+        } catch (final SecurityException | IOException e) {
+            Log.d(Config.LOGTAG, "unable to open file " + image, e);
             return null;
-        } finally {
-            close(is);
         }
     }
 
-    public Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
+    public Bitmap cropCenter(final Uri image, final int newHeight, final int newWidth) {
         if (image == null) {
             return null;
         }
@@ -1458,7 +1460,7 @@ public class FileBackend {
             return dest;
         } catch (SecurityException e) {
             return null; // android 6.0 with revoked permissions for example
-        } catch (FileNotFoundException e) {
+        } catch (IOException e) {
             return null;
         } finally {
             close(is);
@@ -1486,15 +1488,14 @@ public class FileBackend {
         return output;
     }
 
-    private int calcSampleSize(Uri image, int size)
-            throws FileNotFoundException, SecurityException {
+    private int calcSampleSize(final Uri image, int size) throws IOException, SecurityException {
         final BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
-        final InputStream inputStream =
-                mXmppConnectionService.getContentResolver().openInputStream(image);
-        BitmapFactory.decodeStream(inputStream, null, options);
-        close(inputStream);
-        return calcSampleSize(options, size);
+        try (final InputStream inputStream =
+                mXmppConnectionService.getContentResolver().openInputStream(image)) {
+            BitmapFactory.decodeStream(inputStream, null, options);
+            return calcSampleSize(options, size);
+        }
     }
 
     public void updateFileParams(final Message message) {

src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java πŸ”—

@@ -24,7 +24,7 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity {
     }
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
+    protected void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         final ActivityManageAccountsBinding binding =
                 DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts);
@@ -64,11 +64,12 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity {
         }
     }
 
-    private void goToProfilePictureActivity(Account account) {
+    private void goToProfilePictureActivity(final Account account) {
         final Intent startIntent = getIntent();
         final Uri uri = startIntent == null ? null : startIntent.getData();
         if (uri != null) {
             Intent intent = new Intent(this, PublishProfilePictureActivity.class);
+            intent.setAction(Intent.ACTION_ATTACH_DATA);
             intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString());
             intent.setData(uri);
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java πŸ”—

@@ -1202,6 +1202,10 @@ public class EditAccountActivity extends OmemoActivity
         this.binding.accountPassword.setFocusableInTouchMode(editPassword);
         this.binding.accountPassword.setCursorVisible(editPassword);
         this.binding.accountPassword.setEnabled(editPassword);
+        if (!editPassword && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            this.binding.accountJid.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
+            this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
+        }
 
         if (!mInitMode) {
             this.binding.avater.setVisibility(View.VISIBLE);

src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java πŸ”—

@@ -29,18 +29,17 @@
 
 package eu.siacs.conversations.ui;
 
-import static eu.siacs.conversations.ui.PublishProfilePictureActivity.REQUEST_CHOOSE_PICTURE;
-
-import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
 import androidx.annotation.StringRes;
 import androidx.databinding.DataBindingUtil;
-import com.canhub.cropper.CropImage;
+import com.canhub.cropper.CropImageContract;
+import com.canhub.cropper.CropImageContractOptions;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityPublishProfilePictureBinding;
@@ -55,6 +54,15 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity
     private Conversation conversation;
     private Uri uri;
 
+    final ActivityResultLauncher<CropImageContractOptions> cropImage =
+            registerForActivityResult(
+                    new CropImageContract(),
+                    cropResult -> {
+                        if (cropResult.isSuccessful()) {
+                            onAvatarPicked(cropResult.getUriContent());
+                        }
+                    });
+
     @Override
     protected void refreshUiReal() {}
 
@@ -94,8 +102,8 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity
         configureActionBar(getSupportActionBar());
         this.binding.cancelButton.setOnClickListener((v) -> this.finish());
         this.binding.secondaryHint.setVisibility(View.GONE);
-        this.binding.accountImage.setOnClickListener(
-                (v) -> PublishProfilePictureActivity.chooseAvatar(this));
+        this.binding.accountImage.setOnClickListener((v) -> pickAvatar());
+
         final var intent = getIntent();
         final var uuid = intent == null ? null : intent.getStringExtra("uuid");
         if (uuid != null) {
@@ -111,26 +119,16 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity
         xmppConnectionService.publishMucAvatar(conversation, uri, this);
     }
 
-    @Override
-    public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
-            final CropImage.ActivityResult result = CropImage.getActivityResult(data);
-            if (resultCode == RESULT_OK) {
-                this.uri = result == null ? null : result.getUri();
-                if (xmppConnectionServiceBound) {
-                    reloadAvatar();
-                }
-            } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
-                final var error = result == null ? null : result.getError();
-                if (error != null) {
-                    Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show();
-                }
-            }
-        } else if (requestCode == REQUEST_CHOOSE_PICTURE) {
-            if (resultCode == RESULT_OK) {
-                PublishProfilePictureActivity.cropUri(this, data.getData());
-            }
+    public void pickAvatar() {
+        this.cropImage.launch(
+                new CropImageContractOptions(
+                        null, PublishProfilePictureActivity.getCropImageOptions()));
+    }
+
+    private void onAvatarPicked(final Uri uri) {
+        this.uri = uri;
+        if (xmppConnectionServiceBound) {
+            reloadAvatar();
         }
     }
 

src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java πŸ”—

@@ -1,10 +1,8 @@
 package eu.siacs.conversations.ui;
 
-import android.app.Activity;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.Menu;
@@ -12,10 +10,13 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnLongClickListener;
 import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
 import androidx.annotation.NonNull;
 import androidx.annotation.StringRes;
 import androidx.databinding.DataBindingUtil;
-import com.canhub.cropper.CropImage;
+import com.canhub.cropper.CropImageContract;
+import com.canhub.cropper.CropImageContractOptions;
+import com.canhub.cropper.CropImageOptions;
 import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -24,7 +25,6 @@ import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.interfaces.OnAvatarPublication;
 import eu.siacs.conversations.utils.PhoneHelper;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 public class PublishProfilePictureActivity extends XmppActivity
         implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication {
@@ -37,7 +37,6 @@ public class PublishProfilePictureActivity extends XmppActivity
     private Account account;
     private boolean support = false;
     private boolean publishing = false;
-    private final AtomicBoolean handledExternalUri = new AtomicBoolean(false);
     private final OnLongClickListener backToDefaultListener =
             new OnLongClickListener() {
 
@@ -50,6 +49,15 @@ public class PublishProfilePictureActivity extends XmppActivity
             };
     private boolean mInitialAccountSetup;
 
+    final ActivityResultLauncher<CropImageContractOptions> cropImage =
+            registerForActivityResult(
+                    new CropImageContract(),
+                    cropResult -> {
+                        if (cropResult.isSuccessful()) {
+                            onAvatarPicked(cropResult.getUriContent());
+                        }
+                    });
+
     @Override
     public void onAvatarPublicationSucceeded() {
         runOnUiThread(
@@ -85,6 +93,7 @@ public class PublishProfilePictureActivity extends XmppActivity
 
     @Override
     public void onCreate(final Bundle savedInstanceState) {
+
         super.onCreate(savedInstanceState);
 
         this.binding =
@@ -123,12 +132,10 @@ public class PublishProfilePictureActivity extends XmppActivity
                     }
                     finish();
                 });
-        this.binding.accountImage.setOnClickListener(v -> chooseAvatar(this));
+        this.binding.accountImage.setOnClickListener(v -> pickAvatar(null));
         this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext());
         if (savedInstanceState != null) {
             this.avatarUri = savedInstanceState.getParcelable("uri");
-            this.handledExternalUri.set(
-                    savedInstanceState.getBoolean("handle_external_uri", false));
         }
     }
 
@@ -171,47 +178,32 @@ public class PublishProfilePictureActivity extends XmppActivity
         if (this.avatarUri != null) {
             outState.putParcelable("uri", this.avatarUri);
         }
-        outState.putBoolean("handle_external_uri", handledExternalUri.get());
         super.onSaveInstanceState(outState);
     }
 
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
-            CropImage.ActivityResult result = CropImage.getActivityResult(data);
-            if (resultCode == RESULT_OK) {
-                this.avatarUri = result.getUri();
-                if (xmppConnectionServiceBound) {
-                    loadImageIntoPreview(this.avatarUri);
-                }
-            } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
-                Exception error = result.getError();
-                if (error != null) {
-                    Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show();
-                }
-            }
-        } else if (requestCode == REQUEST_CHOOSE_PICTURE) {
-            if (resultCode == RESULT_OK) {
-                cropUri(this, data.getData());
-            }
-        }
+    public void pickAvatar(final Uri image) {
+        this.cropImage.launch(new CropImageContractOptions(image, getCropImageOptions()));
     }
 
-    public static void chooseAvatar(final Activity activity) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-            intent.setType("image/*");
-            activity.startActivityForResult(
-                    Intent.createChooser(
-                            intent, activity.getString(R.string.attach_choose_picture)),
-                    REQUEST_CHOOSE_PICTURE);
+    public static CropImageOptions getCropImageOptions() {
+        final var cropImageOptions = new CropImageOptions();
+        cropImageOptions.aspectRatioX = 1;
+        cropImageOptions.aspectRatioY = 1;
+        cropImageOptions.fixAspectRatio = true;
+        cropImageOptions.outputCompressFormat = Bitmap.CompressFormat.PNG;
+        cropImageOptions.imageSourceIncludeCamera = false;
+        cropImageOptions.minCropResultHeight = Config.AVATAR_SIZE;
+        cropImageOptions.minCropResultWidth = Config.AVATAR_SIZE;
+        return cropImageOptions;
+    }
+
+    private void onAvatarPicked(final Uri uri) {
+        Log.d(Config.LOGTAG, "onAvatarPicked(" + uri + ")");
+        this.avatarUri = uri;
+        if (xmppConnectionServiceBound) {
+            loadImageIntoPreview(uri);
         } else {
-            CropImage.activity()
-                    .setOutputCompressFormat(Bitmap.CompressFormat.PNG)
-                    .setAspectRatio(1, 1)
-                    .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE)
-                    .start(activity);
+            Log.d(Config.LOGTAG, "not ready during avatarPick");
         }
     }
 
@@ -243,61 +235,54 @@ public class PublishProfilePictureActivity extends XmppActivity
     public void onStart() {
         super.onStart();
         final Intent intent = getIntent();
-        this.mInitialAccountSetup = intent != null && intent.getBooleanExtra("setup", false);
-
-        final Uri uri = intent != null ? intent.getData() : null;
+        if (intent == null) {
+            return;
+        }
+        this.mInitialAccountSetup = intent.getBooleanExtra("setup", false);
 
-        if (uri != null && handledExternalUri.compareAndSet(false, true)) {
-            cropUri(this, uri);
+        final var data = intent.getData();
+        final var account = intent.getStringExtra(EXTRA_ACCOUNT);
+        if (Intent.ACTION_ATTACH_DATA.equals(intent.getAction())
+                && data != null
+                && account != null) {
+            pickAvatar(data);
+            final var replacement = new Intent(Intent.ACTION_MAIN);
+            replacement.putExtra(EXTRA_ACCOUNT, account);
+            setIntent(replacement);
             return;
         }
 
         if (this.mInitialAccountSetup) {
             this.binding.cancelButton.setText(R.string.skip);
         }
-        configureActionBar(
-                getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get());
-    }
-
-    public static void cropUri(final Activity activity, final Uri uri) {
-        CropImage.activity(uri)
-                .setOutputCompressFormat(Bitmap.CompressFormat.PNG)
-                .setAspectRatio(1, 1)
-                .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE)
-                .start(activity);
+        configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup);
     }
 
     protected void loadImageIntoPreview(final Uri uri) {
-
-        Bitmap bm = null;
+        Log.d(Config.LOGTAG, "loadImageIntoPreview(" + uri + ")");
+        final Bitmap bitmap;
         if (uri == null) {
-            bm =
+            bitmap =
                     avatarService()
                             .get(
                                     account,
                                     (int) getResources().getDimension(R.dimen.publish_avatar_size));
         } else {
-            try {
-                bm =
-                        xmppConnectionService
-                                .getFileBackend()
-                                .cropCenterSquare(
-                                        uri,
-                                        (int)
-                                                getResources()
-                                                        .getDimension(R.dimen.publish_avatar_size));
-            } catch (final Exception e) {
-                Log.d(Config.LOGTAG, "unable to load bitmap into image view", e);
-            }
+            bitmap =
+                    xmppConnectionService
+                            .getFileBackend()
+                            .cropCenterSquare(
+                                    uri,
+                                    (int) getResources().getDimension(R.dimen.publish_avatar_size));
         }
 
-        if (bm == null) {
+        if (bitmap == null) {
             togglePublishButton(false, R.string.publish);
             this.binding.hintOrWarning.setVisibility(View.VISIBLE);
             this.binding.hintOrWarning.setText(R.string.error_publish_avatar_converting);
             return;
         }
-        this.binding.accountImage.setImageBitmap(bm);
+        this.binding.accountImage.setImageBitmap(bitmap);
         if (support) {
             togglePublishButton(uri != null, R.string.publish);
             this.binding.hintOrWarning.setVisibility(View.INVISIBLE);

src/main/res/values/strings.xml πŸ”—

@@ -843,7 +843,7 @@
     <string name="ebook">e-book</string>
     <string name="video_original">Original (uncompressed)</string>
     <string name="open_with">Open with…</string>
-    <string name="set_profile_picture">Conversations profile picture</string>
+    <string name="set_profile_picture">Avatar</string>
     <string name="choose_account">Choose account</string>
     <string name="restore_backup">Restore backup</string>
     <string name="restore">Restore</string>