Skip to content

Feature/permissions library #1855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ dependencies {
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"

//For handling runtime permissions
implementation 'com.karumi:dexter:5.0.0'

}

android {
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public class CommonsApplication extends Application {
@Inject @Named("application_preferences") SharedPreferences applicationPrefs;
@Inject @Named("prefs") SharedPreferences otherPrefs;

/**
* Constants begin
*/
public static final int OPEN_APPLICATION_DETAIL_SETTINGS = 1001;

public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using [[COM:MOA|Commons Mobile App]]";

public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
Expand All @@ -66,6 +71,10 @@ public class CommonsApplication extends Application {

public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll";

/**
* Constants End
*/

private RefWatcher refWatcher;


Expand Down
120 changes: 104 additions & 16 deletions app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fr.free.nrw.commons.upload;

import android.Manifest;
import android.Manifest.permission;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
Expand All @@ -12,7 +14,6 @@
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
Expand All @@ -23,6 +24,15 @@
import android.widget.AdapterView;
import android.widget.Toast;

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.CommonsApplication;
import fr.free.nrw.commons.utils.DialogUtil;
import fr.free.nrw.commons.utils.DialogUtil.Callback;
import fr.free.nrw.commons.utils.PermissionUtils;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -41,7 +51,6 @@
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
Expand Down Expand Up @@ -81,6 +90,11 @@ public class MultipleShareActivity extends AuthenticatedActivity
private boolean locationPermitted = false;
private boolean isMultipleUploadsPrepared = false;
private boolean isMultipleUploadsFinalised = false; // Checks is user clicked to upload button or regret before this phase
private final String TAG="#MultipleShareActivity#";
private AlertDialog storagePermissionInfoDialog;
private DexterBuilder dexterStoragePermissionBuilder;

private PermissionDeniedResponse permissionDeniedResponse;

@Override
public Media getMediaAtPosition(int i) {
Expand Down Expand Up @@ -124,17 +138,6 @@ public void OnMultipleUploadInitiated() {
multipleUploadBegins();
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 1 && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Timber.d("onRequestPermissionsResult external storage permission granted");
prepareMultipleUpoadList();
} else {
// Permission is not granted, close activity
finish();
}
}

private void multipleUploadBegins() {

Timber.d("Multiple upload begins");
Expand Down Expand Up @@ -216,6 +219,7 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_multiple_uploads);
ButterKnife.bind(this);
initDrawer();
initPermissionsRationaleDialog();

if (savedInstanceState != null) {
photosList = savedInstanceState.getParcelableArrayList("uploadsList");
Expand All @@ -233,6 +237,47 @@ protected void onCreate(Bundle savedInstanceState) {
}
}


/**
* 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(
MultipleShareActivity.this,
getString(R.string.storage_permission), getString(
R.string.write_storage_permission_rationale_for_image_share),
R.drawable.ic_launcher, new 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
return;
}
else if (permissionDeniedResponse.isPermanentlyDenied()) {
PermissionUtils.askUserToManuallyEnablePermissionFromSettings(MultipleShareActivity.this);
} else {
//or if we still have chance to show runtime permission dialog, we show him that.
askDexterToHandleExternalStoragePermission();
}
}

@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 anymore
finish();
}
});
}
}

@Override
protected void onDestroy() {
super.onDestroy();
Expand Down Expand Up @@ -275,20 +320,63 @@ protected void onAuthCookieAcquired(String authCookie) {
isMultipleUploadsPrepared = false;
mwApi.setAuthCookie(authCookie);
if (!ExternalStorageUtils.isStoragePermissionGranted(this)) {
ExternalStorageUtils.requestExternalStoragePermission(this);
//If permission is not there, handle the negative cases
askDexterToHandleExternalStoragePermission();
isMultipleUploadsPrepared = false;
return; // Postpone operation to do after gettion permission
} else {
isMultipleUploadsPrepared = true;
prepareMultipleUpoadList();
prepareMultipleUploadList();
}
}

/**
* 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(TAG, "External storage permission is being requested");
if (null == dexterStoragePermissionBuilder) {
dexterStoragePermissionBuilder = Dexter.withActivity(this)
.withPermission(permission.WRITE_EXTERNAL_STORAGE)
.withListener(new BasePermissionListener() {
@Override
public void onPermissionGranted(PermissionGrantedResponse response) {
Timber.d(TAG,"User has granted us the permission for writing the external storage");
//If permission is granted, well and good
prepareMultipleUploadList();
}

@Override
public void onPermissionDenied(PermissionDeniedResponse response) {
Timber.d(TAG,"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();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS) {
//OnActivity result, no matter what the result is, our function can handle that.
askDexterToHandleExternalStoragePermission();
}
}

/**
* Prepares a list from files will be uploaded. Saves these files temporarily to external
* storage. Adds them to uploads list
*/
private void prepareMultipleUpoadList() {
private void prepareMultipleUploadList() {
Intent intent = getIntent();

if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package fr.free.nrw.commons.utils;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;

import fr.free.nrw.commons.R;
import timber.log.Timber;

public class DialogUtil {
Expand Down Expand Up @@ -92,4 +96,31 @@ public static void showSafely(FragmentActivity activity, DialogFragment dialog)
Timber.e(e, "Could not show dialog.");
}
}

public static AlertDialog getAlertDialogWithPositiveAndNegativeCallbacks(
Context context, String title, String message, int iconResourceId, Callback callback) {

AlertDialog alertDialog = new Builder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(context.getString(R.string.ok), (dialog, which) -> {
callback.onPositiveButtonClicked();
dialog.dismiss();
})
.setNegativeButton(context.getString(R.string.cancel), (dialog, which) -> {
callback.onNegativeButtonClicked();
dialog.dismiss();
})
.setIcon(iconResourceId).create();

return alertDialog;

}

public interface Callback {

void onPositiveButtonClicked();

void onNegativeButtonClicked();
}
}
23 changes: 23 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fr.free.nrw.commons.utils;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import fr.free.nrw.commons.CommonsApplication;

public class PermissionUtils {

/**
* This method can be used by any activity which requires a permission which has been blocked(marked never ask again by the user)
It open the app settings from where the user can manually give us the required permission.
* @param 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);
activity.startActivityForResult(intent,CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS);
}
}
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,6 @@
<string name="images_reverted_explanation">The percentage of images you have uploaded to Commons that were not deleted</string>
<string name="images_used_explanation">The number of images you have uploaded to Commons that were used in Wikimedia articles</string>
<string name="notifications_channel_name_all">Commons Notification</string>
<string name="storage_permission">Storage Permission</string>
<string name="write_storage_permission_rationale_for_image_share">We need your permission to access the external storage of your device in order to upload images.</string>
</resources>