You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
13 KiB
315 lines
13 KiB
/* |
|
* Copyright 2010-2018 Eric Kok et al. |
|
* |
|
* Transdroid is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* Transdroid is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
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 androidx.annotation.NonNull; |
|
import androidx.core.app.ActivityCompat; |
|
import androidx.core.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; |
|
import com.nostra13.universalimageloader.core.DisplayImageOptions; |
|
import com.nostra13.universalimageloader.core.ImageLoader; |
|
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration.Builder; |
|
import com.nostra13.universalimageloader.core.assist.ImageScaleType; |
|
|
|
import org.androidannotations.annotations.EBean; |
|
import org.androidannotations.annotations.RootContext; |
|
import org.transdroid.BuildConfig; |
|
import org.transdroid.R; |
|
|
|
import java.io.IOException; |
|
import java.util.List; |
|
|
|
/** |
|
* Helper for activities to make navigation-related decisions, such as when a device can display a larger, tablet style layout or how to display |
|
* errors. |
|
* |
|
* @author Eric Kok |
|
*/ |
|
@SuppressLint("ResourceAsColor") |
|
@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 |
|
activity.runOnUiThread(new Runnable() { |
|
public void run() { |
|
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} |
|
* @return A {@link Spannable} that can be applied to supporting views (such as the action bar title) so that the input string will be displayed |
|
* using the Roboto Condensed font (if the OS has this) |
|
*/ |
|
public static SpannableString buildCondensedFontString(String string) { |
|
if (string == null) { |
|
return null; |
|
} |
|
SpannableString s = new SpannableString(string); |
|
s.setSpan(new TypefaceSpan("sans-serif-condensed"), 0, s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
|
return s; |
|
} |
|
|
|
/** |
|
* Analyses a torrent http or magnet URI and tries to come up with a reasonable human-readable name. |
|
* |
|
* @param rawTorrentUri The raw http:// or magnet: link to the torrent |
|
* @return A best-guess, reasonably long name for the linked torrent |
|
*/ |
|
public static String extractNameFromUri(Uri rawTorrentUri) { |
|
|
|
if (rawTorrentUri.getScheme() == null) { |
|
// Probably an incorrect URI; just return the whole thing |
|
return rawTorrentUri.toString(); |
|
} |
|
|
|
if (rawTorrentUri.getScheme().equals("magnet")) { |
|
// Magnet links might have a dn (display name) parameter |
|
String dn = getQueryParameter(rawTorrentUri, "dn"); |
|
if (dn != null && !dn.equals("")) { |
|
return dn; |
|
} |
|
// If not, try to return the hash that is specified as xt (exact topci) |
|
String xt = getQueryParameter(rawTorrentUri, "xt"); |
|
if (xt != null && !xt.equals("")) { |
|
return xt; |
|
} |
|
} |
|
|
|
if (rawTorrentUri.isHierarchical()) { |
|
String path = rawTorrentUri.getPath(); |
|
if (path != null) { |
|
if (path.contains("/")) { |
|
path = path.substring(path.lastIndexOf("/")); |
|
} |
|
return path; |
|
} |
|
} |
|
|
|
// No idea what to do with this; return as is |
|
return rawTorrentUri.toString(); |
|
} |
|
|
|
private static String getQueryParameter(Uri uri, String parameter) { |
|
int start = uri.toString().indexOf(parameter + "="); |
|
if (start >= 0) { |
|
int begin = start + (parameter + "=").length(); |
|
int end = uri.toString().indexOf("&", begin); |
|
return uri.toString().substring(begin, end >= 0 ? end : uri.toString().length()); |
|
} |
|
return null; |
|
} |
|
|
|
/** |
|
* Returns (and initialises, if needed) an image cache that uses memory and (1MB) local storage. |
|
* |
|
* @return An image cache that loads web images synchronously and transparently |
|
*/ |
|
public ImageLoader getImageCache() { |
|
if (imageCache == null) { |
|
imageCache = ImageLoader.getInstance(); |
|
try { |
|
LruDiskCache diskCache = new LruDiskCache(context.getCacheDir(), null, new Md5FileNameGenerator(), 640000, 25); |
|
// @formatter:off |
|
Builder imageCacheBuilder = new Builder(context) |
|
.defaultDisplayImageOptions( |
|
new DisplayImageOptions.Builder() |
|
.cacheInMemory(true) |
|
.cacheOnDisk(true) |
|
.imageScaleType(ImageScaleType.IN_SAMPLE_INT) |
|
.showImageForEmptyUri(R.drawable.ic_launcher).build()) |
|
.memoryCache(new UsingFreqLimitedMemoryCache(1024 * 1024)) |
|
.diskCache(diskCache); |
|
imageCache.init(imageCacheBuilder.build()); |
|
// @formatter:on |
|
} catch (IOException e) { |
|
// The cache directory is always available on Android; ignore this exception |
|
} |
|
} |
|
return imageCache; |
|
} |
|
|
|
public void forceOpenInBrowser(Uri link) { |
|
Intent intent = new Intent(Intent.ACTION_VIEW).setData(link); |
|
List<ResolveInfo> activities = context.getPackageManager().queryIntentActivities(intent, 0); |
|
for (ResolveInfo resolveInfo : activities) { |
|
if (activities.size() == 1 || (resolveInfo.isDefault && resolveInfo.activityInfo.packageName.equals(context.getPackageName()))) { |
|
// There is a default browser; use this |
|
intent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name); |
|
return; |
|
} |
|
} |
|
// No default browser found: open chooser |
|
try { |
|
context.startActivity(Intent.createChooser(intent, "Open...")); |
|
} catch (Exception e) { |
|
// No browser installed; consume and fail silently |
|
} |
|
} |
|
|
|
/** |
|
* Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like 180). |
|
* |
|
* @return The app name and version, such as 'Transdroid 1.5.0 (180)' |
|
*/ |
|
public String getAppNameAndVersion() { |
|
return context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")"; |
|
} |
|
|
|
/** |
|
* Returns whether the device is considered small (i.e. a phone) rather than large (i.e. a tablet). Can, for example, be used to determine if a |
|
* dialog should be shown full screen. Currently is true if the device's smallest dimension is 500 dip. |
|
* |
|
* @return True if the app runs on a small device, false otherwise |
|
*/ |
|
public boolean isSmallScreen() { |
|
return context.getResources().getBoolean(R.bool.show_dialog_fullscreen); |
|
} |
|
|
|
/** |
|
* Whether any search-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite |
|
* version. |
|
* |
|
* @return True if search is enabled, false otherwise |
|
*/ |
|
public boolean enableSearchUi() { |
|
return context.getResources().getBoolean(R.bool.search_available); |
|
} |
|
|
|
/** |
|
* Whether any RSS-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite version. |
|
* |
|
* @return True if search is enabled, false otherwise |
|
*/ |
|
public boolean enableRssUi() { |
|
return context.getResources().getBoolean(R.bool.rss_available); |
|
} |
|
|
|
/** |
|
* Returns whether any seedbox-related components should be shown in the interface; specifically the option to add server settings via easy |
|
* seedbox-specific screens. |
|
* |
|
* @return True if seedbox settings should be shown, false otherwise |
|
*/ |
|
public boolean enableSeedboxes() { |
|
return context.getResources().getBoolean(R.bool.seedboxes_available); |
|
} |
|
|
|
/** |
|
* Whether the custom app update checker should be used to check for new app and search module versions. |
|
* |
|
* @return True if it should be checked against transdroid.org if there are app updates (as opposed to using the Play Store for updates, for |
|
* example), false otherwise |
|
*/ |
|
public boolean enableUpdateChecker() { |
|
return context.getResources().getBoolean(R.bool.updatecheck_available); |
|
} |
|
|
|
}
|
|
|