5
5
package io .flutter .plugins .share ;
6
6
7
7
import android .app .Activity ;
8
+ import android .content .Context ;
8
9
import android .content .Intent ;
10
+ import android .content .pm .PackageManager ;
11
+ import android .content .pm .ResolveInfo ;
12
+ import android .net .Uri ;
13
+ import android .os .Environment ;
14
+ import androidx .annotation .NonNull ;
15
+ import androidx .core .content .FileProvider ;
16
+ import java .io .File ;
17
+ import java .io .FileInputStream ;
18
+ import java .io .FileOutputStream ;
19
+ import java .io .IOException ;
20
+ import java .io .InputStream ;
21
+ import java .io .OutputStream ;
22
+ import java .util .ArrayList ;
23
+ import java .util .List ;
9
24
10
25
/** Handles share intent. */
11
26
class Share {
12
27
28
+ private Context context ;
13
29
private Activity activity ;
14
30
15
31
/**
16
- * Constructs a Share object. The {@code activity} is used to start the share intent. It might be
17
- * null when constructing the {@link Share} object and set to non-null when an activity is
18
- * available using {@link #setActivity(Activity)}.
32
+ * Constructs a Share object. The {@code context} and {@code activity} are used to start the share
33
+ * intent. The {@code activity} might be null when constructing the {@link Share} object and set
34
+ * to non-null when an activity is available using {@link #setActivity(Activity)}.
19
35
*/
20
- Share (Activity activity ) {
36
+ Share (Context context , Activity activity ) {
37
+ this .context = context ;
21
38
this .activity = activity ;
22
39
}
23
40
@@ -40,11 +57,177 @@ void share(String text, String subject) {
40
57
shareIntent .putExtra (Intent .EXTRA_SUBJECT , subject );
41
58
shareIntent .setType ("text/plain" );
42
59
Intent chooserIntent = Intent .createChooser (shareIntent , null /* dialog title optional */ );
60
+ startActivity (chooserIntent );
61
+ }
62
+
63
+ void shareFiles (List <String > paths , List <String > mimeTypes , String text , String subject )
64
+ throws IOException {
65
+ if (paths == null || paths .isEmpty ()) {
66
+ throw new IllegalArgumentException ("Non-empty path expected" );
67
+ }
68
+
69
+ clearExternalShareFolder ();
70
+ ArrayList <Uri > fileUris = getUrisForPaths (paths );
71
+
72
+ Intent shareIntent = new Intent ();
73
+ if (fileUris .isEmpty ()) {
74
+ share (text , subject );
75
+ return ;
76
+ } else if (fileUris .size () == 1 ) {
77
+ shareIntent .setAction (Intent .ACTION_SEND );
78
+ shareIntent .putExtra (Intent .EXTRA_STREAM , fileUris .get (0 ));
79
+ shareIntent .setType (
80
+ !mimeTypes .isEmpty () && mimeTypes .get (0 ) != null ? mimeTypes .get (0 ) : "*/*" );
81
+ } else {
82
+ shareIntent .setAction (Intent .ACTION_SEND_MULTIPLE );
83
+ shareIntent .putParcelableArrayListExtra (Intent .EXTRA_STREAM , fileUris );
84
+ shareIntent .setType (reduceMimeTypes (mimeTypes ));
85
+ }
86
+ if (text != null ) shareIntent .putExtra (Intent .EXTRA_TEXT , text );
87
+ if (subject != null ) shareIntent .putExtra (Intent .EXTRA_SUBJECT , subject );
88
+ shareIntent .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
89
+ Intent chooserIntent = Intent .createChooser (shareIntent , null /* dialog title optional */ );
90
+
91
+ List <ResolveInfo > resInfoList =
92
+ getContext ()
93
+ .getPackageManager ()
94
+ .queryIntentActivities (chooserIntent , PackageManager .MATCH_DEFAULT_ONLY );
95
+ for (ResolveInfo resolveInfo : resInfoList ) {
96
+ String packageName = resolveInfo .activityInfo .packageName ;
97
+ for (Uri fileUri : fileUris ) {
98
+ getContext ()
99
+ .grantUriPermission (
100
+ packageName ,
101
+ fileUri ,
102
+ Intent .FLAG_GRANT_WRITE_URI_PERMISSION | Intent .FLAG_GRANT_READ_URI_PERMISSION );
103
+ }
104
+ }
105
+
106
+ startActivity (chooserIntent );
107
+ }
108
+
109
+ private void startActivity (Intent intent ) {
43
110
if (activity != null ) {
44
- activity .startActivity (chooserIntent );
111
+ activity .startActivity (intent );
112
+ } else if (context != null ) {
113
+ intent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
114
+ context .startActivity (intent );
115
+ } else {
116
+ throw new IllegalStateException ("Both context and activity are null" );
117
+ }
118
+ }
119
+
120
+ private ArrayList <Uri > getUrisForPaths (List <String > paths ) throws IOException {
121
+ ArrayList <Uri > uris = new ArrayList <>(paths .size ());
122
+ for (String path : paths ) {
123
+ File file = new File (path );
124
+ if (!fileIsOnExternal (file )) {
125
+ file = copyToExternalShareFolder (file );
126
+ }
127
+
128
+ uris .add (
129
+ FileProvider .getUriForFile (
130
+ getContext (), getContext ().getPackageName () + ".flutter.share_provider" , file ));
131
+ }
132
+ return uris ;
133
+ }
134
+
135
+ private String reduceMimeTypes (List <String > mimeTypes ) {
136
+ if (mimeTypes .size () > 1 ) {
137
+ String reducedMimeType = mimeTypes .get (0 );
138
+ for (int i = 1 ; i < mimeTypes .size (); i ++) {
139
+ String mimeType = mimeTypes .get (i );
140
+ if (!reducedMimeType .equals (mimeType )) {
141
+ if (getMimeTypeBase (mimeType ).equals (getMimeTypeBase (reducedMimeType ))) {
142
+ reducedMimeType = getMimeTypeBase (mimeType ) + "/*" ;
143
+ } else {
144
+ reducedMimeType = "*/*" ;
145
+ break ;
146
+ }
147
+ }
148
+ }
149
+ return reducedMimeType ;
150
+ } else if (mimeTypes .size () == 1 ) {
151
+ return mimeTypes .get (0 );
45
152
} else {
46
- chooserIntent .addFlags (Intent .FLAG_ACTIVITY_NEW_TASK );
47
- activity .startActivity (chooserIntent );
153
+ return "*/*" ;
154
+ }
155
+ }
156
+
157
+ @ NonNull
158
+ private String getMimeTypeBase (String mimeType ) {
159
+ if (mimeType == null || !mimeType .contains ("/" )) {
160
+ return "*" ;
161
+ }
162
+
163
+ return mimeType .substring (0 , mimeType .indexOf ("/" ));
164
+ }
165
+
166
+ private boolean fileIsOnExternal (File file ) {
167
+ try {
168
+ String filePath = file .getCanonicalPath ();
169
+ File externalDir = Environment .getExternalStorageDirectory ();
170
+ return externalDir != null && filePath .startsWith (externalDir .getCanonicalPath ());
171
+ } catch (IOException e ) {
172
+ return false ;
173
+ }
174
+ }
175
+
176
+ @ SuppressWarnings ("ResultOfMethodCallIgnored" )
177
+ private void clearExternalShareFolder () {
178
+ File folder = getExternalShareFolder ();
179
+ if (folder .exists ()) {
180
+ for (File file : folder .listFiles ()) {
181
+ file .delete ();
182
+ }
183
+ folder .delete ();
184
+ }
185
+ }
186
+
187
+ @ SuppressWarnings ("ResultOfMethodCallIgnored" )
188
+ private File copyToExternalShareFolder (File file ) throws IOException {
189
+ File folder = getExternalShareFolder ();
190
+ if (!folder .exists ()) {
191
+ folder .mkdirs ();
192
+ }
193
+
194
+ File newFile = new File (folder , file .getName ());
195
+ copy (file , newFile );
196
+ return newFile ;
197
+ }
198
+
199
+ @ NonNull
200
+ private File getExternalShareFolder () {
201
+ return new File (getContext ().getExternalCacheDir (), "share" );
202
+ }
203
+
204
+ private Context getContext () {
205
+ if (activity != null ) {
206
+ return activity ;
207
+ }
208
+ if (context != null ) {
209
+ return context ;
210
+ }
211
+
212
+ throw new IllegalStateException ("Both context and activity are null" );
213
+ }
214
+
215
+ private static void copy (File src , File dst ) throws IOException {
216
+ InputStream in = new FileInputStream (src );
217
+ try {
218
+ OutputStream out = new FileOutputStream (dst );
219
+ try {
220
+ // Transfer bytes from in to out
221
+ byte [] buf = new byte [1024 ];
222
+ int len ;
223
+ while ((len = in .read (buf )) > 0 ) {
224
+ out .write (buf , 0 , len );
225
+ }
226
+ } finally {
227
+ out .close ();
228
+ }
229
+ } finally {
230
+ in .close ();
48
231
}
49
232
}
50
233
}
0 commit comments