Skip to content

Commit f252467

Browse files
authored
Add fully-automatic mode to the Upload Widget (#113)
1 parent 2e31257 commit f252467

File tree

4 files changed

+198
-53
lines changed

4 files changed

+198
-53
lines changed

sample/src/androidTest/java/cloudinary/android/sample/UploadWidgetTest.java

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package cloudinary.android.sample;
22

3-
import android.app.Activity;
4-
import android.app.Instrumentation;
53
import android.content.Context;
6-
import android.content.Intent;
74
import android.net.Uri;
8-
import androidx.test.platform.app.InstrumentationRegistry;
5+
96
import androidx.test.espresso.intent.rule.IntentsTestRule;
7+
import androidx.test.platform.app.InstrumentationRegistry;
108

119
import com.cloudinary.android.sample.R;
1210
import com.cloudinary.android.sample.app.MainActivity;
11+
import com.cloudinary.android.uploadwidget.UploadWidget;
1312

1413
import org.junit.BeforeClass;
1514
import org.junit.Ignore;
@@ -20,11 +19,10 @@
2019
import java.io.FileOutputStream;
2120
import java.io.IOException;
2221
import java.io.InputStream;
22+
import java.util.Collections;
2323

2424
import static androidx.test.espresso.Espresso.onView;
2525
import static androidx.test.espresso.action.ViewActions.click;
26-
import static androidx.test.espresso.intent.Intents.intending;
27-
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
2826
import static androidx.test.espresso.matcher.ViewMatchers.withId;
2927

3028
public class UploadWidgetTest {
@@ -44,13 +42,10 @@ public static void setup() throws IOException {
4442
@Ignore
4543
@Test
4644
public void testUploadWidget() {
47-
Intent intent = new Intent();
48-
intent.setData(Uri.fromFile(assetFile));
49-
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
50-
51-
intending(hasComponent(MockNativePickerActivity.class.getName())).respondWith(result);
52-
intentsTestRule.getActivity().startActivityForResult(new Intent(intentsTestRule.getActivity(), MockNativePickerActivity.class), MainActivity.CHOOSE_IMAGE_REQUEST_CODE);
53-
45+
UploadWidget.startActivity(intentsTestRule.getActivity(),
46+
MainActivity.UPLOAD_WIDGET_REQUEST_CODE,
47+
new UploadWidget.Options(UploadWidget.Action.START_NOW,
48+
Collections.singletonList(Uri.fromFile(assetFile))));
5449
onView(withId(R.id.crop_action)).perform(click());
5550
onView(withId(R.id.doneButton)).perform(click());
5651
onView(withId(R.id.uploadFab)).perform(click());

sample/src/main/java/com/cloudinary/android/sample/app/MainActivity.java

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,32 @@
1010
import android.os.Bundle;
1111
import android.os.Handler;
1212
import android.os.HandlerThread;
13-
import android.provider.DocumentsContract;
14-
import com.google.android.material.floatingactionbutton.FloatingActionButton;
15-
import com.google.android.material.snackbar.Snackbar;
16-
import com.google.android.material.tabs.TabLayout;
13+
import android.view.Menu;
14+
import android.view.MenuInflater;
15+
import android.view.MenuItem;
16+
import android.view.View;
17+
18+
import androidx.appcompat.app.AlertDialog;
19+
import androidx.appcompat.app.AppCompatActivity;
20+
import androidx.appcompat.widget.Toolbar;
21+
import androidx.core.util.Pair;
1722
import androidx.fragment.app.Fragment;
1823
import androidx.fragment.app.FragmentManager;
1924
import androidx.fragment.app.FragmentPagerAdapter;
2025
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
21-
import androidx.core.util.Pair;
22-
import androidx.viewpager.widget.ViewPager;
23-
import androidx.appcompat.app.AlertDialog;
24-
import androidx.appcompat.app.AppCompatActivity;
2526
import androidx.recyclerview.widget.RecyclerView;
26-
import androidx.appcompat.widget.Toolbar;
27-
import android.view.Menu;
28-
import android.view.MenuInflater;
29-
import android.view.MenuItem;
30-
import android.view.View;
27+
import androidx.viewpager.widget.ViewPager;
3128

3229
import com.cloudinary.android.MediaManager;
33-
import com.cloudinary.android.UploadRequest;
3430
import com.cloudinary.android.sample.R;
3531
import com.cloudinary.android.sample.core.CloudinaryHelper;
3632
import com.cloudinary.android.sample.model.Resource;
3733
import com.cloudinary.android.sample.persist.ResourceRepo;
3834
import com.cloudinary.android.uploadwidget.UploadWidget;
3935
import com.cloudinary.utils.StringUtils;
36+
import com.google.android.material.floatingactionbutton.FloatingActionButton;
37+
import com.google.android.material.snackbar.Snackbar;
38+
import com.google.android.material.tabs.TabLayout;
4039
import com.squareup.picasso.Picasso;
4140
import com.squareup.picasso.PicassoTools;
4241

@@ -50,8 +49,7 @@ public class MainActivity extends AppCompatActivity implements ResourcesAdapter.
5049
public static final int IN_PROGRESS_PAGE_POSITION = 2;
5150
public static final int FAILED_PAGE_POSITION = 3;
5251

53-
public static final int CHOOSE_IMAGE_REQUEST_CODE = 1000;
54-
private static final int UPLOAD_WIDGET_REQUEST_CODE = 1002;
52+
public static final int UPLOAD_WIDGET_REQUEST_CODE = 1002;
5553
private FloatingActionButton fab;
5654
private BroadcastReceiver receiver;
5755
private ViewPager pager;
@@ -72,7 +70,7 @@ protected void onCreate(Bundle savedInstanceState) {
7270
fab.setOnClickListener(new View.OnClickListener() {
7371
@Override
7472
public void onClick(View view) {
75-
UploadWidget.openMediaChooser(MainActivity.this, CHOOSE_IMAGE_REQUEST_CODE);
73+
UploadWidget.startActivity(MainActivity.this, UPLOAD_WIDGET_REQUEST_CODE);
7674
}
7775
});
7876

@@ -223,8 +221,6 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
223221
upload((Resource) data.getSerializableExtra(ImageActivity.RESOURCE_INTENT_EXTRA));
224222
} else if (requestCode == UPLOAD_WIDGET_REQUEST_CODE) {
225223
handleUploadWidgetResult(data);
226-
} else if (requestCode == CHOOSE_IMAGE_REQUEST_CODE && data != null) {
227-
UploadWidget.startActivity(this, UPLOAD_WIDGET_REQUEST_CODE, extractImageUris(data));
228224
}
229225
}
230226
}
@@ -250,11 +246,9 @@ private void handleUploadWidgetResult(final Intent data) {
250246
public void run() {
251247
ArrayList<UploadWidget.Result> results = data.getParcelableArrayListExtra(UploadWidget.RESULT_EXTRA);
252248
for (UploadWidget.Result result : results) {
253-
UploadRequest uploadRequest = UploadWidget.preprocessResult(MainActivity.this, result);
254-
String requestId = uploadRequest.dispatch(MainApplication.get());
255249

256250
Resource resource = createResourceFromUri(result.uri, data.getFlags());
257-
resource.setRequestId(requestId);
251+
resource.setRequestId(result.requestId);
258252
ResourceRepo.getInstance().resourceQueued(resource);
259253
}
260254
}
@@ -324,14 +318,7 @@ public void run() {
324318
}
325319

326320
private Resource createResourceFromUri(final Uri uri, final int flags) {
327-
final int takeFlags = flags & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
328-
329-
if (DocumentsContract.isDocumentUri(MainActivity.this, uri)) {
330-
getContentResolver().takePersistableUriPermission(uri, takeFlags);
331-
}
332-
333321
Pair<String, String> pair = Utils.getResourceNameAndType(MainActivity.this, uri);
334-
335322
return new Resource(uri.toString(), pair.first, pair.second);
336323
}
337324

ui/src/main/java/com/cloudinary/android/uploadwidget/UploadWidget.java

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
import android.os.Build;
88
import android.os.Parcel;
99
import android.os.Parcelable;
10+
1011
import androidx.annotation.NonNull;
12+
import androidx.annotation.Nullable;
1113

1214
import com.cloudinary.android.UploadRequest;
1315
import com.cloudinary.android.uploadwidget.model.CropPoints;
1416
import com.cloudinary.android.uploadwidget.ui.UploadWidgetActivity;
1517

1618
import java.util.ArrayList;
19+
import java.util.Collection;
1720

1821
/**
1922
* Helper class to start the UploadWidget and preprocess its results.
@@ -30,16 +33,47 @@ public class UploadWidget {
3033
*/
3134
public static final String URIS_EXTRA = "uris_extra";
3235

36+
public static final String ACTION_EXTRA = "required_action_extra";
37+
3338
/**
34-
* Start the {@link UploadWidgetActivity}. Please make sure that you have declared it your manifest.
39+
* Start the {@link UploadWidgetActivity} with a pre-populated list of files to upload, and return
40+
* a list of upload request to dispatch. This is equivalent to RequiredAction.NONE.
41+
* Deprecated - please use {@link #startActivity(Activity, int, Options)} directly.
3542
*
3643
* @param activity The activity which requested the upload widget.
3744
* @param requestCode A request code to start the upload widget with.
38-
* @param uris Uris of the selected media files.
45+
* @param uris Uris of the selected media files.
3946
*/
47+
@Deprecated
4048
public static void startActivity(@NonNull Activity activity, int requestCode, @NonNull ArrayList<Uri> uris) {
41-
Intent intent = new Intent(activity, UploadWidgetActivity.class);
42-
intent.putParcelableArrayListExtra(URIS_EXTRA, uris);
49+
startActivity(activity, requestCode, new Options(Action.NONE, uris));
50+
}
51+
52+
/**
53+
* Start the {@link UploadWidgetActivity} configured for full process - Launch file selection UI
54+
* as well as dispatching the created upload request automatically.
55+
*
56+
* @param activity The activity which requested the upload widget.
57+
* @param requestCode A request code to start the upload widget with.
58+
*/
59+
public static void startActivity(@NonNull Activity activity, int requestCode) {
60+
startActivity(activity, requestCode, new Options(Action.DISPATCH, null));
61+
}
62+
63+
/**
64+
* Start the {@link UploadWidgetActivity} configured according to the supplied launch options.
65+
*
66+
* @param activity The activity which requested the upload widget.
67+
* @param requestCode A request code to start the upload widget with.
68+
* @param options The launch option to define the required upload widget behaviour
69+
*/
70+
public static void startActivity(@NonNull Activity activity, int requestCode, Options options) {
71+
Intent intent = new Intent(activity, UploadWidgetActivity.class).putExtra(ACTION_EXTRA, options.action);
72+
73+
if (options.uris != null && !options.uris.isEmpty()) {
74+
intent.putParcelableArrayListExtra(URIS_EXTRA, new ArrayList<Parcelable>(options.uris));
75+
}
76+
4377
activity.startActivityForResult(intent, requestCode);
4478
}
4579

@@ -76,9 +110,9 @@ public static UploadRequest preprocessResult(Context context, Result result) {
76110
* Preprocess the {@code uploadRequest}'s with the upload widget results.
77111
*
78112
* @param uploadRequest Already constructed upload request.
79-
* @param result Result data from the upload widget.
113+
* @param result Result data from the upload widget.
80114
* @return Preprocessed {@link UploadRequest}
81-
* @throws IllegalStateException if {@code uploadRequest} was already dispatched.
115+
* @throws IllegalStateException if {@code uploadRequest} was already dispatched.
82116
*/
83117
public static UploadRequest preprocessResult(Context context, @NonNull UploadRequest uploadRequest, Result result) {
84118
return UploadWidgetResultProcessor.process(context, uploadRequest, result);
@@ -93,7 +127,9 @@ public static UploadRequest preprocessResult(Context context, @NonNull UploadReq
93127
public static void openMediaChooser(Activity activity, int requestCode) {
94128
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
95129
intent.addCategory(Intent.CATEGORY_OPENABLE);
96-
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
130+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
131+
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
132+
}
97133

98134
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
99135
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/jpeg", "image/jpg", "image/png", "video/*"});
@@ -133,6 +169,13 @@ public static final class Result implements Parcelable {
133169
*/
134170
public int rotationAngle;
135171

172+
/**
173+
* The request id, in case the full flow was requested and an upload request was already
174+
* started or dispatched.
175+
*/
176+
public String requestId;
177+
178+
136179
public Result(Uri uri) {
137180
this.uri = uri;
138181
}
@@ -142,6 +185,7 @@ public void writeToParcel(Parcel dest, int flags) {
142185
dest.writeParcelable(uri, flags);
143186
dest.writeParcelable(cropPoints, flags);
144187
dest.writeInt(rotationAngle);
188+
dest.writeString(requestId);
145189
}
146190

147191
public static final Creator<Result> CREATOR = new Creator<Result>() {
@@ -160,6 +204,7 @@ protected Result(Parcel in) {
160204
uri = in.readParcelable(Uri.class.getClassLoader());
161205
cropPoints = in.readParcelable(CropPoints.class.getClassLoader());
162206
rotationAngle = in.readInt();
207+
requestId = in.readString();
163208
}
164209

165210
@Override
@@ -168,4 +213,47 @@ public int describeContents() {
168213
}
169214
}
170215

216+
/**
217+
* This class is used to define the required launch behaviour of the upload widget.
218+
*/
219+
public static class Options {
220+
final Action action;
221+
final Collection<Uri> uris;
222+
223+
/**
224+
* Construct a new instance to use when launching the upload widget activity.
225+
*
226+
* @param action Indicates the widget how to handle the selected files. This also
227+
* affects the result received later in onActivityResult. When the action
228+
* used is DISPATCH or START_NOW the widget returns a list of request IDs.
229+
* When the action is NONE, the widget returns results that needs to be
230+
* processed into UploadRequest, allowing customization before dispatching/starting.
231+
* @param uris A list of Uris of files to display and upload.
232+
*/
233+
public Options(@NonNull Action action, @Nullable Collection<Uri> uris) {
234+
this.action = action;
235+
this.uris = uris;
236+
}
237+
}
238+
239+
/**
240+
* Define how the upload widget handles the selected files to upload
241+
*/
242+
public enum Action {
243+
/**
244+
* Dispatch the selected files within the upload widget, and return request IDs.
245+
*/
246+
DISPATCH,
247+
248+
/**
249+
* Immediately start the selected files within the upload widget, and return request IDs.
250+
*/
251+
START_NOW,
252+
253+
/**
254+
* Create the request data and preprocess configuration without starting any request.
255+
*/
256+
NONE,
257+
}
258+
171259
}

0 commit comments

Comments
 (0)