Detailed changes
  
  
    
    @@ -1148,6 +1148,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         return alwaysNotify() || getBooleanAttribute(ATTRIBUTE_NOTIFY_REPLIES, false);
     }
 
+    public void setStoreInCache(final boolean cache) {
+        setAttribute("storeMedia", cache ? "cache" : "shared");
+    }
+
+    public boolean storeInCache() {
+        if ("cache".equals(getAttribute("storeMedia"))) return true;
+        if ("shared".equals(getAttribute("storeMedia"))) return false;
+        if (mode == Conversation.MODE_MULTI && !mucOptions.isPrivateAndNonAnonymous()) return true;
+        return false;
+    }
+
     public boolean setAttribute(String key, boolean value) {
         return setAttribute(key, String.valueOf(value));
     }
  
  
  
    
    @@ -82,6 +82,7 @@ import io.ipfs.cid.Cid;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.DownloadableFile;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.services.AttachFileToConversationRunnable;
@@ -1083,7 +1084,7 @@ public class FileBackend {
         }
         final File appDirectory =
                 new File(parentDirectory, mXmppConnectionService.getString(R.string.app_name));
-        if (message == null || message.getStatus() == Message.STATUS_DUMMY) {
+        if (message == null || message.getStatus() == Message.STATUS_DUMMY || (message.getConversation() instanceof Conversation && ((Conversation) message.getConversation()).storeInCache())) {
             final var mediaCache = new File(mXmppConnectionService.getCacheDir(), "/media");
             return new File(mediaCache, filename);
         } else {
  
  
  
    
    @@ -518,6 +518,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
                     xmppConnectionService.getAttachments(this.mConversation, limit, this);
                     this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation));
                 }
+
+                binding.storeInCache.setChecked(mConversation.storeInCache());
+                binding.storeInCache.setOnCheckedChangeListener((v, checked) -> {
+                    mConversation.setStoreInCache(checked);
+                    xmppConnectionService.updateConversation(mConversation);
+                });
+
                 updateView();
             }
         }
  
  
  
    
    @@ -735,6 +735,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
                 });
             });
 
+            final var conversation = xmppConnectionService.findOrCreateConversation(account, contact.getJid(), false, true);
+            binding.storeInCache.setChecked(conversation.storeInCache());
+            binding.storeInCache.setOnCheckedChangeListener((v, checked) -> {
+                conversation.setStoreInCache(checked);
+                xmppConnectionService.updateConversation(conversation);
+            });
+
             populateView();
         }
     }
  
  
  
    
    @@ -1787,12 +1787,11 @@ public class ConversationFragment extends XmppFragment
             }
             if (m.isFileOrImage() && !deleted && !cancelable) {
                 final String path = m.getRelativeFilePath();
-                if (path == null
-                        || !path.startsWith("/")
-                        || FileBackend.inConversationsDirectory(requireActivity(), path)) {
-                    saveAsSticker.setVisible(true);
+                if (path != null) {
+                    final var file = new File(path);
+                    if (file.canRead()) saveAsSticker.setVisible(true);
                     blockMedia.setVisible(true);
-                    deleteFile.setVisible(true);
+                    if (file.canWrite()) deleteFile.setVisible(true);
                     deleteFile.setTitle(
                             activity.getString(
                                     R.string.delete_x_file,
  
  
  
    
    @@ -180,6 +180,29 @@
                         android:orientation="vertical"
                         android:padding="@dimen/card_padding_regular">
 
+                        <RelativeLayout
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+
+                            <TextView
+                                android:text="Store media only in cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentStart="true"
+                                android:layout_centerVertical="true"
+                                android:singleLine="true"
+                                android:textAppearance="?textAppearanceBodyMedium" />
+
+                            <androidx.appcompat.widget.SwitchCompat
+                                android:id="@+id/store_in_cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentEnd="true"
+                                android:layout_centerVertical="true"
+                                android:focusable="false" />
+
+                        </RelativeLayout>
+
                         <androidx.recyclerview.widget.RecyclerView
                             android:id="@+id/media"
                             android:layout_width="match_parent"
  
  
  
    
    @@ -431,6 +431,29 @@
                         android:orientation="vertical"
                         android:padding="@dimen/card_padding_regular">
 
+                        <RelativeLayout
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+
+                            <TextView
+                                android:text="Store media only in cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentStart="true"
+                                android:layout_centerVertical="true"
+                                android:singleLine="true"
+                                android:textAppearance="?textAppearanceBodyMedium" />
+
+                            <androidx.appcompat.widget.SwitchCompat
+                                android:id="@+id/store_in_cache"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:layout_alignParentEnd="true"
+                                android:layout_centerVertical="true"
+                                android:focusable="false" />
+
+                        </RelativeLayout>
+
                         <androidx.recyclerview.widget.RecyclerView
                             android:id="@+id/media"
                             android:layout_width="match_parent"