From 78546b5eb9620e6549baa0d6b2ff8ad2340745f6 Mon Sep 17 00:00:00 2001 From: joashrajin Date: Thu, 9 Jul 2026 23:43:15 +0200 Subject: [PATCH] Share note via system share sheet instead of custom app grid The share bottom sheet built its own app grid from PackageManager.queryIntentActivities(), which is subject to package visibility filtering on Android 11+. Since the manifest only declares https VIEW intents in , most share targets (Gmail, WhatsApp, etc.) were invisible and missing from the grid. Replace the grid with a Share button that launches the system share sheet via ShareCompat, which is not affected by visibility filtering and always lists every app that can handle the note text. Fixes #1578 --- RELEASE-NOTES.txt | 1 + .../simplenote/NoteEditorFragment.java | 7 ++ .../simplenote/ShareBottomSheetDialog.java | 78 +------------ .../simplenote/utils/IconResizer.java | 102 ----------------- .../simplenote/utils/ShareButtonAdapter.java | 104 ------------------ .../main/res/drawable/ic_share_other_48dp.xml | 29 +++++ .../main/res/layout/bottom_sheet_share.xml | 29 +++-- .../src/main/res/layout/share_button_item.xml | 18 --- 8 files changed, 56 insertions(+), 312 deletions(-) delete mode 100644 Simplenote/src/main/java/com/automattic/simplenote/utils/IconResizer.java delete mode 100644 Simplenote/src/main/java/com/automattic/simplenote/utils/ShareButtonAdapter.java create mode 100644 Simplenote/src/main/res/drawable/ic_share_other_48dp.xml delete mode 100644 Simplenote/src/main/res/layout/share_button_item.xml diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 8f2cc3b58..ce099d30d 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,6 @@ 2.39 ----- +* Fixed the note share sheet missing installed apps (e.g. Gmail, WhatsApp) by launching the system share sheet [#1578](https://github.com/Automattic/simplenote-android/issues/1578) 2.38 ----- diff --git a/Simplenote/src/main/java/com/automattic/simplenote/NoteEditorFragment.java b/Simplenote/src/main/java/com/automattic/simplenote/NoteEditorFragment.java index d9afb3e7e..a3b45d2e5 100644 --- a/Simplenote/src/main/java/com/automattic/simplenote/NoteEditorFragment.java +++ b/Simplenote/src/main/java/com/automattic/simplenote/NoteEditorFragment.java @@ -1388,6 +1388,13 @@ public void onShareDismissed() { } + @Override + public void onShareOtherClicked() { + if (mNote != null) { + showShare(mNote.getContent()); + } + } + /** * History bottom sheet listeners */ diff --git a/Simplenote/src/main/java/com/automattic/simplenote/ShareBottomSheetDialog.java b/Simplenote/src/main/java/com/automattic/simplenote/ShareBottomSheetDialog.java index b7628c86f..5bd8acd1c 100644 --- a/Simplenote/src/main/java/com/automattic/simplenote/ShareBottomSheetDialog.java +++ b/Simplenote/src/main/java/com/automattic/simplenote/ShareBottomSheetDialog.java @@ -1,16 +1,10 @@ package com.automattic.simplenote; -import android.app.Activity; -import android.content.ComponentName; import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; @@ -18,28 +12,14 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import com.automattic.simplenote.models.Note; -import com.automattic.simplenote.utils.IconResizer; import com.automattic.simplenote.utils.NetworkUtils; -import com.automattic.simplenote.utils.ShareButtonAdapter; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; - -import java.util.ArrayList; -import java.util.List; public class ShareBottomSheetDialog extends BottomSheetDialogBase { public static final String TAG = ShareBottomSheetDialog.class.getSimpleName(); - private static final int SHARE_SHEET_COLUMN_COUNT = 3; - private Fragment mFragment; - private Intent mShareIntent; - private List mShareButtons; - private RecyclerView mRecyclerView; private ShareSheetListener mListener; private TextView mPublishButton; private TextView mUnpublishButton; @@ -63,6 +43,7 @@ public void onDismiss(DialogInterface dialog) { getDialog().setContentView(R.layout.bottom_sheet_share); TextView mCollaborateButton = getDialog().findViewById(R.id.share_collaborate_button); + TextView mShareOtherButton = getDialog().findViewById(R.id.share_other_button); mPublishButton = getDialog().findViewById(R.id.share_publish_button); mUnpublishButton = getDialog().findViewById(R.id.share_unpublish_button); mWordPressButton = getDialog().findViewById(R.id.share_wp_post); @@ -105,30 +86,11 @@ public void onClick(View v) { } }); - mRecyclerView = getDialog().findViewById(R.id.share_button_recycler_view); - mRecyclerView.setHasFixedSize(true); - mRecyclerView.setLayoutManager(new GridLayoutManager(mFragment.requireActivity(), SHARE_SHEET_COLUMN_COUNT)); - - mShareIntent = new Intent(Intent.ACTION_SEND); - mShareIntent.setType("text/plain"); - - mShareButtons = getShareButtons(mFragment.requireActivity(), mShareIntent); - } - - if (getDialog() != null) { - // Set peek height to half height of view (i.e. set STATE_HALF_EXPANDED) to show some of - // sharing options when bottom sheet is shown. - getDialog().setOnShowListener(new DialogInterface.OnShowListener() { + mShareOtherButton.setOnClickListener(new View.OnClickListener() { @Override - public void onShow(DialogInterface dialogInterface) { - BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface; - FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); - - if (bottomSheet != null) { - BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet); - behavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED); - behavior.setSkipCollapsed(true); - } + public void onClick(View v) { + mListener.onShareOtherClicked(); + dismiss(); } }); } @@ -147,41 +109,13 @@ public void show(FragmentManager manager, Note note) { mPublishButton.setVisibility(View.VISIBLE); mUnpublishButton.setVisibility(View.GONE); } - - mShareIntent.putExtra(Intent.EXTRA_TEXT, note.getContent()); - - final ShareButtonAdapter.ItemListener shareListener = new ShareButtonAdapter.ItemListener() { - @Override - public void onItemClick(ShareButtonAdapter.ShareButtonItem item) { - mShareIntent.setComponent(new ComponentName(item.getPackageName(), item.getActivityName())); - mFragment.requireActivity().startActivity(Intent.createChooser(mShareIntent, mFragment.getString(R.string.share))); - dismiss(); - } - }; - - mRecyclerView.setAdapter(new ShareButtonAdapter(mShareButtons, shareListener)); } } - @NonNull - private List getShareButtons(Activity activity, Intent intent) { - List shareButtons = new ArrayList<>(); - final List matches = activity.getPackageManager().queryIntentActivities(intent, 0); - IconResizer iconResizer = new IconResizer(requireContext()); - - for (ResolveInfo match : matches) { - final Drawable icon = iconResizer.createIconThumbnail(match.loadIcon(activity.getPackageManager())); - final CharSequence label = match.loadLabel(activity.getPackageManager()); - shareButtons.add(new ShareButtonAdapter.ShareButtonItem(icon, label, - match.activityInfo.packageName, match.activityInfo.name)); - } - - return shareButtons; - } - public interface ShareSheetListener { void onShareCollaborateClicked(); void onShareDismissed(); + void onShareOtherClicked(); void onSharePublishClicked(); void onShareUnpublishClicked(); void onWordPressPostClicked(); diff --git a/Simplenote/src/main/java/com/automattic/simplenote/utils/IconResizer.java b/Simplenote/src/main/java/com/automattic/simplenote/utils/IconResizer.java deleted file mode 100644 index c001356d8..000000000 --- a/Simplenote/src/main/java/com/automattic/simplenote/utils/IconResizer.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.automattic.simplenote.utils; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.PaintDrawable; - -import com.automattic.simplenote.R; - -/** - * Utility class to resize icons to match default icon size. - */ -public class IconResizer { - - // Code is borrowed from com.android.launcher.Utilities. - private int mIconWidth = -1; - private int mIconHeight = -1; - private final Rect mOldBounds = new Rect(); - private Canvas mCanvas = new Canvas(); - private Context context; - - public IconResizer(Context context) { - this.context = context; - mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, - Paint.FILTER_BITMAP_FLAG)); - - final Resources resources = context.getResources(); - mIconWidth = mIconHeight = (int) resources.getDimension(R.dimen.share_icon_size); - } - - /** - * Returns a Drawable representing the thumbnail of the specified Drawable. - * The size of the thumbnail is defined by the dimension - * android.R.dimen.launcher_application_icon_size. - *

