Merge pull request #121 from domdomegg/fix-permissions-117

Fix permissions on API levels 23 and above
This commit is contained in:
Josephine Lim 2016-06-14 00:38:45 +12:00 committed by GitHub
commit f6d84b3013
7 changed files with 218 additions and 77 deletions

View file

@ -1,23 +1,25 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
dependencies { dependencies {
compile fileTree(dir: 'libs', include: '*.jar') compile fileTree(include: '*.jar', dir: 'libs')
compile 'com.google.code.gson:gson:1.4'
compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar' compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
compile 'in.yuvi:http.fluent:1.3' compile 'in.yuvi:http.fluent:1.3'
compile 'com.android.volley:volley:1.0.0' compile 'com.android.volley:volley:1.0.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.8.4' compile 'com.nostra13.universalimageloader:universal-image-loader:1.8.4'
compile 'ch.acra:acra:4.5.0' compile 'ch.acra:acra:4.5.0'
compile 'org.mediawiki:api:1.3' compile 'org.mediawiki:api:1.3'
compile 'de.keyboardsurfer.android.widget:crouton:1.8.5@aar' compile 'commons-codec:commons-codec:1.10'
compile group: 'commons-codec', name: 'commons-codec', version: '1.10'
compile 'com.android.support:support-v4:23.4.0' compile 'com.android.support:support-v4:23.4.0'
compile 'com.android.support:appcompat-v7:23.4.0' compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
//noinspection GradleDependency - old version has required feature
compile 'com.google.code.gson:gson:1.4'
} }
android { android {
compileSdkVersion 23 compileSdkVersion 23
buildToolsVersion "23.0.2" buildToolsVersion "23.0.3"
useLibrary 'org.apache.http.legacy' useLibrary 'org.apache.http.legacy'

View file

@ -8,16 +8,16 @@
android:targetSdkVersion="23" /> android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.READ_SYNC_STATS"/> <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
<application <application
android:name=".CommonsApplication" android:name=".CommonsApplication"

View file

@ -1,26 +1,39 @@
package fr.free.nrw.commons.auth; package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.NavUtils;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException; import java.io.IOException;
import android.content.*;
import android.net.Uri;
import android.text.*;
import android.view.inputmethod.EditorInfo;
import de.keyboardsurfer.android.widget.crouton.*;
import android.os.*;
import android.accounts.*;
import android.app.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import android.support.v4.app.NavUtils;
import fr.free.nrw.commons.*;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.EventLog; import fr.free.nrw.commons.EventLog;
import fr.free.nrw.commons.contributions.*; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
public class LoginActivity extends AccountAuthenticatorActivity { public class LoginActivity extends AccountAuthenticatorActivity {
@ -79,7 +92,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
} else if(result.equals("NotExists") || result.equals("Illegal") || result.equals("NotExists")) { } else if(result.equals("NotExists") || result.equals("Illegal") || result.equals("NotExists")) {
response = R.string.login_failed_username; response = R.string.login_failed_username;
passwordEdit.setText(""); passwordEdit.setText("");
} else if(result.equals("EmptyPass") || result.equals("WrongPass")) { } else if(result.equals("EmptyPass") || result.equals("WrongPass") || result.equals("WrongPluginPass")) {
response = R.string.login_failed_password; response = R.string.login_failed_password;
passwordEdit.setText(""); passwordEdit.setText("");
} else if(result.equals("Throttled")) { } else if(result.equals("Throttled")) {
@ -88,10 +101,11 @@ public class LoginActivity extends AccountAuthenticatorActivity {
response = R.string.login_failed_blocked; response = R.string.login_failed_blocked;
} else { } else {
// Should never really happen // Should never really happen
Log.d("Commons", "Login failed with reason: " + result);
response = R.string.login_failed_generic; response = R.string.login_failed_generic;
} }
Crouton.makeText(context, response, Style.ALERT, R.id.loginErrors).show(); Toast.makeText(getApplicationContext(), response, Toast.LENGTH_LONG).show();
dialog.dismiss(); dialog.cancel();
} }
} }

View file

