1
0
Fork 0
mirror of https://github.com/gsantner/dandelion synced 2024-11-24 13:22:08 +01:00

Update opoc

Update shared helper utilities of my projects to latest state
This commit is contained in:
Gregor Santner 2019-11-20 00:34:10 +01:00
parent 1948c28cff
commit 7361d4bc3f
No known key found for this signature in database
GPG key ID: 7E83A7834AECB009
9 changed files with 286 additions and 112 deletions

View file

@ -16,6 +16,7 @@ import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -143,4 +144,17 @@ public abstract class GsFragmentBase extends Fragment {
public Menu getFragmentMenu() { public Menu getFragmentMenu() {
return _fragmentMenu; return _fragmentMenu;
} }
/**
* Get the toolbar from activity
* Requires id to be set to @+id/toolbar
*/
@SuppressWarnings("ConstantConditions")
protected Toolbar getToolbar() {
try {
return (Toolbar) getActivity().findViewById(new ContextUtils(getActivity()).getResId(ContextUtils.ResType.ID, "toolbar"));
} catch (Exception e) {
return null;
}
}
} }

View file

@ -547,7 +547,7 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
*/ */
public Date getDateOfDaysAgo(int days) { public Date getDateOfDaysAgo(int days) {
Calendar cal = new GregorianCalendar(); Calendar cal = new GregorianCalendar();
cal.add(Calendar.DAY_OF_MONTH, -days); cal.add(Calendar.DATE, -days);
return cal.getTime(); return cal.getTime();
} }

View file

