Use image picker to pick images for upload (#2278)

* Use image picker to pick images for upload

* Consolidate storage permissions in Upload activity

* With proper request codes for image upload

* Use constants for upload limits

* Check for request code while handling requests

* Let fragment initiate the camera/gallery instead of activity

* Delete unused external storage utils
This commit is contained in:
Vivek Maskara 2019-01-08 21:56:46 +05:30 committed by Josephine Lim
parent b05b302e65
commit 164ef9bcac
17 changed files with 339 additions and 637 deletions

View file

@ -39,6 +39,7 @@ dependencies {
implementation files('libs/simplemagic-1.9.jar')
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
implementation 'com.github.esafirm.android-image-picker:imagepicker:1.13.1'
// Logging
implementation 'ch.acra:acra:4.9.2'

View file

@ -117,8 +117,6 @@
android:name=".bookmarks.BookmarksActivity"
android:label="@string/title_activity_bookmarks" />
<activity android:name="com.github.pedrovgs.lynx.LynxActivity"/>
<service android:name=".upload.UploadService" />
<service
android:name=".auth.WikiAccountAuthenticatorService"

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.bookmarks.locations;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -15,6 +14,8 @@ import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.esafirm.imagepicker.features.ImagePicker;
import com.esafirm.imagepicker.model.Image;
import com.pedrogomez.renderers.RVRendererAdapter;
import java.util.ArrayList;
@ -30,12 +31,8 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.nearby.NearbyAdapterFactory;
import fr.free.nrw.commons.nearby.Place;
import timber.log.Timber;
import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.IntentUtils;
public class BookmarkLocationsFragment extends DaggerFragment {
@ -44,12 +41,11 @@ public class BookmarkLocationsFragment extends DaggerFragment {
@BindView(R.id.listView) RecyclerView recyclerView;
@BindView(R.id.parentLayout) RelativeLayout parentLayout;
@Inject
BookmarkLocationsController controller;
@Inject BookmarkLocationsController controller;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
private NearbyAdapterFactory adapterFactory;
private ContributionController contributionController;
@Inject ContributionController contributionController;
/**
* Create an instance of the fragment with the right bundle parameters
@ -67,7 +63,6 @@ public class BookmarkLocationsFragment extends DaggerFragment {
) {
View v = inflater.inflate(R.layout.fragment_bookmarks_locations, container, false);
ButterKnife.bind(this, v);
contributionController = new ContributionController(this, defaultPrefs);
adapterFactory = new NearbyAdapterFactory(this, contributionController);
return v;
}
@ -103,23 +98,12 @@ public class BookmarkLocationsFragment extends DaggerFragment {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
String wikidataEntityId = directPrefs.getString(WIKIDATA_ENTITY_ID_PREF, null);
String wikidataItemLocation = directPrefs.getString(WIKIDATA_ITEM_LOCATION, null);
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
contributionController.handleImagePicked(requestCode, null, true, wikidataEntityId, wikidataItemLocation);
} else {
contributionController.handleImagePicked(requestCode, data.getData(), true, wikidataEntityId, wikidataItemLocation);
}
if (IntentUtils.shouldBookmarksHandle(requestCode, resultCode, data)) {
List<Image> images = ImagePicker.getImages(data);
Intent shareIntent = contributionController.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
startActivity(shareIntent);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
}
}

View file

@ -1,285 +1,151 @@
package fr.free.nrw.commons.contributions;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.FileProvider;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.BasePermissionListener;
import com.esafirm.imagepicker.features.ImagePicker;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.upload.UploadActivity;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.utils.StringUtils;
import timber.log.Timber;
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.ACTION_SEND_MULTIPLE;
import static android.content.Intent.EXTRA_STREAM;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_EXTERNAL;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
import static fr.free.nrw.commons.wikidata.WikidataConstants.IS_DIRECT_UPLOAD;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
@Singleton
public class ContributionController {
static final int SELECT_FROM_GALLERY = 1;
public static final int SELECT_FROM_CAMERA = 2;
static final int PICK_IMAGE_MULTIPLE = 3;
//request codes
public static final int CAMERA_UPLOAD_REQUEST_CODE = 10011;
public static final int GALLERY_UPLOAD_REQUEST_CODE = 10012;
public static final int NEARBY_CAMERA_UPLOAD_REQUEST_CODE = 10013;
public static final int NEARBY_GALLERY_UPLOAD_REQUEST_CODE = 10014;
public static final int BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE = 10015;
public static final int BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE = 10016;
private Fragment fragment;
private SharedPreferences defaultPrefs;
//upload limits
public static final int MULTIPLE_UPLOAD_IMAGE_LIMIT = 5;
public static final int NEARBY_UPLOAD_IMAGE_LIMIT = 1;
public ContributionController(Fragment fragment, SharedPreferences defaultSharedPrefs) {
this.fragment = fragment;
private final Context context;
private final SharedPreferences defaultPrefs;
private final SharedPreferences directPrefs;
@Inject
public ContributionController(Context context,
@Named("default_preferences") SharedPreferences defaultSharedPrefs,
@Named("direct_nearby_upload_prefs") SharedPreferences directPrefs) {
this.context = context;
this.defaultPrefs = defaultSharedPrefs;
this.directPrefs = directPrefs;
}
// See http://stackoverflow.com/a/5054673/17865 for why this is done
private Uri lastGeneratedCaptureUri;
private Uri reGenerateImageCaptureUriInCache() {
File photoFile = new File(fragment.getContext().getCacheDir() + "/images",
new Date().getTime() + ".jpg");
photoFile.getParentFile().mkdirs();
Context applicationContext = fragment.getActivity().getApplicationContext();
return FileProvider.getUriForFile(
fragment.getContext(),
applicationContext.getPackageName() + ".provider",
photoFile);
}
public void initiateCameraPick(Activity activity) {
public void initiateCameraPick(Fragment fragment,
int requestCode) {
boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true);
if (!useExtStorage) {
startCameraCapture();
initiateCameraUpload(fragment, requestCode);
return;
}
Dexter.withActivity(activity)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(new BasePermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
startCameraCapture();
}
@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
if (response.isPermanentlyDenied()) {
DialogUtil.showAlertDialog(activity,
activity.getString(R.string.storage_permission_title),
activity.getString(R.string.write_storage_permission_rationale),
activity.getString(R.string.navigation_item_settings),
null,
() -> PermissionUtils.askUserToManuallyEnablePermissionFromSettings(activity),
null);
}
}
@Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
DialogUtil.showAlertDialog(activity,
activity.getString(R.string.storage_permission_title),
activity.getString(R.string.write_storage_permission_rationale),
activity.getString(android.R.string.ok),
activity.getString(android.R.string.cancel),
token::continuePermissionRequest,
token::cancelPermissionRequest);
}
}).check();
PermissionUtils.checkPermissionsAndPerformAction(fragment.getActivity(),
Manifest.permission.WRITE_EXTERNAL_STORAGE,
() -> initiateCameraUpload(fragment, requestCode),
R.string.storage_permission_title,
R.string.write_storage_permission_rationale);
}
public void initiateGalleryPick(Activity activity) {
public void initiateGalleryPick(Fragment fragment,
int imageLimit,
int requestCode) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
startGalleryPick();
initiateGalleryUpload(fragment, imageLimit, requestCode);
} else {
Dexter.withActivity(activity)
.withPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
.withListener(new BasePermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
startGalleryPick();
}
@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
if (response.isPermanentlyDenied()) {
DialogUtil.showAlertDialog(activity,
activity.getString(R.string.storage_permission_title),
activity.getString(R.string.read_storage_permission_rationale),
activity.getString(R.string.navigation_item_settings),
null,
() -> PermissionUtils.askUserToManuallyEnablePermissionFromSettings(activity),
null);
}
}
@Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
DialogUtil.showAlertDialog(activity,
activity.getString(R.string.storage_permission_title),
activity.getString(R.string.read_storage_permission_rationale),
activity.getString(android.R.string.ok),
activity.getString(android.R.string.cancel),
token::continuePermissionRequest,
token::cancelPermissionRequest);
}
}).check();
PermissionUtils.checkPermissionsAndPerformAction(fragment.getActivity(),
Manifest.permission.READ_EXTERNAL_STORAGE,
() -> initiateGalleryUpload(fragment, imageLimit, requestCode),
R.string.storage_permission_title,
R.string.read_storage_permission_rationale);
}
}
private static void requestWritePermission(Context context, Intent intent, Uri uri) {
private void initiateGalleryUpload(Fragment fragment,
int imageLimit,
int requestCode) {
ImagePicker imagePicker = ImagePicker.ImagePickerWithFragment
.create(fragment)
.showCamera(false)
.folderMode(true)
.includeVideo(false)
.enableLog(true);
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
private void startCameraCapture() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
lastGeneratedCaptureUri = reGenerateImageCaptureUriInCache();
// Intent.setFlags doesn't work for API level <20
requestWritePermission(fragment.getContext(), takePictureIntent, lastGeneratedCaptureUri);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureUri);
if (!fragment.isAdded()) {
return;
}
fragment.startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA);
}
private void startGalleryPick() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
startMultipleGalleryPick();
if (imageLimit > 1) {
imagePicker.multi().limit(imageLimit).start(requestCode);
} else {
startSingleGalleryPick();
imagePicker.single().start(requestCode);
}
}
private void startSingleGalleryPick() {
//FIXME: Starts gallery (opens Google Photos)
Intent pickImageIntent = new Intent(ACTION_GET_CONTENT);
pickImageIntent.setType("image/*");
// See https://stackoverflow.com/questions/22366596/android-illegalstateexception-fragment-not-attached-to-activity-webview
if (!fragment.isAdded()) {
Timber.d("Fragment is not added, startActivityForResult cannot be called");
return;
}
Timber.d("startSingleGalleryPick() called with pickImageIntent");
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
private void initiateCameraUpload(Fragment fragment, int requestCode) {
ImagePicker.cameraOnly()
.start(fragment, requestCode);
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
private void startMultipleGalleryPick() {
Intent pickImageIntent = new Intent(ACTION_GET_CONTENT);
pickImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
pickImageIntent.setType("image/*");
if (!fragment.isAdded()) {
Timber.d("Fragment is not added, startActivityForResult cannot be called");
return;
}
Timber.d("startMultipleGalleryPick() called with pickImageIntent");
fragment.startActivityForResult(pickImageIntent, PICK_IMAGE_MULTIPLE);
}
void handleImagesPicked(int requestCode, @Nullable ArrayList<Uri> uri) {
FragmentActivity activity = fragment.getActivity();
Intent shareIntent = new Intent(activity, UploadActivity.class);
public Intent handleImagesPicked(ArrayList<Uri> uriList, int requestCode) {
Intent shareIntent = new Intent(context, UploadActivity.class);
shareIntent.setAction(ACTION_SEND_MULTIPLE);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
shareIntent.putExtra(EXTRA_STREAM, uri);
shareIntent.putExtra(EXTRA_SOURCE, getSourceFromRequestCode(requestCode));
shareIntent.putExtra(EXTRA_STREAM, uriList);
shareIntent.setType("image/jpeg");
if (activity != null) {
activity.startActivity(shareIntent);
}
}
public void handleImagePicked(int requestCode, @Nullable Uri uri, boolean isDirectUpload, String wikiDataEntityId, String wikidateItemLocation) {
FragmentActivity activity = fragment.getActivity();
Timber.d("handleImagePicked() called with onActivityResult(). Boolean isDirectUpload: " + isDirectUpload + "String wikiDataEntityId: " + wikiDataEntityId);
Intent shareIntent = new Intent(activity, UploadActivity.class);
shareIntent.setAction(ACTION_SEND);
switch (requestCode) {
case SELECT_FROM_GALLERY:
//Handles image picked from gallery
Uri imageData = uri;
shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
break;
case SELECT_FROM_CAMERA:
//FIXME: Find out appropriate mime type
// AFAIK this is the right type for a JPEG image
// https://developer.android.com/training/sharing/send.html#send-binary-content
shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
break;
default:
break;
}
boolean isDirectUpload = directPrefs.getBoolean(IS_DIRECT_UPLOAD, false);
Timber.i("Image selected");
shareIntent.putExtra("isDirectUpload", isDirectUpload);
Timber.d("Put extras into image intent, isDirectUpload is " + isDirectUpload);
try {
if (wikiDataEntityId != null && !wikiDataEntityId.equals("")) {
shareIntent.putExtra(WIKIDATA_ENTITY_ID_PREF, wikiDataEntityId);
shareIntent.putExtra(WIKIDATA_ITEM_LOCATION, wikidateItemLocation);
}
} catch (SecurityException e) {
Timber.e(e, "Security Exception");
String wikiDataEntityId = directPrefs.getString(WIKIDATA_ENTITY_ID_PREF, null);
String wikiDataItemLocation = directPrefs.getString(WIKIDATA_ITEM_LOCATION, null);
if (!StringUtils.isNullOrWhiteSpace(wikiDataEntityId)) {
shareIntent.putExtra(WIKIDATA_ENTITY_ID_PREF, wikiDataEntityId);
shareIntent.putExtra(WIKIDATA_ITEM_LOCATION, wikiDataItemLocation);
}
if (activity != null) {
activity.startActivity(shareIntent);
}
return shareIntent;
}
void saveState(Bundle outState) {
if (outState != null) {
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureUri);
private String getSourceFromRequestCode(int requestCode) {
switch (requestCode) {
case CAMERA_UPLOAD_REQUEST_CODE:
case NEARBY_CAMERA_UPLOAD_REQUEST_CODE:
case BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE:
return SOURCE_CAMERA;
case GALLERY_UPLOAD_REQUEST_CODE:
case NEARBY_GALLERY_UPLOAD_REQUEST_CODE:
case BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE:
return SOURCE_GALLERY;
}
}
void loadState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
lastGeneratedCaptureUri = savedInstanceState.getParcelable("lastGeneratedCaptureURI");
}
return SOURCE_EXTERNAL;
}
}

View file

@ -1,16 +1,11 @@
package fr.free.nrw.commons.contributions;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -22,7 +17,10 @@ import android.widget.ListAdapter;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.ArrayList;
import com.esafirm.imagepicker.features.ImagePicker;
import com.esafirm.imagepicker.model.Image;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
@ -32,12 +30,14 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.utils.ConfigUtils;
import timber.log.Timber;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.IntentUtils;
import static android.app.Activity.RESULT_OK;
import static android.view.View.*;
import static android.view.View.GONE;
import static fr.free.nrw.commons.contributions.ContributionController.SELECT_FROM_GALLERY;
import static android.view.View.VISIBLE;
import static fr.free.nrw.commons.contributions.ContributionController.CAMERA_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.GALLERY_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.MULTIPLE_UPLOAD_IMAGE_LIMIT;
/**
* Created by root on 01.06.2018.
@ -60,9 +60,9 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.noDataYet)
TextView noDataYet;
@Inject
@Named("default_preferences")
SharedPreferences defaultPrefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
@Inject ContributionController controller;
private Animation fab_close;
private Animation fab_open;
@ -71,7 +71,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
private boolean isFabOpen = false;
public ContributionController controller;
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
@ -84,25 +83,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (controller == null) {
controller = new ContributionController(this, defaultPrefs);
}
controller.loadState(savedInstanceState);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (controller != null) {
controller.saveState(outState);
} else {
controller = new ContributionController(this, defaultPrefs);
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@ -123,10 +103,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
private void setListeners() {
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
fabCamera.setOnClickListener(view -> {
controller.initiateCameraPick(getActivity());
});
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(getActivity()));
fabCamera.setOnClickListener(view -> controller.initiateCameraPick(this, CAMERA_UPLOAD_REQUEST_CODE));
fabGallery.setOnClickListener(view -> controller.initiateGalleryPick(this, MULTIPLE_UPLOAD_IMAGE_LIMIT, GALLERY_UPLOAD_REQUEST_CODE));
}
private void animateFAB(boolean isFabOpen) {
@ -156,44 +134,14 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
parentFragment.waitForContributionsListFragment.countDown();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, false, null, null);
} else if (requestCode == ContributionController.PICK_IMAGE_MULTIPLE) {
handleMultipleImages(requestCode, data);
} else if (requestCode == ContributionController.SELECT_FROM_GALLERY){
controller.handleImagePicked(requestCode, data.getData(), false, null, null);
}
if (IntentUtils.shouldContributionsListHandle(requestCode, resultCode, data)) {
List<Image> images = ImagePicker.getImages(data);
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
startActivity(shareIntent);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
}
}
private void handleMultipleImages(int requestCode, Intent data) {
if (getContext() == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
&& data.getClipData() != null) {
ClipData mClipData = data.getClipData();
ArrayList<Uri> mArrayUri = new ArrayList<>();
for (int i = 0; i < mClipData.getItemCount(); i++) {
ClipData.Item item = mClipData.getItemAt(i);
Uri uri = item.getUri();
mArrayUri.add(uri);
}
Log.v("LOG_TAG", "Selected Images" + mArrayUri.size());
controller.handleImagesPicked(requestCode, mArrayUri);
} else if(data.getData() != null) {
controller.handleImagePicked(SELECT_FROM_GALLERY, data.getData(), false, null, null);
super.onActivityResult(requestCode, resultCode, data);
}
}

View file

@ -3,16 +3,16 @@ package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.esafirm.imagepicker.features.ImagePicker;
import com.esafirm.imagepicker.model.Image;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
@ -30,13 +30,11 @@ import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.UriDeserializer;
import timber.log.Timber;
import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
import static fr.free.nrw.commons.utils.IntentUtils.shouldNearbyHandle;
public class NearbyListFragment extends DaggerFragment {
private Bundle bundleForUpdates; // Carry information from activity about changed nearby places and current location
@ -51,9 +49,8 @@ public class NearbyListFragment extends DaggerFragment {
private NearbyAdapterFactory adapterFactory;
private RecyclerView recyclerView;
private ContributionController controller;
@Inject ContributionController controller;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
@ -77,8 +74,6 @@ public class NearbyListFragment extends DaggerFragment {
View view = inflater.inflate(R.layout.fragment_nearby_list, container, false);
recyclerView = view.findViewById(R.id.listView);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
controller = new ContributionController(this, defaultPrefs);
adapterFactory = new NearbyAdapterFactory(this, controller);
return view;
}
@ -140,23 +135,12 @@ public class NearbyListFragment extends DaggerFragment {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
String wikidataEntityId = directPrefs.getString(WIKIDATA_ENTITY_ID_PREF, null);
String wikidataItemLocation = directPrefs.getString(WIKIDATA_ITEM_LOCATION, null);
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, true, wikidataEntityId, wikidataItemLocation);
} else {
controller.handleImagePicked(requestCode, data.getData(), true, wikidataEntityId, wikidataItemLocation);
}
if (shouldNearbyHandle(requestCode, resultCode, data)) {
List<Image> images = ImagePicker.getImages(data);
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
startActivity(shareIntent);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
}

View file

@ -5,7 +5,6 @@ import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
@ -28,6 +27,8 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.esafirm.imagepicker.features.ImagePicker;
import com.esafirm.imagepicker.model.Image;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
@ -44,7 +45,6 @@ import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -60,14 +60,18 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.utils.ImageUtils;
import fr.free.nrw.commons.utils.IntentUtils;
import fr.free.nrw.commons.utils.LocationUtils;
import fr.free.nrw.commons.utils.PlaceUtils;
import fr.free.nrw.commons.utils.UriDeserializer;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
import static android.app.Activity.RESULT_OK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_CAMERA_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_GALLERY_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_UPLOAD_IMAGE_LIMIT;
import static fr.free.nrw.commons.wikidata.WikidataConstants.IS_DIRECT_UPLOAD;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
@ -111,7 +115,6 @@ public class NearbyMapFragment extends DaggerFragment {
private Animation fab_close;
private Animation fab_open;
private Animation rotate_forward;
public ContributionController controller;
private Place place;
private Marker selected;
@ -132,17 +135,11 @@ public class NearbyMapFragment extends DaggerFragment {
private Bundle bundleForUpdates;// Carry information from activity about changed nearby places and current location
private boolean searchedAroundCurrentLocation = true;
@Inject
@Named("prefs")
SharedPreferences prefs;
@Inject
@Named("default_preferences")
SharedPreferences defaultPrefs;
@Inject
@Named("direct_nearby_upload_prefs")
SharedPreferences directPrefs;
@Inject
BookmarkLocationsDao bookmarkLocationDao;
@Inject ContributionController controller;
@Inject @Named("prefs") SharedPreferences prefs;
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
@Inject BookmarkLocationsDao bookmarkLocationDao;
private static final double ZOOM_LEVEL = 14f;
@ -154,7 +151,6 @@ public class NearbyMapFragment extends DaggerFragment {
super.onCreate(savedInstanceState);
Timber.d("Nearby map fragment created");
controller = new ContributionController(this, defaultPrefs);
Bundle bundle = this.getArguments();
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriDeserializer())
@ -874,7 +870,7 @@ public class NearbyMapFragment extends DaggerFragment {
if (fabCamera.isShown()) {
Timber.d("Camera button tapped. Place: %s", place.toString());
storeSharedPrefs();
controller.initiateCameraPick(getActivity());
controller.initiateCameraPick(this, NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
}
});
@ -882,7 +878,7 @@ public class NearbyMapFragment extends DaggerFragment {
if (fabGallery.isShown()) {
Timber.d("Gallery button tapped. Place: %s", place.toString());
storeSharedPrefs();
controller.initiateGalleryPick(getActivity());
controller.initiateGalleryPick(this, NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
}
});
}
@ -894,29 +890,18 @@ public class NearbyMapFragment extends DaggerFragment {
editor.putString("Category", place.getCategory());
editor.putString(WIKIDATA_ENTITY_ID_PREF, place.getWikiDataEntityId());
editor.putString(WIKIDATA_ITEM_LOCATION, PlaceUtils.latLangToString(place.location));
editor.putBoolean(IS_DIRECT_UPLOAD, true);
editor.apply();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
String wikidataEntityId = directPrefs.getString(WIKIDATA_ENTITY_ID_PREF, null);
String wikidataItemLocation = directPrefs.getString(WIKIDATA_ITEM_LOCATION, null);
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
// If coming from camera, pass null as uri. Because camera photos get saved to a
// fixed directory
controller.handleImagePicked(requestCode, null, true, wikidataEntityId, wikidataItemLocation);
} else {
controller.handleImagePicked(requestCode, data.getData(), true, wikidataEntityId, wikidataItemLocation);
}
if (IntentUtils.shouldNearbyHandle(requestCode, resultCode, data)) {
List<Image> images = ImagePicker.getImages(data);
Intent shareIntent = controller.handleImagesPicked(ImageUtils.getUriListFromImages(images), requestCode);
startActivity(shareIntent);
} else {
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
}

View file

@ -33,7 +33,11 @@ import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.utils.PlaceUtils;
import timber.log.Timber;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_CAMERA_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_GALLERY_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_UPLOAD_IMAGE_LIMIT;
import static fr.free.nrw.commons.theme.NavigationBaseActivity.startActivityWithFlags;
import static fr.free.nrw.commons.wikidata.WikidataConstants.IS_DIRECT_UPLOAD;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
@ -142,7 +146,7 @@ public class PlaceRenderer extends Renderer<Place> {
} else {
Timber.d("Camera button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
storeSharedPrefs();
controller.initiateCameraPick(fragment.getActivity());
controller.initiateCameraPick(fragment, NEARBY_CAMERA_UPLOAD_REQUEST_CODE);
}
});
@ -162,7 +166,7 @@ public class PlaceRenderer extends Renderer<Place> {
}else {
Timber.d("Gallery button tapped. Image title: " + place.getName() + "Image desc: " + place.getLongDescription());
storeSharedPrefs();
controller.initiateGalleryPick(fragment.getActivity());
controller.initiateGalleryPick(fragment, NEARBY_UPLOAD_IMAGE_LIMIT, NEARBY_GALLERY_UPLOAD_REQUEST_CODE);
}
});
@ -197,6 +201,7 @@ public class PlaceRenderer extends Renderer<Place> {
editor.putString("Category", place.getCategory());
editor.putString(WIKIDATA_ENTITY_ID_PREF, place.getWikiDataEntityId());
editor.putString(WIKIDATA_ITEM_LOCATION, PlaceUtils.latLangToString(place.location));
editor.putBoolean(IS_DIRECT_UPLOAD, true);
editor.apply();
}

View file

@ -1,153 +0,0 @@
package fr.free.nrw.commons.upload;
import android.app.Activity;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.DexterBuilder;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.single.BasePermissionListener;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.ExternalStorageUtils;
import fr.free.nrw.commons.utils.PermissionUtils;
import io.reactivex.Completable;
import io.reactivex.subjects.CompletableSubject;
import timber.log.Timber;
public class DexterPermissionObtainer {
private final String requestedPermission;
private android.app.AlertDialog storagePermissionInfoDialog;
private DexterBuilder dexterStoragePermissionBuilder;
private PermissionDeniedResponse permissionDeniedResponse;
private boolean storagePromptInProgress;
private final String rationaleTitle;
private final String rationaleText;
private Activity activity;
private CompletableSubject storagePromptObservable;
/**
* @param activity The activity that is requesting the permission
* @param requestedPermission The permission being requested in the form of Manifest.permission.*
* @param rationaleTitle The title of the rationale dialog
* @param rationaleText The text inside the rationale dialog
*/
DexterPermissionObtainer(Activity activity, String requestedPermission, String rationaleTitle, String rationaleText) {
this.activity = activity;
this.rationaleTitle = rationaleTitle;
this.rationaleText = rationaleText;
this.requestedPermission = requestedPermission;
this.storagePromptObservable = CompletableSubject.create();
initPermissionsRationaleDialog();
}
/**
* Checks if storage permissions are obtained, prompts the users to grant storage permissions if necessary.
* When storage permission is present, onPermissionObtained is called.
*/
Completable confirmStoragePermissions() {
if (ExternalStorageUtils.isStoragePermissionGranted(activity)) {
Timber.i("Storage permissions already granted.");
storagePromptObservable.onComplete();
} else if (!storagePromptInProgress) {
if (storagePromptObservable.hasComplete()) {
storagePromptObservable = CompletableSubject.create();
}
//If permission is not there, ask for it
storagePromptInProgress = true;
askDexterToHandleExternalStoragePermission();
}
return storagePromptObservable;
}
/**
* To be called when the user returns to the original activity after manually enabling storage permissions.
*/
void onManualPermissionReturned() {
//OnActivity result, no matter what the result is, our function can handle that.
askDexterToHandleExternalStoragePermission();
}
/**
* This method initialised the Dexter's permission builder (if not already initialised). Also makes sure that the builder is initialised
* only once, otherwise we would'nt know on which instance of it, the user is working on. And after the builder is initialised, it checks
* for the required permission and then handles the permission status, thanks to Dexter's appropriate callbacks.
*/
private void askDexterToHandleExternalStoragePermission() {
Timber.d("External storage permission is being requested");
if (null == dexterStoragePermissionBuilder) {
dexterStoragePermissionBuilder = Dexter.withActivity(activity)
.withPermission(requestedPermission)
.withListener(new BasePermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
Timber.d("User has granted us the permission for writing the external storage");
//If permission is granted, well and good
storagePromptInProgress = false;
storagePromptObservable.onComplete();
//onPermissionObtained.run();
}
@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
Timber.d("User has granted us the permission for writing the external storage");
//If permission is not granted in whatsoever scenario, we show him a dialog stating why we need the permission
permissionDeniedResponse = response;
if (null != storagePermissionInfoDialog && !storagePermissionInfoDialog
.isShowing()) {
storagePermissionInfoDialog.show();
}
}
});
}
dexterStoragePermissionBuilder.check();
}
/**
* We have agreed to show a dialog showing why we need a particular permission.
* This method is used to initialise the dialog which is going to show the permission's rationale.
* The dialog is initialised along with a callback for positive and negative user actions.
*/
private void initPermissionsRationaleDialog() {
if (storagePermissionInfoDialog == null) {
storagePermissionInfoDialog = DialogUtil
.getAlertDialogWithPositiveAndNegativeCallbacks(
activity,
rationaleTitle, rationaleText,
R.drawable.ic_launcher, new DialogUtil.Callback() {
@Override
public void onPositiveButtonClicked() {
//If the user is willing to give us the permission
//But had somehow previously choose never ask again, we take him to app settings to manually enable permission
if (null == permissionDeniedResponse) {
//Dexter returned null, lets see if this ever happens
Timber.w("Dexter returned null as permissionDeniedResponse");
} else if (permissionDeniedResponse.isPermanentlyDenied()) {
PermissionUtils.askUserToManuallyEnablePermissionFromSettings(activity);
Timber.i("Permission permanently denied.");
} else {
//or if we still have chance to show runtime permission dialog, we show him that.
askDexterToHandleExternalStoragePermission();
Timber.d("Asking via Dexter for permission.");
}
}
@Override
public void onNegativeButtonClicked() {
//This was the behaviour as of now, I was planning to maybe snack him with some message
//and then call finish after some time, or may be it could be associated with some action
// on the snack. If the user does not want us to give the permission, even after showing
// rationale dialog, lets not trouble him any more.
activity.finish();
}
});
}
}
}