@ -9,6 +9,7 @@ import android.location.LocationManager;
import android.media.ExifInterface; import android.media.ExifInterface;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.design.widget.Snackbar;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
@ -71,7 +72,7 @@ public class GPSExtractor {
* Extracts geolocation of image from EXIF data. * Extracts geolocation of image from EXIF data.
* @return coordinates of image as string (needs to be passed as a String in API query) * @return coordinates of image as string (needs to be passed as a String in API query)
*/ */
public String getCoords() { public String getCoords(boolean useGPS) {
ExifInterface exif; ExifInterface exif;
String latitude = ""; String latitude = "";
@ -87,25 +88,27 @@ public class GPSExtractor {
return null; return null;
} }
if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) { if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null && useGPS) {
registerLocationManager();
imageCoordsExists = false; imageCoordsExists = false;
Log.d(TAG, "Picture has no GPS info"); Log.d(TAG, "EXIF data has no location info");
//Check what user's preference is for automatic location detection //Check what user's preference is for automatic location detection
boolean gpsPrefEnabled = gpsPreferenceEnabled(); boolean gpsPrefEnabled = gpsPreferenceEnabled();
if (gpsPrefEnabled) { if (gpsPrefEnabled) {
Log.d(TAG, "Current location values: Lat = " + currentLatitude + " Long = " + currentLongitude); Log.d(TAG, "Current location values: Lat = " + currentLatitude + " Long = " + currentLongitude);
String currentCoords = String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude); return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude);
return currentCoords;
} else { } else {
//Otherwise treat as if no coords found // No coords found
return null; return null;
} }
} else if(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
return null;
} else { } else {
imageCoordsExists = true; imageCoordsExists = true;
Log.d(TAG, "Picture has GPS info"); Log.d(TAG, "EXIF data has location info");
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF); latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);

View file