@ -11,8 +11,11 @@
package net.gsantner.opoc.ui; package net.gsantner.opoc.ui;
import android.app.Activity; import android.app.Activity;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -23,6 +26,7 @@ import android.support.v7.widget.AppCompatEditText;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.Gravity;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -56,9 +60,14 @@ public class SearchOrCustomTextDialog {
public Callback.a1<String> callback; public Callback.a1<String> callback;
public List<? extends CharSequence> data = new ArrayList<>(); public List<? extends CharSequence> data = new ArrayList<>();
public List<? extends CharSequence> highlightData = new ArrayList<>(); public List<? extends CharSequence> highlightData = new ArrayList<>();
public List<Integer> iconsForData = new ArrayList<>();
public String messageText = ""; public String messageText = "";
public boolean isSearchEnabled = true; public boolean isSearchEnabled = true;
public boolean isDarkDialog = false; public boolean isDarkDialog = false;
public int dialogWidthDp = WindowManager.LayoutParams.MATCH_PARENT;
public int dialogHeightDp = WindowManager.LayoutParams.WRAP_CONTENT;
public int gravity = Gravity.NO_GRAVITY;
public int searchInputType = 0;
@ColorInt @ColorInt
public int textColor = 0xFF000000; public int textColor = 0xFF000000;
@ -89,6 +98,17 @@ public class SearchOrCustomTextDialog {
TextView textView = (TextView) super.getView(pos, convertView, parent); TextView textView = (TextView) super.getView(pos, convertView, parent);
String text = textView.getText().toString(); String text = textView.getText().toString();
int posInOriginalList = dopt.data.indexOf(text);
if (posInOriginalList >= 0 && dopt.iconsForData != null && posInOriginalList < dopt.iconsForData.size() && dopt.iconsForData.get(posInOriginalList) != 0) {
textView.setCompoundDrawablesWithIntrinsicBounds(dopt.iconsForData.get(posInOriginalList), 0, 0, 0);
textView.setCompoundDrawablePadding(32);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
textView.setCompoundDrawableTintList(ColorStateList.valueOf(dopt.isDarkDialog ? Color.WHITE : Color.BLACK));
}
} else {
textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
boolean hl = dopt.highlightData.contains(text); boolean hl = dopt.highlightData.contains(text);
textView.setTextColor(hl ? dopt.highlightColor : dopt.textColor); textView.setTextColor(hl ? dopt.highlightColor : dopt.textColor);
textView.setTypeface(null, hl ? Typeface.BOLD : Typeface.NORMAL); textView.setTypeface(null, hl ? Typeface.BOLD : Typeface.NORMAL);
@ -132,6 +152,7 @@ public class SearchOrCustomTextDialog {
searchEditText.setTextColor(dopt.textColor); searchEditText.setTextColor(dopt.textColor);
searchEditText.setHintTextColor((dopt.textColor & 0x00FFFFFF) | 0x99000000); searchEditText.setHintTextColor((dopt.textColor & 0x00FFFFFF) | 0x99000000);
searchEditText.setHint(dopt.searchHintText); searchEditText.setHint(dopt.searchHintText);
searchEditText.setInputType(dopt.searchInputType == 0 ? searchEditText.getInputType() : dopt.searchInputType);
searchEditText.addTextChangedListener(new TextWatcher() { searchEditText.addTextChangedListener(new TextWatcher() {
@Override @Override
@ -166,9 +187,11 @@ public class SearchOrCustomTextDialog {
dialogBuilder.setMessage(dopt.messageText); dialogBuilder.setMessage(dopt.messageText);
} }
dialogBuilder.setView(linearLayout) dialogBuilder.setView(linearLayout)
.setTitle(dopt.titleText)
.setOnCancelListener(null) .setOnCancelListener(null)
.setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss()); .setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss());
if (dopt.titleText != 0) {
dialogBuilder.setTitle(dopt.titleText);
}
if (dopt.isSearchEnabled) { if (dopt.isSearchEnabled) {
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> { dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
dialogInterface.dismiss(); dialogInterface.dismiss();
@ -204,7 +227,15 @@ public class SearchOrCustomTextDialog {
} }
dialog.show(); dialog.show();
if ((w = dialog.getWindow()) != null) { if ((w = dialog.getWindow()) != null) {
w.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); int ds_w = dopt.dialogWidthDp < 100 ? dopt.dialogWidthDp : ((int) (dopt.dialogWidthDp * activity.getResources().getDisplayMetrics().density));
int ds_h = dopt.dialogHeightDp < 100 ? dopt.dialogHeightDp : ((int) (dopt.dialogHeightDp * activity.getResources().getDisplayMetrics().density));
w.setLayout(ds_w, ds_h);
}
if ((w = dialog.getWindow()) != null && dopt.gravity != Gravity.NO_GRAVITY) {
WindowManager.LayoutParams wlp = w.getAttributes();
wlp.gravity = dopt.gravity;
w.setAttributes(wlp);
} }
if (dopt.isSearchEnabled) { if (dopt.isSearchEnabled) {

View file

@ -12,6 +12,11 @@ package net.gsantner.opoc.util;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class Callback { public class Callback {
public interface a0 {
void callback();
}
public interface a1<A> { public interface a1<A> {
void callback(A arg1); void callback(A arg1);
} }

View file

@ -41,6 +41,8 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes; import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
@ -51,7 +53,9 @@ import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.ActivityManagerCompat; import android.support.v4.app.ActivityManagerCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.util.Pair; import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.text.Html; import android.text.Html;
import android.text.InputFilter; import android.text.InputFilter;
import android.text.SpannableString; import android.text.SpannableString;
@ -82,10 +86,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import static android.content.Context.VIBRATOR_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.graphics.Bitmap.CompressFormat; import static android.graphics.Bitmap.CompressFormat;
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "ObsoleteSdkInt", "deprecation", "SpellCheckingInspection"}) @SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "ObsoleteSdkInt", "deprecation", "SpellCheckingInspection", "TryFinallyCanBeTryWithResources", "UnusedAssignment"})
public class ContextUtils { public class ContextUtils {
// //
// Members, Constructors // Members, Constructors
@ -117,21 +122,29 @@ public class ContextUtils {
* *
* @return A valid id if the id could be found, else 0 * @return A valid id if the id could be found, else 0
*/ */
public int getResId(ResType resType, final String name) { public int getResId(final ResType resType, final String name) {
return _context.getResources().getIdentifier(name, resType.name().toLowerCase(), _context.getPackageName()); try {
return _context.getResources().getIdentifier(name, resType.name().toLowerCase(), _context.getPackageName());
} catch (Exception e) {
return 0;
}
} }
/** /**
* Get String by given string ressource id (nuermic) * Get String by given string ressource id (nuermic)
*/ */
public String rstr(@StringRes int strResId) { public String rstr(@StringRes final int strResId) {
return _context.getString(strResId); try {
return _context.getString(strResId);
} catch (Exception e) {
return null;
}
} }
/** /**
* Get String by given string ressource identifier (textual) * Get String by given string ressource identifier (textual)
*/ */
public String rstr(String strResKey) { public String rstr(final String strResKey) {
try { try {
return rstr(getResId(ResType.STRING, strResKey)); return rstr(getResId(ResType.STRING, strResKey));
} catch (Resources.NotFoundException e) { } catch (Resources.NotFoundException e) {
@ -142,14 +155,22 @@ public class ContextUtils {
/** /**
* Get drawable from given ressource identifier * Get drawable from given ressource identifier
*/ */
public Drawable rdrawable(@DrawableRes int resId) { public Drawable rdrawable(@DrawableRes final int resId) {
return ContextCompat.getDrawable(_context, resId); try {
return ContextCompat.getDrawable(_context, resId);
} catch (Exception e) {
return null;
}
} }
/** /**
* Get color by given color ressource id * Get color by given color ressource id
*/ */
public int rcolor(@ColorRes int resId) { public int rcolor(@ColorRes final int resId) {
if (resId == 0) {
Log.e(getClass().getName(), "ContextUtils::rcolor: resId is 0!");
return Color.BLACK;
}
return ContextCompat.getColor(_context, resId); return ContextCompat.getColor(_context, resId);
} }
@ -175,12 +196,12 @@ public class ContextUtils {
* @param intColor The color coded in int * @param intColor The color coded in int
* @param withAlpha Optional; Set first bool parameter to true to also include alpha value * @param withAlpha Optional; Set first bool parameter to true to also include alpha value
*/ */
public String colorToHexString(int intColor, boolean... withAlpha) { public static String colorToHexString(final int intColor, final boolean... withAlpha) {
boolean a = withAlpha != null && withAlpha.length >= 1 && withAlpha[0]; boolean a = withAlpha != null && withAlpha.length >= 1 && withAlpha[0];
return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor); return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor);
} }
public String getAndroidVersion() { public static String getAndroidVersion() {
return Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")"; return Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")";
} }
@ -205,7 +226,7 @@ public class ContextUtils {
src = _context.getPackageManager().getInstallerPackageName(getPackageIdManifest()); src = _context.getPackageManager().getInstallerPackageName(getPackageIdManifest());
} catch (Exception ignored) { } catch (Exception ignored) {
} }
if (TextUtils.isEmpty(src)) { if (src == null || src.trim().isEmpty()) {
return "Sideloaded"; return "Sideloaded";
} else if (src.toLowerCase().contains(".amazon.")) { } else if (src.toLowerCase().contains(".amazon.")) {
return "Amazon Appstore"; return "Amazon Appstore";
@ -213,7 +234,7 @@ public class ContextUtils {
switch (src) { switch (src) {
case "com.android.vending": case "com.android.vending":
case "com.google.android.feedback": { case "com.google.android.feedback": {
return "Google Play Store"; return "Google Play";
} }
case "org.fdroid.fdroid.privileged": case "org.fdroid.fdroid.privileged":
case "org.fdroid.fdroid": { case "org.fdroid.fdroid": {
@ -237,12 +258,12 @@ public class ContextUtils {
* If the parameter is an string a browser will get triggered * If the parameter is an string a browser will get triggered
*/ */
public void openWebpageInExternalBrowser(final String url) { public void openWebpageInExternalBrowser(final String url) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
try { try {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
_context.startActivity(intent); _context.startActivity(intent);
} catch (ActivityNotFoundException e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -252,7 +273,7 @@ public class ContextUtils {
*/ */
public String getPackageIdManifest() { public String getPackageIdManifest() {
String pkg = rstr("manifest_package_id"); String pkg = rstr("manifest_package_id");
return pkg != null ? pkg : _context.getPackageName(); return !TextUtils.isEmpty(pkg) ? pkg : _context.getPackageName();
} }
/** /**
@ -270,7 +291,7 @@ public class ContextUtils {
* of the package set in manifest (root element). * of the package set in manifest (root element).
* Falls back to applicationId of the app which may differ from manifest. * Falls back to applicationId of the app which may differ from manifest.
*/ */
public Object getBuildConfigValue(String fieldName) { public Object getBuildConfigValue(final String fieldName) {
String pkg = getPackageIdManifest() + ".BuildConfig"; String pkg = getPackageIdManifest() + ".BuildConfig";
try { try {
Class<?> c = Class.forName(pkg); Class<?> c = Class.forName(pkg);
@ -284,9 +305,9 @@ public class ContextUtils {
/** /**
* Get a BuildConfig bool value * Get a BuildConfig bool value
*/ */
public Boolean bcbool(String fieldName, Boolean defaultValue) { public Boolean bcbool(final String fieldName, final Boolean defaultValue) {
Object field = getBuildConfigValue(fieldName); Object field = getBuildConfigValue(fieldName);
if (field != null && field instanceof Boolean) { if (field instanceof Boolean) {
return (Boolean) field; return (Boolean) field;
} }
return defaultValue; return defaultValue;
@ -295,9 +316,9 @@ public class ContextUtils {
/** /**
* Get a BuildConfig string value * Get a BuildConfig string value
*/ */
public String bcstr(String fieldName, String defaultValue) { public String bcstr(final String fieldName, final String defaultValue) {
Object field = getBuildConfigValue(fieldName); Object field = getBuildConfigValue(fieldName);
if (field != null && field instanceof String) { if (field instanceof String) {
return (String) field; return (String) field;
} }
return defaultValue; return defaultValue;
@ -306,9 +327,9 @@ public class ContextUtils {
/** /**
* Get a BuildConfig string value * Get a BuildConfig string value
*/ */
public Integer bcint(String fieldName, int defaultValue) { public Integer bcint(final String fieldName, final int defaultValue) {
Object field = getBuildConfigValue(fieldName); Object field = getBuildConfigValue(fieldName);
if (field != null && field instanceof Integer) { if (field instanceof Integer) {
return (Integer) field; return (Integer) field;
} }
return defaultValue; return defaultValue;
@ -396,8 +417,8 @@ public class ContextUtils {
* Check if app with given {@code packageName} is installed * Check if app with given {@code packageName} is installed
*/ */
public boolean isAppInstalled(String packageName) { public boolean isAppInstalled(String packageName) {
PackageManager pm = _context.getApplicationContext().getPackageManager();
try { try {
PackageManager pm = _context.getApplicationContext().getPackageManager();
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES); pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
return true; return true;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
@ -409,17 +430,17 @@ public class ContextUtils {
* Restart the current app. Supply the class to start on startup * Restart the current app. Supply the class to start on startup
*/ */
public void restartApp(Class classToStart) { public void restartApp(Class classToStart) {
Intent inte = new Intent(_context, classToStart); Intent intent = new Intent(_context, classToStart);
PendingIntent inteP = PendingIntent.getActivity(_context, 555, inte, PendingIntent.FLAG_CANCEL_CURRENT); PendingIntent pendi = PendingIntent.getActivity(_context, 555, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) _context.getSystemService(Context.ALARM_SERVICE); AlarmManager mgr = (AlarmManager) _context.getSystemService(Context.ALARM_SERVICE);
if (_context instanceof Activity) { if (_context instanceof Activity) {
((Activity) _context).finish(); ((Activity) _context).finish();
} }
if (mgr != null) { if (mgr != null) {
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, inteP); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendi);
} else { } else {
inte.addFlags(FLAG_ACTIVITY_NEW_TASK); intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
_context.startActivity(inte); _context.startActivity(intent);
} }
Runtime.getRuntime().exit(0); Runtime.getRuntime().exit(0);
} }
@ -488,19 +509,25 @@ public class ContextUtils {
* {@code androidLC} may be in any of the forms: en, de, de-rAt * {@code androidLC} may be in any of the forms: en, de, de-rAt
* If given an empty string, the default (system) locale gets loaded * If given an empty string, the default (system) locale gets loaded
*/ */
public void setAppLanguage(String androidLC) { public void setAppLanguage(final String androidLC) {
Locale locale = getLocaleByAndroidCode(androidLC); Locale locale = getLocaleByAndroidCode(androidLC);
locale = (locale != null && !androidLC.isEmpty()) ? locale : Resources.getSystem().getConfiguration().locale;
setLocale(locale);
}
public ContextUtils setLocale(final Locale locale) {
Configuration config = _context.getResources().getConfiguration(); Configuration config = _context.getResources().getConfiguration();
config.locale = (locale != null && !androidLC.isEmpty()) config.locale = (locale != null ? locale : Resources.getSystem().getConfiguration().locale);
? locale : Resources.getSystem().getConfiguration().locale;
_context.getResources().updateConfiguration(config, null); _context.getResources().updateConfiguration(config, null);
Locale.setDefault(locale);
return this;
} }
/** /**
* Try to guess if the color on top of the given {@code colorOnBottomInt} * Try to guess if the color on top of the given {@code colorOnBottomInt}
* should be light or dark. Returns true if top color should be light * should be light or dark. Returns true if top color should be light
*/ */
public boolean shouldColorOnTopBeLight(@ColorInt int colorOnBottomInt) { public boolean shouldColorOnTopBeLight(@ColorInt final int colorOnBottomInt) {
return 186 > (((0.299 * Color.red(colorOnBottomInt)) return 186 > (((0.299 * Color.red(colorOnBottomInt))
+ ((0.587 * Color.green(colorOnBottomInt)) + ((0.587 * Color.green(colorOnBottomInt))
+ (0.114 * Color.blue(colorOnBottomInt))))); + (0.114 * Color.blue(colorOnBottomInt)))));
@ -509,7 +536,7 @@ public class ContextUtils {
/** /**
* Convert a html string to an android {@link Spanned} object * Convert a html string to an android {@link Spanned} object
*/ */
public Spanned htmlToSpanned(String html) { public Spanned htmlToSpanned(final String html) {
Spanned result; Spanned result;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
@ -568,7 +595,7 @@ public class ContextUtils {
return dirs; return dirs;
} }
public String getStorageName(File externalFileDir, boolean storageNameWithoutType) { public String getStorageName(final File externalFileDir, final boolean storageNameWithoutType) {
boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath()); boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
String[] split = externalFileDir.getAbsolutePath().split("/"); String[] split = externalFileDir.getAbsolutePath().split("/");
@ -579,7 +606,7 @@ public class ContextUtils {
} }
} }
public List<Pair<File, String>> getStorages(boolean internalStorageFolder, boolean sdcardFolders) { public List<Pair<File, String>> getStorages(final boolean internalStorageFolder, final boolean sdcardFolders) {
List<Pair<File, String>> storages = new ArrayList<>(); List<Pair<File, String>> storages = new ArrayList<>();
for (Pair<File, String> pair : getAppDataPublicDirs(internalStorageFolder, sdcardFolders, true)) { for (Pair<File, String> pair : getAppDataPublicDirs(internalStorageFolder, sdcardFolders, true)) {
if (pair.first != null && pair.first.getAbsolutePath().lastIndexOf("/Android/data") > 0) { if (pair.first != null && pair.first.getAbsolutePath().lastIndexOf("/Android/data") > 0) {
@ -592,7 +619,7 @@ public class ContextUtils {
return storages; return storages;
} }
public File getStorageRootFolder(File file) { public File getStorageRootFolder(final File file) {
String filepath; String filepath;
try { try {
filepath = file.getCanonicalPath(); filepath = file.getCanonicalPath();
@ -613,7 +640,7 @@ public class ContextUtils {
* *
* @param files Files and folders to scan * @param files Files and folders to scan
*/ */
public void mediaScannerScanFile(File... files) { public void mediaScannerScanFile(final File... files) {
if (android.os.Build.VERSION.SDK_INT > 19) { if (android.os.Build.VERSION.SDK_INT > 19) {
String[] paths = new String[files.length]; String[] paths = new String[files.length];
for (int i = 0; i < files.length; i++) { for (int i = 0; i < files.length; i++) {
@ -662,8 +689,12 @@ public class ContextUtils {
/** /**
* Get a {@link Bitmap} out of a {@link DrawableRes} * Get a {@link Bitmap} out of a {@link DrawableRes}
*/ */
public Bitmap drawableToBitmap(@DrawableRes int drawableId) { public Bitmap drawableToBitmap(@DrawableRes final int drawableId) {
return drawableToBitmap(ContextCompat.getDrawable(_context, drawableId)); try {
return drawableToBitmap(ContextCompat.getDrawable(_context, drawableId));
} catch (Exception e) {
return null;
}
} }
/** /**
@ -671,7 +702,7 @@ public class ContextUtils {
* Specifying a {@code maxDimen} is also possible and a value below 2000 * Specifying a {@code maxDimen} is also possible and a value below 2000
* is recommended, otherwise a {@link OutOfMemoryError} may occur * is recommended, otherwise a {@link OutOfMemoryError} may occur
*/ */
public Bitmap loadImageFromFilesystem(File imagePath, int maxDimen) { public Bitmap loadImageFromFilesystem(final File imagePath, final int maxDimen) {
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath.getAbsolutePath(), options); BitmapFactory.decodeFile(imagePath.getAbsolutePath(), options);
@ -687,7 +718,7 @@ public class ContextUtils {
* @param maxDimen Max size of the Bitmap (width or height) * @param maxDimen Max size of the Bitmap (width or height)
* @return the scaling factor that needs to be applied to the bitmap * @return the scaling factor that needs to be applied to the bitmap
*/ */
public int calculateInSampleSize(BitmapFactory.Options options, int maxDimen) { public int calculateInSampleSize(final BitmapFactory.Options options, final int maxDimen) {
// Raw height and width of image // Raw height and width of image
int height = options.outHeight; int height = options.outHeight;
int width = options.outWidth; int width = options.outWidth;
@ -703,7 +734,7 @@ public class ContextUtils {
* Scale the bitmap so both dimensions are lower or equal to {@code maxDimen} * Scale the bitmap so both dimensions are lower or equal to {@code maxDimen}
* This keeps the aspect ratio * This keeps the aspect ratio
*/ */
public Bitmap scaleBitmap(Bitmap bitmap, int maxDimen) { public Bitmap scaleBitmap(final Bitmap bitmap, final int maxDimen) {
int picSize = Math.min(bitmap.getHeight(), bitmap.getWidth()); int picSize = Math.min(bitmap.getHeight(), bitmap.getWidth());
float scale = 1.f * maxDimen / picSize; float scale = 1.f * maxDimen / picSize;
Matrix matrix = new Matrix(); Matrix matrix = new Matrix();
@ -714,7 +745,7 @@ public class ContextUtils {
/** /**
* Write the given {@link Bitmap} to {@code imageFile}, in {@link CompressFormat#JPEG} format * Write the given {@link Bitmap} to {@code imageFile}, in {@link CompressFormat#JPEG} format
*/ */
public boolean writeImageToFileJpeg(File imageFile, Bitmap image) { public boolean writeImageToFileJpeg(final File imageFile, final Bitmap image) {
return writeImageToFile(imageFile, image, Bitmap.CompressFormat.JPEG, 95); return writeImageToFile(imageFile, image, Bitmap.CompressFormat.JPEG, 95);
} }
@ -727,7 +758,7 @@ public class ContextUtils {
* @param quality Quality level, defaults to 95 * @param quality Quality level, defaults to 95
* @return True if writing was successful * @return True if writing was successful
*/ */
public boolean writeImageToFile(File targetFile, Bitmap image, CompressFormat format, Integer quality) { public boolean writeImageToFile(final File targetFile, final Bitmap image, CompressFormat format, Integer quality) {
File folder = new File(targetFile.getParent()); File folder = new File(targetFile.getParent());
if (quality == null || quality < 0 || quality > 100) { if (quality == null || quality < 0 || quality > 100) {
quality = 95; quality = 95;
@ -765,7 +796,7 @@ public class ContextUtils {
* Draw text in the center of the given {@link DrawableRes} * Draw text in the center of the given {@link DrawableRes}
* This may be useful for e.g. badge counts * This may be useful for e.g. badge counts
*/ */
public Bitmap drawTextOnDrawable(@DrawableRes int drawableRes, String text, int textSize) { public Bitmap drawTextOnDrawable(@DrawableRes final int drawableRes, final String text, final int textSize) {
Resources resources = _context.getResources(); Resources resources = _context.getResources();
float scale = resources.getDisplayMetrics().density; float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = drawableToBitmap(drawableRes); Bitmap bitmap = drawableToBitmap(drawableRes);
@ -790,7 +821,7 @@ public class ContextUtils {
* Try to tint all {@link Menu}s {@link MenuItem}s with given color * Try to tint all {@link Menu}s {@link MenuItem}s with given color
*/ */
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public void tintMenuItems(Menu menu, boolean recurse, @ColorInt int iconColor) { public void tintMenuItems(final Menu menu, final boolean recurse, @ColorInt final int iconColor) {
for (int i = 0; i < menu.size(); i++) { for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i); MenuItem item = menu.getItem(i);
try { try {
@ -807,14 +838,14 @@ public class ContextUtils {
/** /**
* Loads {@link Drawable} by given {@link DrawableRes} and applies a color * Loads {@link Drawable} by given {@link DrawableRes} and applies a color
*/ */
public Drawable tintDrawable(@DrawableRes int drawableRes, @ColorInt int color) { public Drawable tintDrawable(@DrawableRes final int drawableRes, @ColorInt final int color) {
return tintDrawable(rdrawable(drawableRes), color); return tintDrawable(rdrawable(drawableRes), color);
} }
/** /**
* Tint a {@link Drawable} with given {@code color} * Tint a {@link Drawable} with given {@code color}
*/ */
public Drawable tintDrawable(@Nullable Drawable drawable, @ColorInt int color) { public Drawable tintDrawable(@Nullable Drawable drawable, @ColorInt final int color) {
if (drawable != null) { if (drawable != null) {
drawable = DrawableCompat.wrap(drawable); drawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(drawable.mutate(), color); DrawableCompat.setTint(drawable.mutate(), color);
@ -826,7 +857,10 @@ public class ContextUtils {
* Try to make icons in Toolbar/ActionBars SubMenus visible * Try to make icons in Toolbar/ActionBars SubMenus visible
* This may not work on some devices and it maybe won't work on future android updates * This may not work on some devices and it maybe won't work on future android updates
*/ */
public void setSubMenuIconsVisiblity(Menu menu, boolean visible) { public void setSubMenuIconsVisiblity(final Menu menu, final boolean visible) {
if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL) {
return;
}
if (menu.getClass().getSimpleName().equals("MenuBuilder")) { if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
try { try {
@SuppressLint("PrivateApi") Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); @SuppressLint("PrivateApi") Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
@ -886,7 +920,7 @@ public class ContextUtils {
} }
public String getMimeType(File file) { public String getMimeType(final File file) {
return getMimeType(Uri.fromFile(file)); return getMimeType(Uri.fromFile(file));
} }
@ -895,7 +929,7 @@ public class ContextUtils {
* Android/Java's own MimeType map is very very small and detection barely works at all * Android/Java's own MimeType map is very very small and detection barely works at all
* Hence use custom map for some file extensions * Hence use custom map for some file extensions
*/ */
public String getMimeType(Uri uri) { public String getMimeType(final Uri uri) {
String mimeType = null; String mimeType = null;
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
ContentResolver cr = _context.getContentResolver(); ContentResolver cr = _context.getContentResolver();
@ -936,7 +970,7 @@ public class ContextUtils {
return mimeType; return mimeType;
} }
public Integer parseColor(String colorstr) { public Integer parseColor(final String colorstr) {
if (colorstr == null || colorstr.trim().isEmpty()) { if (colorstr == null || colorstr.trim().isEmpty()) {
return null; return null;
} }
@ -957,6 +991,22 @@ public class ContextUtils {
return true; return true;
} }
} }
// Vibrate device one time by given amount of time, defaulting to 50ms
// Requires <uses-permission android:name="android.permission.VIBRATE" /> in AndroidManifest to work
@SuppressWarnings("UnnecessaryReturnStatement")
@SuppressLint("MissingPermission")
public void vibrate(final int... ms) {
int ms_v = ms != null && ms.length > 0 ? ms[0] : 50;
Vibrator vibrator = ((Vibrator) _context.getSystemService(VIBRATOR_SERVICE));
if (vibrator == null) {
return;
} else if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(ms_v, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(ms_v);
}
}
} }

View file

@ -29,6 +29,7 @@ import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URLConnection; import java.net.URLConnection;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -240,6 +241,30 @@ public class FileUtils {
} }
} }
public static boolean copyFile(final File src, final FileOutputStream os) {
InputStream is = null;
try {
try {
is = new FileInputStream(src);
byte[] buf = new byte[BUFFER_SIZE];
int len;
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
return true;
} finally {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
}
} catch (IOException ex) {
return false;
}
}
// Returns -1 if the file did not contain any of the needles, otherwise, // Returns -1 if the file did not contain any of the needles, otherwise,
// the index of which needle was found in the contents of the file. // the index of which needle was found in the contents of the file.
// //
@ -452,7 +477,15 @@ public class FileUtils {
} }
String[] units = abbreviation ? new String[]{"B", "kB", "MB", "GB", "TB"} : new String[]{"Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"}; String[] units = abbreviation ? new String[]{"B", "kB", "MB", "GB", "TB"} : new String[]{"Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"};
int unit = (int) (Math.log10(size) / Math.log10(1024)); int unit = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, unit)) return new DecimalFormat("#,##0.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH)).format(size / Math.pow(1024, unit)) + " " + units[unit];
+ " " + units[unit]; }
public static int[] getTimeDiffHMS(long now, long past) {
int[] ret = new int[3];
long diff = Math.abs(now - past);
ret[0] = (int) (diff / (1000 * 60 * 60)); // hours
ret[1] = (int) (diff / (1000 * 60)) % 60; // min
ret[2] = (int) (diff / 1000) % 60; // sec
return ret;
} }
} }

View file

@ -77,7 +77,7 @@ public class NetworkUtils {
int written = 0; int written = 0;
final float invLength = 1f / connection.getContentLength(); final float invLength = 1f / connection.getContentLength();
byte data[] = new byte[BUFFER_SIZE]; byte[] data = new byte[BUFFER_SIZE];
while ((count = input.read(data)) != -1) { while ((count = input.read(data)) != -1) {
output.write(data, 0, count); output.write(data, 0, count);
if (invLength != -1f && progressCallback != null) { if (invLength != -1f && progressCallback != null) {

View file

@ -96,12 +96,12 @@ public class ShareUtil {
protected String _fileProviderAuthority; protected String _fileProviderAuthority;
protected String _chooserTitle; protected String _chooserTitle;
public ShareUtil(Context context) { public ShareUtil(final Context context) {
_context = context; _context = context;
_chooserTitle = ""; _chooserTitle = "";
} }
public void setContext(Context c) { public void setContext(final Context c) {
_context = c; _context = c;
} }
@ -116,13 +116,13 @@ public class ShareUtil {
return _fileProviderAuthority; return _fileProviderAuthority;
} }
public ShareUtil setFileProviderAuthority(String fileProviderAuthority) { public ShareUtil setFileProviderAuthority(final String fileProviderAuthority) {
_fileProviderAuthority = fileProviderAuthority; _fileProviderAuthority = fileProviderAuthority;
return this; return this;
} }
public ShareUtil setChooserTitle(String title) { public ShareUtil setChooserTitle(final String title) {
_chooserTitle = title; _chooserTitle = title;
return this; return this;
} }
@ -133,7 +133,7 @@ public class ShareUtil {
* @param file the file * @param file the file
* @return Uri for this file * @return Uri for this file
*/ */
public Uri getUriByFileProviderAuthority(File file) { public Uri getUriByFileProviderAuthority(final File file) {
return FileProvider.getUriForFile(_context, getFileProviderAuthority(), file); return FileProvider.getUriForFile(_context, getFileProviderAuthority(), file);
} }
@ -143,7 +143,7 @@ public class ShareUtil {
* @param intent Thing to be shared * @param intent Thing to be shared
* @param chooserText The title text for the chooser, or null for default * @param chooserText The title text for the chooser, or null for default
*/ */
public void showChooser(Intent intent, String chooserText) { public void showChooser(final Intent intent, final String chooserText) {
_context.startActivity(Intent.createChooser(intent, _context.startActivity(Intent.createChooser(intent,
chooserText != null ? chooserText : _chooserTitle)); chooserText != null ? chooserText : _chooserTitle));
} }
@ -157,7 +157,7 @@ public class ShareUtil {
* @param iconRes Icon resource for the item * @param iconRes Icon resource for the item
* @param title Title of the item * @param title Title of the item
*/ */
public void createLauncherDesktopShortcut(Intent intent, @DrawableRes int iconRes, String title) { public void createLauncherDesktopShortcut(final Intent intent, @DrawableRes final int iconRes, final String title) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (intent.getAction() == null) { if (intent.getAction() == null) {
@ -182,7 +182,7 @@ public class ShareUtil {
* @param iconRes Icon resource for the item * @param iconRes Icon resource for the item
* @param title Title of the item * @param title Title of the item
*/ */
public void createLauncherDesktopShortcutLegacy(Intent intent, @DrawableRes int iconRes, String title) { public void createLauncherDesktopShortcutLegacy(final Intent intent, @DrawableRes final int iconRes, final String title) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (intent.getAction() == null) { if (intent.getAction() == null) {
@ -203,7 +203,7 @@ public class ShareUtil {
* @param text The text to share * @param text The text to share
* @param mimeType MimeType or null (uses text/plain) * @param mimeType MimeType or null (uses text/plain)
*/ */
public void shareText(String text, @Nullable String mimeType) { public void shareText(final String text, @Nullable final String mimeType) {
Intent intent = new Intent(Intent.ACTION_SEND); Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, text); intent.putExtra(Intent.EXTRA_TEXT, text);
intent.setType(mimeType != null ? mimeType : MIME_TEXT_PLAIN); intent.setType(mimeType != null ? mimeType : MIME_TEXT_PLAIN);
@ -216,7 +216,7 @@ public class ShareUtil {
* @param file The file to share * @param file The file to share
* @param mimeType The files mime type * @param mimeType The files mime type
*/ */
public boolean shareStream(File file, String mimeType) { public boolean shareStream(final File file, final String mimeType) {
Intent intent = new Intent(Intent.ACTION_SEND); Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath()); intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath());
intent.setType(mimeType); intent.setType(mimeType);
@ -237,7 +237,7 @@ public class ShareUtil {
* @param files The files to share * @param files The files to share
* @param mimeType The files mime type. Usally * / * is the best option * @param mimeType The files mime type. Usally * / * is the best option
*/ */
public boolean shareStreamMultiple(Collection<File> files, String mimeType) { public boolean shareStreamMultiple(final Collection<File> files, final String mimeType) {
ArrayList<Uri> uris = new ArrayList<>(); ArrayList<Uri> uris = new ArrayList<>();
for (File file : files) { for (File file : files) {
File uri = new File(file.toString()); File uri = new File(file.toString());
@ -258,14 +258,13 @@ public class ShareUtil {
/** /**
* Start calendar application to add new event, with given details prefilled * Start calendar application to add new event, with given details prefilled
*/ */
public boolean createCalendarAppointment(@Nullable String title, @Nullable String description, @Nullable String location, @Nullable Long... startAndEndTime) { public boolean createCalendarAppointment(@Nullable final String title, @Nullable final String description, @Nullable final String location, @Nullable final Long... startAndEndTime) {
Intent intent = new Intent(Intent.ACTION_INSERT).setData(CalendarContract.Events.CONTENT_URI); Intent intent = new Intent(Intent.ACTION_INSERT).setData(CalendarContract.Events.CONTENT_URI);
if (title != null) { if (title != null) {
intent.putExtra(CalendarContract.Events.TITLE, title); intent.putExtra(CalendarContract.Events.TITLE, title);
} }
if (description != null) { if (description != null) {
description = description.length() > 800 ? description.substring(0, 800) : description; intent.putExtra(CalendarContract.Events.DESCRIPTION, (description.length() > 800 ? description.substring(0, 800) : description));
intent.putExtra(CalendarContract.Events.DESCRIPTION, description);
} }
if (location != null) { if (location != null) {
intent.putExtra(CalendarContract.Events.EVENT_LOCATION, location); intent.putExtra(CalendarContract.Events.EVENT_LOCATION, location);
@ -292,7 +291,7 @@ public class ShareUtil {
* *
* @param file The file to share * @param file The file to share
*/ */
public boolean viewFileInOtherApp(File file, @Nullable String type) { public boolean viewFileInOtherApp(final File file, @Nullable final String type) {
// On some specific devices the first won't work // On some specific devices the first won't work
Uri fileUri = null; Uri fileUri = null;
try { try {
@ -324,7 +323,7 @@ public class ShareUtil {
* @param format A {@link Bitmap.CompressFormat}, supporting JPEG,PNG,WEBP * @param format A {@link Bitmap.CompressFormat}, supporting JPEG,PNG,WEBP
* @return if success, true * @return if success, true
*/ */
public boolean shareImage(Bitmap bitmap, Bitmap.CompressFormat format) { public boolean shareImage(final Bitmap bitmap, final Bitmap.CompressFormat format) {
return shareImage(bitmap, format, 95, "SharedImage"); return shareImage(bitmap, format, 95, "SharedImage");
} }
@ -337,7 +336,7 @@ public class ShareUtil {
* @param quality Quality of the exported image [0-100] * @param quality Quality of the exported image [0-100]
* @return if success, true * @return if success, true
*/ */
public boolean shareImage(Bitmap bitmap, Bitmap.CompressFormat format, int quality, String imageName) { public boolean shareImage(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String imageName) {
try { try {
String ext = format.name().toLowerCase(); String ext = format.name().toLowerCase();
File file = File.createTempFile(imageName, "." + ext.replace("jpeg", "jpg"), _context.getExternalCacheDir()); File file = File.createTempFile(imageName, "." + ext.replace("jpeg", "jpg"), _context.getExternalCacheDir());
@ -359,19 +358,23 @@ public class ShareUtil {
* @return {{@link PrintJob}} or null * @return {{@link PrintJob}} or null
*/ */
@RequiresApi(api = Build.VERSION_CODES.KITKAT) @RequiresApi(api = Build.VERSION_CODES.KITKAT)
@SuppressWarnings("deprecation") public PrintJob print(final WebView webview, final String jobName, final boolean... landscape) {
public PrintJob print(WebView webview, String jobName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
PrintDocumentAdapter printAdapter; final PrintDocumentAdapter printAdapter;
PrintManager printManager = (PrintManager) _context.getSystemService(Context.PRINT_SERVICE); final PrintManager printManager = (PrintManager) _context.getSystemService(Context.PRINT_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
printAdapter = webview.createPrintDocumentAdapter(jobName); printAdapter = webview.createPrintDocumentAdapter(jobName);
} else { } else {
printAdapter = webview.createPrintDocumentAdapter(); printAdapter = webview.createPrintDocumentAdapter();
} }
final PrintAttributes.Builder attrib = new PrintAttributes.Builder();
if (landscape != null && landscape.length > 0 && landscape[0]) {
attrib.setMediaSize(new PrintAttributes.MediaSize("ISO_A4", "android", 11690, 8270));
attrib.setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0));
}
if (printManager != null) { if (printManager != null) {
try { try {
return printManager.print(jobName, printAdapter, new PrintAttributes.Builder().build()); return printManager.print(jobName, printAdapter, attrib.build());
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
@ -386,8 +389,7 @@ public class ShareUtil {
* See {@link #print(WebView, String) print method} * See {@link #print(WebView, String) print method}
*/ */
@RequiresApi(api = Build.VERSION_CODES.KITKAT) @RequiresApi(api = Build.VERSION_CODES.KITKAT)
@SuppressWarnings("deprecation") public PrintJob createPdf(final WebView webview, final String jobName) {
public PrintJob createPdf(WebView webview, String jobName) {
return print(webview, jobName); return print(webview, jobName);
} }
@ -399,7 +401,7 @@ public class ShareUtil {
* @return A {@link Bitmap} or null * @return A {@link Bitmap} or null
*/ */
@Nullable @Nullable
public static Bitmap getBitmapFromWebView(WebView webView) { public static Bitmap getBitmapFromWebView(final WebView webView) {
try { try {
//Measure WebView's content //Measure WebView's content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
@ -432,7 +434,7 @@ public class ShareUtil {
* Replace (primary) clipboard contents with given {@code text} * Replace (primary) clipboard contents with given {@code text}
* @param text Text to be set * @param text Text to be set
*/ */
public boolean setClipboard(CharSequence text) { public boolean setClipboard(final CharSequence text) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager cm = ((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)); android.text.ClipboardManager cm = ((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE));
if (cm != null) { if (cm != null) {
@ -485,7 +487,7 @@ public class ShareUtil {
* @param callback Callback after paste try * @param callback Callback after paste try
* @param serverOrNothing Supply one or no hastebin server. If empty, the default gets taken * @param serverOrNothing Supply one or no hastebin server. If empty, the default gets taken
*/ */
public void pasteOnHastebin(final String text, final Callback.a2<Boolean, String> callback, String... serverOrNothing) { public void pasteOnHastebin(final String text, final Callback.a2<Boolean, String> callback, final String... serverOrNothing) {
final Handler handler = new Handler(); final Handler handler = new Handler();
final String server = (serverOrNothing != null && serverOrNothing.length > 0 && serverOrNothing[0] != null) final String server = (serverOrNothing != null && serverOrNothing.length > 0 && serverOrNothing[0] != null)
? serverOrNothing[0] : "https://hastebin.com"; ? serverOrNothing[0] : "https://hastebin.com";
@ -507,7 +509,7 @@ public class ShareUtil {
* @param body Body (content) text to be prefilled in the mail * @param body Body (content) text to be prefilled in the mail
* @param to recipients to be prefilled in the mail * @param to recipients to be prefilled in the mail
*/ */
public void draftEmail(String subject, String body, String... to) { public void draftEmail(final String subject, final String body, final String... to) {
Intent intent = new Intent(Intent.ACTION_SENDTO); Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:")); intent.setData(Uri.parse("mailto:"));
if (subject != null) { if (subject != null) {
@ -528,7 +530,7 @@ public class ShareUtil {
* @param receivingIntent The intent from {@link Activity#getIntent()} * @param receivingIntent The intent from {@link Activity#getIntent()}
* @return A file or null if extraction did not succeed * @return A file or null if extraction did not succeed
*/ */
public File extractFileFromIntent(Intent receivingIntent) { public File extractFileFromIntent(final Intent receivingIntent) {
String action = receivingIntent.getAction(); String action = receivingIntent.getAction();
String type = receivingIntent.getType(); String type = receivingIntent.getType();
File tmpf; File tmpf;
@ -572,6 +574,14 @@ public class ShareUtil {
} }
} }
// media/ prefix for External storage
if (fileStr.startsWith((tmps = "media/"))) {
File f = new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + fileStr.substring(tmps.length())));
if (f.exists()) {
return f;
}
}
// Next/OwnCloud Fileprovider // Next/OwnCloud Fileprovider
for (String fp : new String[]{"org.nextcloud.files", "org.nextcloud.beta.files", "org.owncloud.files"}) { for (String fp : new String[]{"org.nextcloud.files", "org.nextcloud.beta.files", "org.owncloud.files"}) {
if (fileProvider.equals(fp) && fileStr.startsWith(tmps = "external_files/")) { if (fileProvider.equals(fp) && fileStr.startsWith(tmps = "external_files/")) {
@ -587,6 +597,16 @@ public class ShareUtil {
return new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + fileStr.substring(tmps.length()))); return new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + fileStr.substring(tmps.length())));
} }
if (fileStr.startsWith(tmps = "external_files/")) {
for (String prefix : new String[]{Environment.getExternalStorageDirectory().getAbsolutePath(), "/storage", ""}) {
File f = new File(Uri.decode(prefix + "/" + fileStr.substring(tmps.length())));
if (f.exists()) {
return f;
}
}
}
// URI Encoded paths with full path after content://package/ // URI Encoded paths with full path after content://package/
if (fileStr.startsWith("/") || fileStr.startsWith("%2F")) { if (fileStr.startsWith("/") || fileStr.startsWith("%2F")) {
tmpf = new File(Uri.decode(fileStr)); tmpf = new File(Uri.decode(fileStr));
@ -624,6 +644,11 @@ public class ShareUtil {
} }
} }
public String extractFileFromIntentStr(final Intent receivingIntent) {
File f = extractFileFromIntent(receivingIntent);
return f != null ? f.getAbsolutePath() : null;
}
/** /**
* Request a picture from camera-like apps * Request a picture from camera-like apps
* Result ({@link String}) will be available from {@link Activity#onActivityResult(int, int, Intent)}. * Result ({@link String}) will be available from {@link Activity#onActivityResult(int, int, Intent)}.
@ -634,7 +659,8 @@ public class ShareUtil {
* *
* @param target Path to file to write to, if folder the filename gets app_name + millis + random filename. If null DCIM folder is used. * @param target Path to file to write to, if folder the filename gets app_name + millis + random filename. If null DCIM folder is used.
*/ */
public String requestCameraPicture(File target) { @SuppressWarnings("RegExpRedundantEscape")
public String requestCameraPicture(final File target) {
if (!(_context instanceof Activity)) { if (!(_context instanceof Activity)) {
throw new RuntimeException("Error: ShareUtil.requestCameraPicture needs an Activity Context."); throw new RuntimeException("Error: ShareUtil.requestCameraPicture needs an Activity Context.");
} }
@ -647,7 +673,7 @@ public class ShareUtil {
if (target != null && !target.isDirectory()) { if (target != null && !target.isDirectory()) {
photoFile = target; photoFile = target;
} else { } else {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", Locale.getDefault()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", Locale.ENGLISH);
File storageDir = target != null ? target : new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); File storageDir = target != null ? target : new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera");
String imageFileName = ((new ContextUtils(_context).rstr("app_name")).replaceAll("[^a-zA-Z0-9\\.\\-]", "_") + "_").replace("__", "_") + sdf.format(new Date()); String imageFileName = ((new ContextUtils(_context).rstr("app_name")).replaceAll("[^a-zA-Z0-9\\.\\-]", "_") + "_").replace("__", "_") + sdf.format(new Date());
photoFile = new File(storageDir, imageFileName + ".jpg"); photoFile = new File(storageDir, imageFileName + ".jpg");
@ -686,7 +712,7 @@ public class ShareUtil {
* Also may forward results via local broadcast * Also may forward results via local broadcast
*/ */
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
public Object extractResultFromActivityResult(int requestCode, int resultCode, Intent data, Activity... activityOrNull) { public Object extractResultFromActivityResult(final int requestCode, final int resultCode, final Intent data, final Activity... activityOrNull) {
Activity activity = greedyGetActivity(activityOrNull); Activity activity = greedyGetActivity(activityOrNull);
switch (requestCode) { switch (requestCode) {
case REQUEST_CAMERA_PICTURE: { case REQUEST_CAMERA_PICTURE: {
@ -717,6 +743,10 @@ public class ShareUtil {
cursor.close(); cursor.close();
} }
// Try to grab via file extraction method
data.setAction(Intent.ACTION_VIEW);
picturePath = picturePath != null ? picturePath : extractFileFromIntentStr(data);
// Retrieve image from file descriptor / Cloud, e.g.: Google Drive, Picasa // Retrieve image from file descriptor / Cloud, e.g.: Google Drive, Picasa
if (picturePath == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (picturePath == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try { try {
@ -762,7 +792,7 @@ public class ShareUtil {
* Send a local broadcast (to receive within app), with given action and string-extra+value. * Send a local broadcast (to receive within app), with given action and string-extra+value.
* This is a convenience method for quickly sending just one thing. * This is a convenience method for quickly sending just one thing.
*/ */
public void sendLocalBroadcastWithStringExtra(String action, String extra, CharSequence value) { public void sendLocalBroadcastWithStringExtra(final String action, final String extra, final CharSequence value) {
Intent intent = new Intent(action); Intent intent = new Intent(action);
intent.putExtra(extra, value); intent.putExtra(extra, value);
LocalBroadcastManager.getInstance(_context).sendBroadcast(intent); LocalBroadcastManager.getInstance(_context).sendBroadcast(intent);
@ -776,7 +806,7 @@ public class ShareUtil {
* @param filterActions All {@link IntentFilter} actions to filter for * @param filterActions All {@link IntentFilter} actions to filter for
* @return The created instance. Has to be unregistered on {@link Activity} lifecycle events. * @return The created instance. Has to be unregistered on {@link Activity} lifecycle events.
*/ */
public BroadcastReceiver receiveResultFromLocalBroadcast(Callback.a2<Intent, BroadcastReceiver> callback, boolean autoUnregister, String... filterActions) { public BroadcastReceiver receiveResultFromLocalBroadcast(final Callback.a2<Intent, BroadcastReceiver> callback, final boolean autoUnregister, final String... filterActions) {
IntentFilter intentFilter = new IntentFilter(); IntentFilter intentFilter = new IntentFilter();
for (String filterAction : filterActions) { for (String filterAction : filterActions) {
intentFilter.addAction(filterAction); intentFilter.addAction(filterAction);
@ -804,7 +834,7 @@ public class ShareUtil {
* *
* @param file File that should be edited * @param file File that should be edited
*/ */
public void requestPictureEdit(File file) { public void requestPictureEdit(final File file) {
Uri uri = getUriByFileProviderAuthority(file); Uri uri = getUriByFileProviderAuthority(file);
int flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION; int flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION;
@ -826,9 +856,10 @@ public class ShareUtil {
* *
* @param file Target file * @param file Target file
* @param mode 1 for picture, 2 for video, anything else for other * @param mode 1 for picture, 2 for video, anything else for other
* @return * @return Media URI
*/ */
public Uri getMediaUri(File file, int mode) { @SuppressWarnings("TryFinallyCanBeTryWithResources")
public Uri getMediaUri(final File file, final int mode) {
Uri uri = MediaStore.Files.getContentUri("external"); Uri uri = MediaStore.Files.getContentUri("external");
uri = (mode != 0) ? (mode == 1 ? MediaStore.Images.Media.EXTERNAL_CONTENT_URI : MediaStore.Video.Media.EXTERNAL_CONTENT_URI) : uri; uri = (mode != 0) ? (mode == 1 ? MediaStore.Images.Media.EXTERNAL_CONTENT_URI : MediaStore.Video.Media.EXTERNAL_CONTENT_URI) : uri;
@ -854,7 +885,7 @@ public class ShareUtil {
* which implement the Chrome Custom Tab interface. This method changes * which implement the Chrome Custom Tab interface. This method changes
* the customtab intent to use an available compatible browser, if available. * the customtab intent to use an available compatible browser, if available.
*/ */
public void enableChromeCustomTabsForOtherBrowsers(Intent customTabIntent) { public void enableChromeCustomTabsForOtherBrowsers(final Intent customTabIntent) {
String[] checkpkgs = new String[]{ String[] checkpkgs = new String[]{
"com.android.chrome", "com.chrome.beta", "com.chrome.dev", "com.google.android.apps.chrome", "org.chromium.chrome", "com.android.chrome", "com.chrome.beta", "com.chrome.dev", "com.google.android.apps.chrome", "org.chromium.chrome",
"org.mozilla.fennec_fdroid", "org.mozilla.firefox", "org.mozilla.firefox_beta", "org.mozilla.fennec_aurora", "org.mozilla.fennec_fdroid", "org.mozilla.firefox", "org.mozilla.firefox_beta", "org.mozilla.fennec_aurora",
@ -905,7 +936,7 @@ public class ShareUtil {
* Request storage access. The user needs to press "Select storage" at the correct storage. * Request storage access. The user needs to press "Select storage" at the correct storage.
* @param activity The activity which will receive the result from startActivityForResult * @param activity The activity which will receive the result from startActivityForResult
*/ */
public void requestStorageAccessFramework(Activity... activity) { public void requestStorageAccessFramework(final Activity... activity) {
Activity a = greedyGetActivity(activity); Activity a = greedyGetActivity(activity);
if (a != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { if (a != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
@ -961,8 +992,12 @@ public class ShareUtil {
* @param file The file object (file/folder) * @param file The file object (file/folder)
* @return Wether or not the file is under storage access folder * @return Wether or not the file is under storage access folder
*/ */
public boolean isUnderStorageAccessFolder(File file) { public boolean isUnderStorageAccessFolder(final File file) {
if (file != null) { if (file != null) {
// When file writeable as is, it's the fastest way to learn SAF isn't required
if (file.canWrite()) {
return false;
}
ContextUtils cu = new ContextUtils(_context); ContextUtils cu = new ContextUtils(_context);
for (Pair<File, String> storage : cu.getStorages(false, true)) { for (Pair<File, String> storage : cu.getStorages(false, true)) {
if (file.getAbsolutePath().startsWith(storage.first.getAbsolutePath())) { if (file.getAbsolutePath().startsWith(storage.first.getAbsolutePath())) {
@ -978,7 +1013,7 @@ public class ShareUtil {
/** /**
* Greedy extract Activity from parameter or convert context if it's a activity * Greedy extract Activity from parameter or convert context if it's a activity
*/ */
private Activity greedyGetActivity(Activity... activity) { private Activity greedyGetActivity(final Activity... activity) {
if (activity != null && activity.length != 0 && activity[0] != null) { if (activity != null && activity.length != 0 && activity[0] != null) {
return activity[0]; return activity[0];
} }
@ -996,10 +1031,11 @@ public class ShareUtil {
* @param isDir Wether or not the given file parameter is a directory * @param isDir Wether or not the given file parameter is a directory
* @return Wether or not the file can be written * @return Wether or not the file can be written
*/ */
public boolean canWriteFile(File file, boolean isDir) { public boolean canWriteFile(final File file, final boolean isDir) {
if (file == null) { if (file == null) {
return false; return false;
} else if (file.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { } else if (file.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())
|| file.getAbsolutePath().startsWith(_context.getFilesDir().getAbsolutePath())) {
boolean s1 = isDir && file.getParentFile().canWrite(); boolean s1 = isDir && file.getParentFile().canWrite();
return !isDir && file.getParentFile() != null ? file.getParentFile().canWrite() : file.canWrite(); return !isDir && file.getParentFile() != null ? file.getParentFile().canWrite() : file.canWrite();
} else { } else {
@ -1017,7 +1053,8 @@ public class ShareUtil {
* @param isDir Wether or not file is a directory. For non-existing (to be created) files this info is not known hence required. * @param isDir Wether or not file is a directory. For non-existing (to be created) files this info is not known hence required.
* @return A {@link DocumentFile} object or null if file cannot be converted * @return A {@link DocumentFile} object or null if file cannot be converted
*/ */
public DocumentFile getDocumentFile(File file, boolean isDir) { @SuppressWarnings("RegExpRedundantEscape")
public DocumentFile getDocumentFile(final File file, final boolean isDir) {
// On older versions use fromFile // On older versions use fromFile
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return DocumentFile.fromFile(file); return DocumentFile.fromFile(file);
@ -1066,7 +1103,7 @@ public class ShareUtil {
return dof; return dof;
} }
public void showMountSdDialog(@StringRes int title, @StringRes int description, @DrawableRes int mountDescriptionGraphic, Activity... activityOrNull) { public void showMountSdDialog(@StringRes final int title, @StringRes final int description, @DrawableRes final int mountDescriptionGraphic, final Activity... activityOrNull) {
Activity activity = greedyGetActivity(activityOrNull); Activity activity = greedyGetActivity(activityOrNull);
if (activity == null) { if (activity == null) {
return; return;
@ -1087,11 +1124,12 @@ public class ShareUtil {
dialogi.show(); dialogi.show();
} }
public void writeFile(File file, boolean isDirectory, Callback.a2<Boolean, FileOutputStream> writeFileCallback) { @SuppressWarnings({"ResultOfMethodCallIgnored", "StatementWithEmptyBody"})
public void writeFile(final File file, final boolean isDirectory, final Callback.a2<Boolean, FileOutputStream> writeFileCallback) {
try { try {
FileOutputStream fileOutputStream = null; FileOutputStream fileOutputStream = null;
ParcelFileDescriptor pfd = null; ParcelFileDescriptor pfd = null;
if (file.canWrite()) { if (file.canWrite() || (!file.exists() && file.getParentFile().canWrite())) {
if (isDirectory) { if (isDirectory) {
file.mkdirs(); file.mkdirs();
} else { } else {
@ -1112,7 +1150,10 @@ public class ShareUtil {
writeFileCallback.callback(fileOutputStream != null || (isDirectory && file.exists()), fileOutputStream); writeFileCallback.callback(fileOutputStream != null || (isDirectory && file.exists()), fileOutputStream);
} }
if (fileOutputStream != null) { if (fileOutputStream != null) {
fileOutputStream.close(); try {
fileOutputStream.close();
} catch (Exception ignored) {
}
} }
if (pfd != null) { if (pfd != null) {
pfd.close(); pfd.close();
@ -1132,7 +1173,7 @@ public class ShareUtil {
* @param directCall Direct call number if possible * @param directCall Direct call number if possible
*/ */
@SuppressWarnings("SimplifiableConditionalExpression") @SuppressWarnings("SimplifiableConditionalExpression")
public void callTelephoneNumber(String telNo, boolean... directCall) { public void callTelephoneNumber(final String telNo, final boolean... directCall) {
Activity activity = greedyGetActivity(); Activity activity = greedyGetActivity();
if (activity == null) { if (activity == null) {
throw new RuntimeException("Error: ShareUtil::callTelephoneNumber needs to be contstructed with activity context"); throw new RuntimeException("Error: ShareUtil::callTelephoneNumber needs to be contstructed with activity context");

View file

@ -13,8 +13,8 @@ import java.text.SimpleDateFormat
buildscript { buildscript {
ext { ext {
version_gradle_tools = "3.5.1" version_gradle_tools = "3.5.2"
version_plugin_kotlin = "1.3.50" version_plugin_kotlin = "1.3.60"
enable_plugin_kotlin = false enable_plugin_kotlin = false
version_compileSdk = 28 version_compileSdk = 28