View file

@ -57,6 +57,7 @@ import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.StringUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Observable;
@ -67,6 +68,7 @@ import timber.log.Timber;
import static fr.free.nrw.commons.utils.ImageUtils.Result;
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
import static fr.free.nrw.commons.wikidata.WikidataConstants.IS_DIRECT_UPLOAD;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ITEM_LOCATION;
@ -126,8 +128,6 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView,
private RVRendererAdapter<CategoryItem> categoriesAdapter;
private CompositeDisposable compositeDisposable;
DexterPermissionObtainer dexterPermissionObtainer;
@SuppressLint("CheckResult")
@Override
@ -150,12 +150,11 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView,
presenter.init();
dexterPermissionObtainer = new DexterPermissionObtainer(this,
PermissionUtils.checkPermissionsAndPerformAction(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
getString(R.string.storage_permission),
getString(R.string.write_storage_permission_rationale_for_image_share));
dexterPermissionObtainer.confirmStoragePermissions().subscribe(this::receiveSharedItems);
this::receiveSharedItems,
R.string.storage_permission_title,
R.string.write_storage_permission_rationale_for_image_share);
}
@Override
@ -180,9 +179,8 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView,
protected void onResume() {
super.onResume();
checkIfLoggedIn();
compositeDisposable.add(
dexterPermissionObtainer.confirmStoragePermissions()
.subscribe(() -> presenter.addView(this)));
checkStoragePermissions();
compositeDisposable.add(
RxTextView.textChanges(categoriesSearch)
.doOnEach(v -> categoriesSearchContainer.setError(null))
@ -193,6 +191,14 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView,
);
}
private void checkStoragePermissions() {
PermissionUtils.checkPermissionsAndPerformAction(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
() -> presenter.addView(this),
R.string.storage_permission_title,
R.string.write_storage_permission_rationale_for_image_share);
}
@Override
protected void onPause() {
presenter.removeView();
@ -428,7 +434,7 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView,
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS) {
dexterPermissionObtainer.onManualPermissionReturned();
//TODO: Confirm if handling manual permission enabled is required
}
}
@ -610,33 +616,52 @@ public class UploadActivity extends AuthenticatedActivity implements UploadView,
source = Contribution.SOURCE_EXTERNAL;
}
Timber.d("Received intent %s with action %s and mimeType %s from source %s",
intent.toString(),
intent.getAction(),
mimeType,
source);
ArrayList<Uri> urisList = new ArrayList<>();
if (Intent.ACTION_SEND.equals(intent.getAction())) {
Uri mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if(mediaUri == null) {
handleNullMedia();
return;
}
if (intent.getBooleanExtra("isDirectUpload", false)) {
String imageTitle = directPrefs.getString("Title", "");
String imageDesc = directPrefs.getString("Desc", "");
Timber.i("Received direct upload with title %s and description %s", imageTitle, imageDesc);
String wikidataEntityIdPref = intent.getStringExtra(WIKIDATA_ENTITY_ID_PREF);
String wikidataItemLocation = intent.getStringExtra(WIKIDATA_ITEM_LOCATION);
presenter.receiveDirect(mediaUri, mimeType, source, wikidataEntityIdPref, imageTitle, imageDesc, wikidataItemLocation);
} else {
Timber.i("Received single upload");
presenter.receive(mediaUri, mimeType, source);
if (mediaUri != null) {
urisList.add(mediaUri);
}
} else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
Timber.i("Received multiple upload %s", urisList.size());
}
if(urisList.isEmpty()) {
handleNullMedia();
return;
}
if (urisList.isEmpty()) {
handleNullMedia();
return;
}
if (intent.getBooleanExtra("isDirectUpload", false)) {
String imageTitle = directPrefs.getString("Title", "");
String imageDesc = directPrefs.getString("Desc", "");
Timber.i("Received direct upload with title %s and description %s", imageTitle, imageDesc);
String wikiDataEntityIdPref = intent.getStringExtra(WIKIDATA_ENTITY_ID_PREF);
String wikiDataItemLocation = intent.getStringExtra(WIKIDATA_ITEM_LOCATION);
presenter.receiveDirect(urisList.get(0), mimeType, source, wikiDataEntityIdPref, imageTitle, imageDesc, wikiDataItemLocation);
} else {
presenter.receive(urisList, mimeType, source);
}
resetDirectPrefs();
}
public void resetDirectPrefs() {
SharedPreferences.Editor editor = directPrefs.edit();
editor.remove("Title");
editor.remove("Desc");
editor.remove("Category");
editor.remove(WIKIDATA_ENTITY_ID_PREF);
editor.remove(WIKIDATA_ITEM_LOCATION);
editor.remove(IS_DIRECT_UPLOAD);
editor.apply();
}
/**

View file

@ -97,7 +97,11 @@ public class UploadPresenter {
* @param source File source from {@link Contribution.FileSource}
*/
@SuppressLint("CheckResult")
void receiveDirect(Uri media, String mimeType, @Contribution.FileSource String source, String wikidataEntityIdPref, String title, String desc, String wikidataItemLocation) {
void receiveDirect(Uri media, String mimeType,
@Contribution.FileSource String source,
String wikidataEntityIdPref,
String title, String desc,
String wikidataItemLocation) {
Completable.fromRunnable(() -> uploadModel.receiveDirect(media, mimeType, source, wikidataEntityIdPref, title, desc, similarImageInterface, wikidataItemLocation))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

View file

@ -1,46 +0,0 @@
package fr.free.nrw.commons.utils;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import timber.log.Timber;
/**
* Created by root on 23.07.2018.
*/
public class ExternalStorageUtils {
/**
* Checks if external storage permission is granted
* @param context activity we are on
* @return true if permission is granted, false if not
*/
public static boolean isStoragePermissionGranted(Context context) {
if (Build.VERSION.SDK_INT >= 23) {
if (context.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Timber.d("External storage permission granted, API >= 23");
return true;
} else {
Timber.d("External storage permission not granted, API >= 23");
return false;
}
} else { //permission is automatically granted on sdk<23 upon installation
Timber.d("External storage permission granted before, API < 23");
return true;
}
}
/**
* Requests external storage permission
* @param context activity we are on
*/
public static void requestExternalStoragePermission(Context context) {
Timber.d("External storage permission requested");
ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
}

View file

@ -9,26 +9,6 @@ import timber.log.Timber;
public class FragmentUtils {
public static boolean addAndCommitFragmentWithImmediateExecution(
@NonNull FragmentManager fragmentManager,
@IdRes int containerViewId,
@NonNull Fragment fragment) {
if (fragment.isAdded()) {
Timber.w("Could not add fragment. The fragment is already added.");
return false;
}
try {
fragmentManager.beginTransaction()
.add(containerViewId, fragment)
.commitNow();
return true;
} catch (IllegalStateException e) {
Timber.e(e, "Could not add & commit fragment. "
+ "Did you mean to call commitAllowingStateLoss?");
}
return false;
}
/**
* Utility function to check whether the fragment UI is still active or not
* @param fragment

View file

@ -9,8 +9,8 @@ import android.graphics.Rect;
import android.net.Uri;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Log;
import com.esafirm.imagepicker.model.Image;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
@ -24,6 +24,8 @@ import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
@ -253,4 +255,15 @@ public class ImageUtils {
return errorMessage.toString();
}
public static ArrayList<Uri> getUriListFromImages(List<Image> imageList) {
ArrayList<Uri> uriList = new ArrayList<>();
for (Image imagePath : imageList) {
if (!StringUtils.isNullOrWhiteSpace(imagePath.getPath())) {
uriList.add(Uri.parse(imagePath.getPath()));
}
}
return uriList;
}
}

View file

@ -0,0 +1,41 @@
package fr.free.nrw.commons.utils;
import android.app.Activity;
import android.content.Intent;
import static fr.free.nrw.commons.contributions.ContributionController.BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.CAMERA_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.GALLERY_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_CAMERA_UPLOAD_REQUEST_CODE;
import static fr.free.nrw.commons.contributions.ContributionController.NEARBY_GALLERY_UPLOAD_REQUEST_CODE;
public class IntentUtils {
/**
* Check if the intent should be handled by nearby list or map fragment
*/
public static boolean shouldNearbyHandle(int requestCode, int resultCode, Intent data) {
return resultCode == Activity.RESULT_OK
&& (requestCode == NEARBY_CAMERA_UPLOAD_REQUEST_CODE || requestCode == NEARBY_GALLERY_UPLOAD_REQUEST_CODE)
&& data != null;
}
/**
* Check if the intent should be handled by contributions list fragment
*/
public static boolean shouldContributionsListHandle(int requestCode, int resultCode, Intent data) {
return resultCode == Activity.RESULT_OK
&& (requestCode == GALLERY_UPLOAD_REQUEST_CODE || requestCode == CAMERA_UPLOAD_REQUEST_CODE)
&& data != null;
}
/**
* Check if the intent should be handled by contributions list fragment
*/
public static boolean shouldBookmarksHandle(int requestCode, int resultCode, Intent data) {
return resultCode == Activity.RESULT_OK
&& (requestCode == BOOKMARK_CAMERA_UPLOAD_REQUEST_CODE || requestCode == BOOKMARK_GALLERY_UPLOAD_REQUEST_CODE)
&& data != null;
}
}

View file

@ -5,9 +5,18 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.Settings;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.BasePermissionListener;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
public class PermissionUtils {
@ -17,8 +26,7 @@ public class PermissionUtils {
It open the app settings from where the user can manually give us the required permission.
* @param activity
*/
public static void askUserToManuallyEnablePermissionFromSettings(
Activity activity) {
public static void askUserToManuallyEnablePermissionFromSettings(Activity activity) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
@ -36,4 +44,62 @@ public class PermissionUtils {
return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
}
/**
* Checks for a particular permission and runs the runnable to perform an action when the permission is granted
* Also, it shows a rationale if needed
*
* Sample usage:
*
* PermissionUtils.checkPermissionsAndPerformAction(activity,
* Manifest.permission.WRITE_EXTERNAL_STORAGE,
* () -> initiateCameraUpload(activity),
* R.string.storage_permission_title,
* R.string.write_storage_permission_rationale);
*
*
* @param activity activity requesting permissions
* @param permission the permission being requests
* @param onPermissionGranted the runnable to be executed when the permission is granted
* @param rationaleTitle rationale title to be displayed when permission was denied
* @param rationaleMessage rationale message to be displayed when permission was denied
*/
public static void checkPermissionsAndPerformAction(Activity activity,
String permission,
Runnable onPermissionGranted,
@StringRes int rationaleTitle,
@StringRes int rationaleMessage) {
Dexter.withActivity(activity)
.withPermission(permission)
.withListener(new BasePermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
onPermissionGranted.run();
}
@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
if (response.isPermanentlyDenied()) {
DialogUtil.showAlertDialog(activity,
activity.getString(rationaleTitle),
activity.getString(rationaleMessage),
activity.getString(R.string.navigation_item_settings),
null,
() -> askUserToManuallyEnablePermissionFromSettings(activity),
null);
}
}
@Override
public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
DialogUtil.showAlertDialog(activity,
activity.getString(rationaleTitle),
activity.getString(rationaleMessage),
activity.getString(android.R.string.ok),
activity.getString(android.R.string.cancel),
token::continuePermissionRequest,
token::cancelPermissionRequest);
}
}).check();
}
}

View file

@ -3,4 +3,5 @@ package fr.free.nrw.commons.wikidata;
public class WikidataConstants {
public static final String WIKIDATA_ENTITY_ID_PREF = "WikiDataEntityId";
public static final String WIKIDATA_ITEM_LOCATION = "WikiDataItemLocation";
public static final String IS_DIRECT_UPLOAD = "isDirectUpload";
}