@ -1,14 +1,22 @@
package fr.free.nrw.commons.upload; package fr.free.nrw.commons.upload;
import android.Manifest;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v4.content.ContextCompat;
import android.util.Log; import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
@ -19,9 +27,10 @@ import java.util.List;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.EventLog; import fr.free.nrw.commons.EventLog;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.*; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
import fr.free.nrw.commons.category.CategorizationFragment; import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.contributions.*; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.modifications.CategoryModifier; import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence; import fr.free.nrw.commons.modifications.ModifierSequence;
@ -56,6 +65,12 @@ public class ShareActivity
private boolean cacheFound; private boolean cacheFound;
private GPSExtractor imageObj; private GPSExtractor imageObj;
private String filePath;
private String decimalCoords;
private boolean useNewPermissions = false;
private boolean storagePermission = false;
private boolean locationPermission = false;
public ShareActivity() { public ShareActivity() {
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE); super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
@ -167,6 +182,11 @@ public class ShareActivity
finish(); finish();
} }
/**
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
*/
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -197,57 +217,148 @@ public class ShareActivity
contribution = savedInstanceState.getParcelable("contribution"); contribution = savedInstanceState.getParcelable("contribution");
} }
requestAuthToken(); requestAuthToken();
}
/**
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
*/
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "Uri: " + mediaUriString); Log.d(TAG, "Uri: " + mediaUriString);
Log.d(TAG, "Ext storage dir: " + Environment.getExternalStorageDirectory()); Log.d(TAG, "Ext storage dir: " + Environment.getExternalStorageDirectory());
//convert image Uri to file path if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String filePath = FileUtils.getPath(this, mediaUri); useNewPermissions = true;
Log.d(TAG, "Filepath: " + filePath); if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
storagePermission = true;
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
locationPermission = true;
}
}
// Check storage permissions if marshmallow or newer
if (useNewPermissions && (!storagePermission || !locationPermission)) {
if (!storagePermission && !locationPermission) {
String permissionRationales = getResources().getString(R.string.storage_permission_rationale) + "\n" + getResources().getString(R.string.location_permission_rationale);
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), permissionRationales,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(ShareActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_FINE_LOCATION}, 3);
}
});
snackbar.show();
View snackbarView = snackbar.getView();
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
textView.setMaxLines(3);
} else if (!storagePermission) {
Snackbar.make(findViewById(android.R.id.content), R.string.storage_permission_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(ShareActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
}).show();
} else if (!locationPermission) {
Snackbar.make(findViewById(android.R.id.content), R.string.location_permission_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(ShareActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);
}
}).show();
}
} else if (useNewPermissions && storagePermission && !locationPermission) {
getFileMetadata();
} else if(!useNewPermissions || (storagePermission && locationPermission)) {
getFileMetadata();
getLocationData();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
// 1 = Storage
case 1: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getFileMetadata();
}
return;
}
// 2 = Location
case 2: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getLocationData();
}
return;
}
// 3 = Storage + Location
case 3: {
if (grantResults.length > 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getFileMetadata();
}
if (grantResults.length > 1
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
getLocationData();
}
}
}
}
public void getFileMetadata() {
filePath = FileUtils.getPath(this, mediaUri);
Log.d(TAG, "Filepath: " + filePath);
Log.d(TAG, "Calling GPSExtractor"); Log.d(TAG, "Calling GPSExtractor");
imageObj = new GPSExtractor(filePath, this); imageObj = new GPSExtractor(filePath, this);
imageObj.registerLocationManager();
if (filePath != null && !filePath.equals("")) { if (filePath != null && !filePath.equals("")) {
//Gets image coords if exist, otherwise gets last known coords // Gets image coords from exif data
String decimalCoords = imageObj.getCoords(); decimalCoords = imageObj.getCoords(false);
useImageCoords();
}
}
if (decimalCoords != null) { public void getLocationData() {
Log.d(TAG, "Decimal coords of image: " + decimalCoords); if(imageObj == null) {
imageObj = new GPSExtractor(filePath, this);
}
//Only set cache for this point if image has coords decimalCoords = imageObj.getCoords(true);
if (imageObj.imageCoordsExists) { useImageCoords();
double decLongitude = imageObj.getDecLongitude(); }
double decLatitude = imageObj.getDecLatitude();
app.cacheData.setQtPoint(decLongitude, decLatitude);
}
MwVolleyApi apiCall = new MwVolleyApi(this); public void useImageCoords() {
if(decimalCoords != null) {
Log.d(TAG, "Decimal coords of image: " + decimalCoords);
List displayCatList = app.cacheData.findCategory(); // Only set cache for this point if image has coords
boolean catListEmpty = displayCatList.isEmpty(); if (imageObj.imageCoordsExists) {
double decLongitude = imageObj.getDecLongitude();
double decLatitude = imageObj.getDecLatitude();
app.cacheData.setQtPoint(decLongitude, decLatitude);
}
//if no categories found in cache, call MW API to match image coords with nearby Commons categories MwVolleyApi apiCall = new MwVolleyApi(this);
if (catListEmpty) {
cacheFound = false;
apiCall.request(decimalCoords);
Log.d(TAG, "displayCatList size 0, calling MWAPI" + displayCatList.toString());
} else { List displayCatList = app.cacheData.findCategory();
cacheFound = true; boolean catListEmpty = displayCatList.isEmpty();
Log.d(TAG, "Cache found, setting categoryList in MwVolleyApi to " + displayCatList.toString());
MwVolleyApi.setGpsCat(displayCatList); // If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
} if (catListEmpty) {
cacheFound = false;
apiCall.request(decimalCoords);
Log.d(TAG, "displayCatList size 0, calling MWAPI" + displayCatList.toString());
} else {
cacheFound = true;
Log.d(TAG, "Cache found, setting categoryList in MwVolleyApi to " + displayCatList.toString());
MwVolleyApi.setGpsCat(displayCatList);
} }
} }
} }
@ -255,7 +366,13 @@ public class ShareActivity
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
imageObj.unregisterLocationManager(); try {
imageObj.unregisterLocationManager();
Log.d(TAG, "Unregistered locationManager");
}
catch (NullPointerException e) {
Log.d(TAG, "locationManager does not exist, not unregistered");
}
} }
@Override @Override

View file

@ -148,4 +148,9 @@
<string name="detail_license_empty">Unknown license</string> <string name="detail_license_empty">Unknown license</string>
<string name="provider_campaigns">Campaigns</string> <string name="provider_campaigns">Campaigns</string>
<string name="menu_refresh">Refresh</string> <string name="menu_refresh">Refresh</string>
<string name="storage_permission_rationale">Recommended: Storage for photo metadata</string>
<string name="location_permission_rationale">Optional: Current location for category suggestions</string>
<string name="ok">OK</string>
<string name="back">Back</string>
</resources> </resources>

View file

@ -5,7 +5,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.1.0' classpath 'com.android.tools.build:gradle:2.1.2'
} }
} }