- * This method is not thread-safe and should be invoked on the UI thread only. - * - * @param icon The icon to get a thumbnail of. - * @return A thumbnail for the specified icon or the icon itself if the - * thumbnail could not be created. - */ - public Drawable createIconThumbnail(Drawable icon) { - int width = mIconWidth; - int height = mIconHeight; - final int iconWidth = icon.getIntrinsicWidth(); - final int iconHeight = icon.getIntrinsicHeight(); - if (icon instanceof PaintDrawable) { - PaintDrawable painter = (PaintDrawable) icon; - painter.setIntrinsicWidth(width); - painter.setIntrinsicHeight(height); - } - if (width > 0 && height > 0) { - if (width < iconWidth || height < iconHeight) { - final float ratio = (float) iconWidth / iconHeight; - if (iconWidth > iconHeight) { - height = (int) (width / ratio); - } else if (iconHeight > iconWidth) { - width = (int) (height * ratio); - } - final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? - Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; - final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); - final Canvas canvas = mCanvas; - canvas.setBitmap(thumb); - // Copy the old bounds to restore them later - // If we were to do oldBounds = icon.getBounds(), - // the call to setBounds() that follows would - // change the same instance and we would lose the - // old bounds - mOldBounds.set(icon.getBounds()); - final int x = (mIconWidth - width) / 2; - final int y = (mIconHeight - height) / 2; - icon.setBounds(x, y, x + width, y + height); - icon.draw(canvas); - icon.setBounds(mOldBounds); - icon = new BitmapDrawable(context.getResources(), thumb); - canvas.setBitmap(null); - } else if (iconWidth < width && iconHeight < height) { - final Bitmap.Config c = Bitmap.Config.ARGB_8888; - final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); - final Canvas canvas = mCanvas; - canvas.setBitmap(thumb); - mOldBounds.set(icon.getBounds()); - final int x = (width - iconWidth) / 2; - final int y = (height - iconHeight) / 2; - icon.setBounds(x, y, x + iconWidth, y + iconHeight); - icon.draw(canvas); - icon.setBounds(mOldBounds); - icon = new BitmapDrawable(context.getResources(), thumb); - canvas.setBitmap(null); - } - } - return icon; - } -} diff --git a/Simplenote/src/main/java/com/automattic/simplenote/utils/ShareButtonAdapter.java b/Simplenote/src/main/java/com/automattic/simplenote/utils/ShareButtonAdapter.java deleted file mode 100644 index 559c0d2c4..000000000 --- a/Simplenote/src/main/java/com/automattic/simplenote/utils/ShareButtonAdapter.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.automattic.simplenote.utils; - -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.automattic.simplenote.R; - -import java.util.List; - -public class ShareButtonAdapter extends RecyclerView.Adapter { - - private List mItems; - private ItemListener mListener; - - public ShareButtonAdapter(List items, ItemListener listener) { - mItems = items; - mListener = listener; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.share_button_item, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - holder.setData(mItems.get(position)); - } - - @Override - public int getItemCount() { - return mItems.size(); - } - - public interface ItemListener { - void onItemClick(ShareButtonItem item); - } - - public static class ShareButtonItem { - - private Drawable mDrawableRes; - - private CharSequence mTitle; - private String mPackageName; - private String mActivityName; - - public ShareButtonItem(Drawable drawable, CharSequence title, - String packageName, String activityName) { - mDrawableRes = drawable; - mTitle = title; - mPackageName = packageName; - mActivityName = activityName; - } - - public Drawable getDrawable() { - return mDrawableRes; - } - - public CharSequence getTitle() { - return mTitle; - } - - public String getPackageName() { - return mPackageName; - } - - public String getActivityName() { - return mActivityName; - } - } - - public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - - public TextView button; - public ShareButtonItem item; - - public ViewHolder(View itemView) { - super(itemView); - itemView.setOnClickListener(this); - button = itemView.findViewById(R.id.share_button); - } - - public void setData(ShareButtonItem item) { - this.item = item; - button.setCompoundDrawablesWithIntrinsicBounds(null, item.getDrawable(), null, null); - button.setText(item.getTitle()); - } - - @Override - public void onClick(View v) { - if (mListener != null) { - mListener.onItemClick(item); - } - } - } -} \ No newline at end of file diff --git a/Simplenote/src/main/res/drawable/ic_share_other_48dp.xml b/Simplenote/src/main/res/drawable/ic_share_other_48dp.xml new file mode 100644 index 000000000..013a8d756 --- /dev/null +++ b/Simplenote/src/main/res/drawable/ic_share_other_48dp.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Simplenote/src/main/res/layout/bottom_sheet_share.xml b/Simplenote/src/main/res/layout/bottom_sheet_share.xml index 6014df637..73ed2a50d 100644 --- a/Simplenote/src/main/res/layout/bottom_sheet_share.xml +++ b/Simplenote/src/main/res/layout/bottom_sheet_share.xml @@ -65,22 +65,19 @@ style="@style/Theme.Simplestyle.BottomSheetDialogText"> - - - - + + - - + diff --git a/Simplenote/src/main/res/layout/share_button_item.xml b/Simplenote/src/main/res/layout/share_button_item.xml deleted file mode 100644 index 313f9da86..000000000 --- a/Simplenote/src/main/res/layout/share_button_item.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file