Browse Source

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

pull/280/head
Eric Kok 9 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. 4
      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 {
} }
} }
lintOptions { 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 {
if (server.has("new_torrent_alarm")) if (server.has("new_torrent_alarm"))
editor.putBoolean("server_alarmnew_" + postfix, server.getBoolean("new_torrent_alarm")); editor.putBoolean("server_alarmnew_" + postfix, server.getBoolean("new_torrent_alarm"));
if (server.has("alarm_filter_exclude")) 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")) 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 {
if (feed.has("new_item_alarm")) if (feed.has("new_item_alarm"))
editor.putBoolean("rssfeed_alarmnew_" + postfix, feed.getBoolean("new_item_alarm")); editor.putBoolean("rssfeed_alarmnew_" + postfix, feed.getBoolean("new_item_alarm"));
if (feed.has("alarm_filter_include")) 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")) if (feed.has("alarm_filter_exclude"))
editor.putBoolean("rssfeed_alarmexclude_" + postfix, feed.getBoolean("alarm_filter_exclude")); editor.putString("rssfeed_exclude_" + postfix, feed.getString("alarm_filter_exclude"));
if (feed.has("last_seen")) if (feed.has("last_seen_time"))
editor.putString("rssfeed_lastnew_" + postfix, feed.getString("last_seen")); 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 {
server.put("server_timeout", prefs.getString("server_timeout_" + postfixi, null)); server.put("server_timeout", prefs.getString("server_timeout_" + postfixi, null));
server.put("download_alarm", prefs.getBoolean("server_alarmfinished_" + postfixi, false)); server.put("download_alarm", prefs.getBoolean("server_alarmfinished_" + postfixi, false));
server.put("new_torrent_alarm", prefs.getBoolean("server_alarmnew_" + 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_exclude", prefs.getString("server_alarmexclude_" + postfixi, null));
server.put("alarm_filter_include", prefs.getBoolean("server_alarminclude_" + postfixi, false)); server.put("alarm_filter_include", prefs.getString("server_alarminclude_" + postfixi, null));
servers.put(server); servers.put(server);
i++; i++;
@ -317,9 +319,10 @@ public class SettingsPersistence {
feed.put("url", prefs.getString("rssfeed_url_" + postfixk, null)); feed.put("url", prefs.getString("rssfeed_url_" + postfixk, null));
feed.put("needs_auth", prefs.getBoolean("rssfeed_reqauth_" + postfixk, false)); feed.put("needs_auth", prefs.getBoolean("rssfeed_reqauth_" + postfixk, false));
feed.put("new_item_alarm", prefs.getBoolean("rssfeed_alarmnew_" + 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_exclude", prefs.getString("server_alarmexclude_" + postfixk, null));
feed.put("alarm_filter_include", prefs.getBoolean("server_alarminclude_" + postfixk, false)); feed.put("alarm_filter_include", prefs.getString("server_alarminclude_" + postfixk, null));
feed.put("last_seen", prefs.getString("rssfeed_lastnew_" + 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); feeds.put(feed);
k++; k++;

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

@ -24,6 +24,7 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.ActionBarDrawerToggle;
@ -215,6 +216,9 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
// Auto refresh task // Auto refresh task
private AsyncTask<Void, Void, Void> autoRefreshTask; private AsyncTask<Void, Void, Void> autoRefreshTask;
private String awaitingAddLocalFile;
private String awaitingAddTitle;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
// Set the theme according to the user preference // Set the theme according to the user preference
@ -739,6 +743,14 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
return true; 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) @Click(R.id.addmenu_link_button)
protected void startUrlEntryDialog() { protected void startUrlEntryDialog() {
addmenuButton.collapse(); addmenuButton.collapse();
@ -1043,6 +1055,12 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE
@Background @Background
protected void addTorrentByFile(String localFile, String title) { 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); DaemonTaskResult result = AddByFileTask.create(currentConnection, localFile).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title)); 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 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.net.Uri; 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.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.TypefaceSpan; 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.impl.ext.LruDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache; import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache;
@ -50,10 +60,80 @@ import java.util.List;
@EBean @EBean
public class NavigationHelper { 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; private static ImageLoader imageCache;
@RootContext @RootContext
protected Context context; 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 * Converts a string into a {@link Spannable} that displays the string in the Roboto Condensed font
* @param string A plain text {@link String} * @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;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener; import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.Snackbar;
@ -81,6 +81,7 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
protected ErrorLogSender errorLogSender; protected ErrorLogSender errorLogSender;
@Bean @Bean
protected SettingsPersistence settingsPersistence; protected SettingsPersistence settingsPersistence;
private OnPreferenceClickListener onCheckUpdatesClick = new OnPreferenceClickListener() { private OnPreferenceClickListener onCheckUpdatesClick = new OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
@ -103,19 +104,12 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
private OnClickListener importSettingsFromFile = new OnClickListener() { private OnClickListener importSettingsFromFile = new OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); if (!navigationHelper.checkSettingsReadPermission(SystemSettingsActivity.this))
try { return; // We are requesting permission to access file storage
settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); importSettingsFromFile();
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 OnClickListener importSettingsFromQr = new OnClickListener() { private OnClickListener importSettingsFromQr = new OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
@ -125,16 +119,12 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
private OnClickListener exportSettingsToFile = new OnClickListener() { private OnClickListener exportSettingsToFile = new OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); if (!navigationHelper.checkSettingsWritePermission(SystemSettingsActivity.this))
try { return; // We are requesting permission to access file storage
settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); exportSettingsToFile();
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));
}
} }
}; };
private OnClickListener exportSettingsToQr = new OnClickListener() { private OnClickListener exportSettingsToQr = new OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
@ -176,8 +166,42 @@ public class SystemSettingsActivity extends PreferenceCompatActivity {
MainSettingsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); 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) @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 // We should have received Intent extras with the QR-decoded data representing Transdroid settings
if (data == null || !data.hasExtra("SCAN_RESULT")) if (data == null || !data.hasExtra("SCAN_RESULT"))
return; // Cancelled scan; ignore return; // Cancelled scan; ignore

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

@ -436,6 +436,10 @@
<item>86400</item> <item>86400</item>
</string-array> </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_httperror">Error during communication; check your connection</string>
<string name="error_unsupported">Your torrent client does not support this operation</string> <string name="error_unsupported">Your torrent client does not support this operation</string>
<string name="error_jsonrequesterror">Internal error building request</string> <string name="error_jsonrequesterror">Internal error building request</string>

2
build.gradle

@ -3,7 +3,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { 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' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
} }
} }

4
gradle/wrapper/gradle-wrapper.properties vendored

@ -1,6 +1,6 @@
#Tue Dec 09 09:45:38 CET 2014 #Wed Jan 20 12:20:00 CET 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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