Browse Source

Support for runtime permissios, to read/write local storage for settings/torrents. Fixes #258.

pull/280/head
Eric Kok 8 years ago
parent
commit
f82ba6e12b
  1. 2
      app/build.gradle
  2. 25
      app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java
  3. 18
      app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
  4. 80
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java
  5. 66
      app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java
  6. 6
      app/src/main/res/values/strings.xml
  7. 2
      build.gradle
  8. 4
      gradle/wrapper/gradle-wrapper.properties

2
app/build.gradle

@ -28,7 +28,7 @@ android { @@ -28,7 +28,7 @@ android {
}
}
lintOptions {
disable 'MissingTranslation', 'ExtraTranslation'
disable 'MissingTranslation', 'ExtraTranslation', 'StringFormatInvalid'
}
}

25
app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java

@ -134,9 +134,9 @@ public class SettingsPersistence { @@ -134,9 +134,9 @@ public class SettingsPersistence {
if (server.has("new_torrent_alarm"))
editor.putBoolean("server_alarmnew_" + postfix, server.getBoolean("new_torrent_alarm"));
if (server.has("alarm_filter_exclude"))
editor.putBoolean("server_alarmexclude_" + postfix, server.getBoolean("alarm_filter_exclude"));
editor.putString("server_alarmexclude_" + postfix, server.getString("alarm_filter_exclude"));
if (server.has("alarm_filter_include"))
editor.putBoolean("server_alarminclude_" + postfix, server.getBoolean("alarm_filter_include"));
editor.putString("server_alarminclude_" + postfix, server.getString("alarm_filter_include"));
}
}
@ -174,11 +174,13 @@ public class SettingsPersistence { @@ -174,11 +174,13 @@ public class SettingsPersistence {
if (feed.has("new_item_alarm"))
editor.putBoolean("rssfeed_alarmnew_" + postfix, feed.getBoolean("new_item_alarm"));
if (feed.has("alarm_filter_include"))
editor.putBoolean("rssfeed_alarminclude_" + postfix, feed.getBoolean("alarm_filter_include"));
editor.putString("rssfeed_include_" + postfix, feed.getString("alarm_filter_include"));
if (feed.has("alarm_filter_exclude"))
editor.putBoolean("rssfeed_alarmexclude_" + postfix, feed.getBoolean("alarm_filter_exclude"));
if (feed.has("last_seen"))
editor.putString("rssfeed_lastnew_" + postfix, feed.getString("last_seen"));
editor.putString("rssfeed_exclude_" + postfix, feed.getString("alarm_filter_exclude"));
if (feed.has("last_seen_time"))
editor.putLong("rssfeed_lastviewed_" + postfix, feed.getLong("last_seen_time"));
if (feed.has("last_seen_item"))
editor.putString("rssfeed_lastvieweditemurl_" + postfix, feed.getString("last_seen_item"));
}
}
@ -280,8 +282,8 @@ public class SettingsPersistence { @@ -280,8 +282,8 @@ public class SettingsPersistence {
server.put("server_timeout", prefs.getString("server_timeout_" + postfixi, null));
server.put("download_alarm", prefs.getBoolean("server_alarmfinished_" + postfixi, false));
server.put("new_torrent_alarm", prefs.getBoolean("server_alarmnew_" + postfixi, false));
server.put("alarm_filter_exclude", prefs.getBoolean("server_alarmexclude_" + postfixi, false));
server.put("alarm_filter_include", prefs.getBoolean("server_alarminclude_" + postfixi, false));
server.put("alarm_filter_exclude", prefs.getString("server_alarmexclude_" + postfixi, null));
server.put("alarm_filter_include", prefs.getString("server_alarminclude_" + postfixi, null));
servers.put(server);
i++;
@ -317,9 +319,10 @@ public class SettingsPersistence { @@ -317,9 +319,10 @@ public class SettingsPersistence {
feed.put("url", prefs.getString("rssfeed_url_" + postfixk, null));
feed.put("needs_auth", prefs.getBoolean("rssfeed_reqauth_" + postfixk, false));
feed.put("new_item_alarm", prefs.getBoolean("rssfeed_alarmnew_" + postfixk, false));
feed.put("alarm_filter_exclude", prefs.getBoolean("server_alarmexclude_" + postfixk, false));
feed.put("alarm_filter_include", prefs.getBoolean("server_alarminclude_" + postfixk, false));
feed.put("last_seen", prefs.getString("rssfeed_lastnew_" + postfixk, null));
feed.put("alarm_filter_exclude", prefs.getString("server_alarmexclude_" + postfixk, null));
feed.put("alarm_filter_include", prefs.getString("server_alarminclude_" + postfixk, null));
feed.put("last_seen_time", prefs.getLong("rssfeed_lastviewed_" + postfixk, -1));
feed.put("last_seen_item", prefs.getString("rssfeed_lastvieweditemurl_" + postfixk, null));
feeds.put(feed);
k++;

18
app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java

@ -24,6 +24,7 @@ import android.net.Uri; @@ -24,6 +24,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
@ -215,6 +216,9 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE @@ -215,6 +216,9 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
// Auto refresh task
private AsyncTask<Void, Void, Void> autoRefreshTask;
private String awaitingAddLocalFile;
private String awaitingAddTitle;
@Override
public void onCreate(Bundle savedInstanceState) {
// Set the theme according to the user preference
@ -739,6 +743,14 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE @@ -739,6 +743,14 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (awaitingAddLocalFile != null && awaitingAddTitle != null &&
Boolean.TRUE.equals(navigationHelper.handleTorrentReadPermissionResult(requestCode, grantResults))) {
addTorrentByFile(awaitingAddLocalFile, awaitingAddTitle);
}
}
@Click(R.id.addmenu_link_button)
protected void startUrlEntryDialog() {
addmenuButton.collapse();
@ -1043,6 +1055,12 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE @@ -1043,6 +1055,12 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
@Background
protected void addTorrentByFile(String localFile, String title) {
if (!navigationHelper.checkTorrentReadPermission(this)) {
// No read permission yet (which we get the result of in onRequestPermissionsResult)
awaitingAddLocalFile = localFile;
awaitingAddTitle = title;
return;
}
DaemonTaskResult result = AddByFileTask.create(currentConnection, localFile).execute(log);
if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));

80
app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java

@ -16,15 +16,25 @@ @@ -16,15 +16,25 @@
*/
package org.transdroid.core.gui.navigation;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.TypefaceSpan;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache;
@ -50,10 +60,80 @@ import java.util.List; @@ -50,10 +60,80 @@ import java.util.List;
@EBean
public class NavigationHelper {
private static final int REQUEST_TORRENT_READ_PERMISSION = 0;
private static final int REQUEST_SETTINGS_READ_PERMISSION = 1;
private static final int REQUEST_SETTINGS_WRITE_PERMISSION = 2;
private static ImageLoader imageCache;
@RootContext
protected Context context;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkTorrentReadPermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_TORRENT_READ_PERMISSION);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkSettingsReadPermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_SETTINGS_READ_PERMISSION);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkSettingsWritePermission(final Activity activity) {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUEST_SETTINGS_WRITE_PERMISSION);
}
private boolean checkPermission(final Activity activity, final String permission, final int requestCode) {
if (hasPermission(permission))
// Permission already granted
return true;
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
// Never asked again: show a dialog with an explanation
new MaterialDialog.Builder(context).content(R.string.permission_readtorrent).positiveText(android.R.string.ok)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
}
}).show();
return false;
}
// Permission not granted (and we asked for it already before)
ActivityCompat.requestPermissions(activity, new String[]{permission}, REQUEST_TORRENT_READ_PERMISSION);
return false;
}
private boolean hasPermission(String requiredPermission) {
return ContextCompat.checkSelfPermission(context, requiredPermission) == PackageManager.PERMISSION_GRANTED;
}
public Boolean handleTorrentReadPermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_TORRENT_READ_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
public Boolean handleSettingsReadPermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_SETTINGS_READ_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
public Boolean handleSettingsWritePermissionResult(int requestCode, int[] grantResults) {
if (requestCode == REQUEST_SETTINGS_WRITE_PERMISSION) {
// Return permission granting result
return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}
return null;
}
/**
* Converts a string into a {@link Spannable} that displays the string in the Roboto Condensed font
* @param string A plain text {@link String}

66
app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java

@ -28,8 +28,8 @@ import android.os.Bundle; @@ -28,8 +28,8 @@ import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.nispok.snackbar.Snackbar;
@ -81,6 +81,7 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { @@ -81,6 +81,7 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
protected ErrorLogSender errorLogSender;
@Bean
protected SettingsPersistence settingsPersistence;
private OnPreferenceClickListener onCheckUpdatesClick = new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
@ -103,19 +104,12 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { @@ -103,19 +104,12 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
private OnClickListener importSettingsFromFile = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try {
settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE);
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success));
} catch (FileNotFoundException e) {
SnackbarManager
.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_file_not_found).colorResource(R.color.red));
} catch (JSONException e) {
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this)
.text(getString(R.string.error_no_valid_settings_file, getString(R.string.app_name))).colorResource(R.color.red));
}
if (!navigationHelper.checkSettingsReadPermission(SystemSettingsActivity.this))
return; // We are requesting permission to access file storage
importSettingsFromFile();
}
};
private OnClickListener importSettingsFromQr = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@ -125,16 +119,12 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { @@ -125,16 +119,12 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
private OnClickListener exportSettingsToFile = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try {
settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE);
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_export_success));
} catch (JSONException | IOException e) {
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file)
.colorResource(R.color.red));
}
if (!navigationHelper.checkSettingsWritePermission(SystemSettingsActivity.this))
return; // We are requesting permission to access file storage
exportSettingsToFile();
}
};
private OnClickListener exportSettingsToQr = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@ -176,8 +166,42 @@ public class SystemSettingsActivity extends PreferenceCompatActivity { @@ -176,8 +166,42 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
MainSettingsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (Boolean.TRUE.equals(navigationHelper.handleSettingsReadPermissionResult(requestCode, grantResults))) {
importSettingsFromFile();
} else if (Boolean.TRUE.equals(navigationHelper.handleSettingsWritePermissionResult(requestCode, grantResults))) {
exportSettingsToFile();
}
}
private void importSettingsFromFile() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try {
settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE);
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success));
} catch (FileNotFoundException e) {
SnackbarManager
.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_file_not_found).colorResource(R.color.red));
} catch (JSONException e) {
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this)
.text(getString(R.string.error_no_valid_settings_file, getString(R.string.app_name))).colorResource(R.color.red));
}
}
private void exportSettingsToFile() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try {
settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE);
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_export_success));
} catch (JSONException | IOException e) {
SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file)
.colorResource(R.color.red));
}
}
@OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_QRSETTINGS)
public void onQrCodeScanned(int resultCode, Intent data) {
public void onQrCodeScanned(@SuppressWarnings("UnusedParameters") int resultCode, Intent data) {
// We should have received Intent extras with the QR-decoded data representing Transdroid settings
if (data == null || !data.hasExtra("SCAN_RESULT"))
return; // Cancelled scan; ignore

6
app/src/main/res/values/strings.xml

@ -435,7 +435,11 @@ @@ -435,7 +435,11 @@
<item>43200</item>
<item>86400</item>
</string-array>
<string name="permission_readtorrent">Transdroid requires read access to your file storage in order to read local .torrent files</string>
<string name="permission_readsettings">Transdroid requires read access to your file storage if you want to read from a local settings file</string>
<string name="permission_writesettings">Transdroid requires write access to your file storage to write the local settings file</string>
<string name="error_httperror">Error during communication; check your connection</string>
<string name="error_unsupported">Your torrent client does not support this operation</string>
<string name="error_jsonrequesterror">Internal error building request</string>

2
build.gradle

@ -3,7 +3,7 @@ buildscript { @@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.android.tools.build:gradle:2.0.0-alpha5'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}

4
gradle/wrapper/gradle-wrapper.properties vendored

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
#Tue Dec 09 09:45:38 CET 2014
#Wed Jan 20 12:20:00 CET 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

Loading…
Cancel
Save