mirror of
https://github.com/gsantner/dandelion
synced 2024-11-22 04:12:08 +01:00
Update opoc
This commit is contained in:
parent
3ec8ab89c6
commit
a618da97d8
12 changed files with 701 additions and 39 deletions
|
@ -108,11 +108,13 @@ dependencies {
|
|||
implementation "com.android.support:support-v4:${version_library_appcompat}"
|
||||
implementation "com.android.support:customtabs:${version_library_appcompat}"
|
||||
implementation "com.android.support:cardview-v7:${version_library_appcompat}"
|
||||
implementation "com.android.support:preference-v7:${version_library_appcompat}"
|
||||
|
||||
// UI libraries
|
||||
implementation "com.github.DASAR:ShiftColorPicker:v0.5"
|
||||
|
||||
// Tool libraries
|
||||
implementation 'commons-io:commons-io:2.6'
|
||||
implementation "info.guardianproject.netcipher:netcipher:${version_library_netcipher}"
|
||||
implementation "info.guardianproject.netcipher:netcipher-webkit:${version_library_netcipher}"
|
||||
implementation "com.jakewharton:butterknife:${version_library_butterknife}"
|
||||
|
|
|
@ -16,7 +16,10 @@ import android.support.annotation.LayoutRes;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
|
@ -33,6 +36,7 @@ public abstract class GsFragmentBase extends Fragment {
|
|||
|
||||
protected ContextUtils _cu;
|
||||
protected Bundle _savedInstanceState = null;
|
||||
protected Menu _fragmentMenu;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -51,6 +55,9 @@ public abstract class GsFragmentBase extends Fragment {
|
|||
_cu = new ContextUtils(inflater.getContext());
|
||||
_cu.setAppLanguage(getAppLanguage());
|
||||
_savedInstanceState = savedInstanceState;
|
||||
if (getLayoutResId() == 0) {
|
||||
Log.e(getClass().getCanonicalName(), "Error: GsFragmentbase.onCreateview: Returned 0 for getLayoutResId");
|
||||
}
|
||||
View view = inflater.inflate(getLayoutResId(), container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
|
@ -126,4 +133,14 @@ public abstract class GsFragmentBase extends Fragment {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
_fragmentMenu = menu;
|
||||
}
|
||||
|
||||
public Menu getFragmentMenu() {
|
||||
return _fragmentMenu;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
#########################################################*/
|
||||
package net.gsantner.opoc.preference;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection", "unused", "SameParameterValue"})
|
||||
|
|
|
@ -532,7 +532,7 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
* A method to determine if current hour is between begin and end.
|
||||
* This is especially useful for time-based light/dark mode
|
||||
*/
|
||||
public boolean isCurrentHourOfDayBetween(int begin, int end) {
|
||||
public static boolean isCurrentHourOfDayBetween(int begin, int end) {
|
||||
begin = (begin >= 23 || begin < 0) ? 0 : begin;
|
||||
end = (end >= 23 || end < 0) ? 0 : end;
|
||||
int h = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
|
||||
|
|
|
@ -12,10 +12,12 @@ package net.gsantner.opoc.ui;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.AppCompatEditText;
|
||||
import android.text.Editable;
|
||||
|
@ -24,6 +26,7 @@ import android.text.TextWatcher;
|
|||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Filter;
|
||||
|
@ -31,12 +34,20 @@ import android.widget.LinearLayout;
|
|||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.gsantner.opoc.util.ActivityUtils;
|
||||
import net.gsantner.opoc.util.Callback;
|
||||
import net.gsantner.opoc.util.ContextUtils;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.IOFileFilter;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SearchOrCustomTextDialog {
|
||||
|
@ -115,6 +126,7 @@ public class SearchOrCustomTextDialog {
|
|||
}
|
||||
};
|
||||
|
||||
final ActivityUtils activityUtils = new ActivityUtils(activity);
|
||||
final AppCompatEditText searchEditText = new AppCompatEditText(activity);
|
||||
searchEditText.setSingleLine(true);
|
||||
searchEditText.setMaxLines(1);
|
||||
|
@ -140,6 +152,7 @@ public class SearchOrCustomTextDialog {
|
|||
final ListView listView = new ListView(activity);
|
||||
final LinearLayout linearLayout = new LinearLayout(activity);
|
||||
listView.setAdapter(listAdapter);
|
||||
listView.setVisibility(dopt.data != null && !dopt.data.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
if (dopt.isSearchEnabled) {
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
|
@ -157,7 +170,7 @@ public class SearchOrCustomTextDialog {
|
|||
dialogBuilder.setView(linearLayout)
|
||||
.setTitle(dopt.titleText)
|
||||
.setOnCancelListener(null)
|
||||
.setNegativeButton(dopt.cancelButtonText, null);
|
||||
.setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss());
|
||||
if (dopt.isSearchEnabled) {
|
||||
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
||||
dialogInterface.dismiss();
|
||||
|
@ -186,9 +199,124 @@ public class SearchOrCustomTextDialog {
|
|||
return false;
|
||||
});
|
||||
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
|
||||
Window w;
|
||||
if ((w = dialog.getWindow()) != null && dopt.isSearchEnabled) {
|
||||
w.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
}
|
||||
dialog.show();
|
||||
if ((w = dialog.getWindow()) != null) {
|
||||
w.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
if (dopt.isSearchEnabled) {
|
||||
searchEditText.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static SearchFilesTask recursiveFileSearch(Activity activity, File searchDir, String query, Callback.a1<List<String>> callback) {
|
||||
query = query.replaceAll("(?<![.])[*]", ".*");
|
||||
SearchFilesTask task = new SearchFilesTask(activity, searchDir, query, callback, query.startsWith("^") || query.contains("*"));
|
||||
task.execute();
|
||||
return task;
|
||||
}
|
||||
|
||||
public static class SearchFilesTask extends AsyncTask<Void, File, List<String>> implements IOFileFilter {
|
||||
private final Callback.a1<List<String>> _callback;
|
||||
private final File _searchDir;
|
||||
private final String _query;
|
||||
private final boolean _isRegex;
|
||||
private final WeakReference<Activity> _activityRef;
|
||||
|
||||
private final Pattern _regex;
|
||||
private Snackbar _snackBar;
|
||||
|
||||
public SearchFilesTask(Activity activity, File searchDir, String query, Callback.a1<List<String>> callback, boolean isRegex) {
|
||||
_searchDir = searchDir;
|
||||
_query = isRegex ? query : query.toLowerCase();
|
||||
_callback = callback;
|
||||
_isRegex = isRegex;
|
||||
_regex = isRegex ? Pattern.compile(_query) : null;
|
||||
_activityRef = new WeakReference<>(activity);
|
||||
}
|
||||
|
||||
// Called for both, file and folder filter
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return isMatching(file, true);
|
||||
}
|
||||
|
||||
// Not called
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return isMatching(new File(dir, name), true);
|
||||
}
|
||||
|
||||
// In iterateFilesAndDirs, subdirs are only scanned when returning true on it
|
||||
// But those dirs will also occur in iterator
|
||||
// Hence call this aagain with alwaysMatchDir=false
|
||||
public boolean isMatching(File file, boolean alwaysMatchDir) {
|
||||
if (file.isDirectory()) {
|
||||
// Do never scan .git directories, lots of files, lots of time
|
||||
if (file.getName().equals(".git")) {
|
||||
return false;
|
||||
}
|
||||
if (alwaysMatchDir) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
String name = file.getName();
|
||||
file = file.getParentFile();
|
||||
return _isRegex ? _regex.matcher(name).matches() : name.toLowerCase().contains(_query);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
if (_activityRef.get() != null) {
|
||||
_snackBar = Snackbar.make(_activityRef.get().findViewById(android.R.id.content), _query + "...", Snackbar.LENGTH_INDEFINITE);
|
||||
_snackBar.setAction(android.R.string.cancel, (v) -> {
|
||||
_snackBar.dismiss();
|
||||
cancel(true);
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> doInBackground(Void... voidp) {
|
||||
List<String> ret = new ArrayList<>();
|
||||
|
||||
boolean first = true;
|
||||
Iterator<File> iter = FileUtils.iterateFilesAndDirs(_searchDir, this, this);
|
||||
while (iter.hasNext() && !isCancelled()) {
|
||||
File f = iter.next();
|
||||
if (first) {
|
||||
first = false;
|
||||
if (f.equals(_searchDir)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (f.isFile() || (f.isDirectory() && isMatching(f, false))) {
|
||||
ret.add(f.getAbsolutePath().replace(_searchDir.getAbsolutePath() + "/", ""));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<String> ret) {
|
||||
super.onPostExecute(ret);
|
||||
if (_snackBar != null) {
|
||||
_snackBar.dismiss();
|
||||
}
|
||||
if (_callback != null) {
|
||||
try {
|
||||
_callback.callback(ret);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
new ActivityUtils(_activityRef.get()).hideSoftKeyboard().freeContextRef();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,11 @@ import android.content.Context;
|
|||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.CalendarContract;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
@ -47,6 +50,12 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
_activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void freeContextRef() {
|
||||
super.freeContextRef();
|
||||
_activity = null;
|
||||
}
|
||||
|
||||
//########################
|
||||
//## Methods
|
||||
//########################
|
||||
|
@ -85,9 +94,11 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
}
|
||||
|
||||
|
||||
public void showSnackBar(@StringRes int stringResId, boolean showLong) {
|
||||
Snackbar.make(_activity.findViewById(android.R.id.content), stringResId,
|
||||
showLong ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT).show();
|
||||
public Snackbar showSnackBar(@StringRes int stringResId, boolean showLong) {
|
||||
Snackbar s = Snackbar.make(_activity.findViewById(android.R.id.content), stringResId,
|
||||
showLong ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT);
|
||||
s.show();
|
||||
return s;
|
||||
}
|
||||
|
||||
public void showSnackBar(@StringRes int stringResId, boolean showLong, @StringRes int actionResId, View.OnClickListener listener) {
|
||||
|
@ -97,19 +108,59 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
.show();
|
||||
}
|
||||
|
||||
public void hideSoftKeyboard() {
|
||||
public ActivityUtils setSoftKeyboardVisibile(boolean visible, View... editView) {
|
||||
final Activity activity = _activity;
|
||||
if (activity != null) {
|
||||
final View v = (editView != null && editView.length > 0) ? (editView[0]) : (activity.getCurrentFocus() != null && activity.getCurrentFocus().getWindowToken() != null ? activity.getCurrentFocus() : null);
|
||||
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (v != null && imm != null) {
|
||||
Runnable r = () -> {
|
||||
if (visible) {
|
||||
v.requestFocus();
|
||||
imm.showSoftInput(v, InputMethodManager.SHOW_FORCED);
|
||||
} else {
|
||||
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
};
|
||||
r.run();
|
||||
for (int d : new int[]{100, 350}) {
|
||||
v.postDelayed(r, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ActivityUtils hideSoftKeyboard() {
|
||||
if (_activity != null) {
|
||||
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||
imm.hideSoftInputFromWindow(_activity.getCurrentFocus().getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void showSoftKeyboard() {
|
||||
public ActivityUtils showSoftKeyboard() {
|
||||
if (_activity != null) {
|
||||
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||
imm.showSoftInput(_activity.getCurrentFocus(), InputMethodManager.SHOW_FORCED);
|
||||
showSoftKeyboard(_activity.getCurrentFocus());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ActivityUtils showSoftKeyboard(View textInputView) {
|
||||
if (_activity != null) {
|
||||
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (imm != null && textInputView != null) {
|
||||
imm.showSoftInput(textInputView, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void showDialogWithHtmlTextView(@StringRes int resTitleId, String html) {
|
||||
showDialogWithHtmlTextView(resTitleId, html, true, null);
|
||||
|
@ -142,7 +193,7 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
}
|
||||
|
||||
// Toggle with no param, else set visibility according to first bool
|
||||
public void toggleStatusbarVisibility(boolean... optionalForceVisible) {
|
||||
public ActivityUtils toggleStatusbarVisibility(boolean... optionalForceVisible) {
|
||||
WindowManager.LayoutParams attrs = _activity.getWindow().getAttributes();
|
||||
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
if (optionalForceVisible.length == 0) {
|
||||
|
@ -153,9 +204,10 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
attrs.flags |= flag;
|
||||
}
|
||||
_activity.getWindow().setAttributes(attrs);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void showGooglePlayEntryForThisApp() {
|
||||
public ActivityUtils showGooglePlayEntryForThisApp() {
|
||||
String pkgId = "details?id=" + _activity.getPackageName();
|
||||
Intent goToMarket = new Intent(Intent.ACTION_VIEW, Uri.parse("market://" + pkgId));
|
||||
goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY |
|
||||
|
@ -167,9 +219,10 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
_activity.startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/" + pkgId)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setStatusbarColor(int color, boolean... fromRes) {
|
||||
public ActivityUtils setStatusbarColor(int color, boolean... fromRes) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (fromRes != null && fromRes.length > 0 && fromRes[0]) {
|
||||
color = ContextCompat.getColor(_context, color);
|
||||
|
@ -177,13 +230,55 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
|
||||
_activity.getWindow().setStatusBarColor(color);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setLauncherActivityEnabled(Class activityClass, boolean enable) {
|
||||
public ActivityUtils setLauncherActivityEnabled(Class activityClass, boolean enable) {
|
||||
Context context = _context.getApplicationContext();
|
||||
PackageManager pkg = context.getPackageManager();
|
||||
ComponentName component = new ComponentName(context, activityClass);
|
||||
pkg.setComponentEnabledSetting(component, enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
, PackageManager.DONT_KILL_APP);
|
||||
pkg.setComponentEnabledSetting(component, enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ColorInt
|
||||
public Integer getCurrentPrimaryColor() {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorPrimary"), typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public Integer getCurrentPrimaryDarkColor() {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorPrimaryDark"), typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public Integer getCurrentAccentColor() {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorAccent"), typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public Integer getActivityBackgroundColor() {
|
||||
TypedArray array = _activity.getTheme().obtainStyledAttributes(new int[]{
|
||||
android.R.attr.colorBackground,
|
||||
});
|
||||
int c = array.getColor(0, 0xFF0000);
|
||||
array.recycle();
|
||||
return c;
|
||||
}
|
||||
|
||||
public ActivityUtils startCalendarApp() {
|
||||
Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
|
||||
builder.appendPath("time");
|
||||
builder.appendPath(Long.toString(System.currentTimeMillis()));
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, builder.build());
|
||||
_activity.startActivity(intent);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,4 +31,24 @@ public class Callback {
|
|||
public interface a5<A, B, C, D, E> {
|
||||
void callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
||||
}
|
||||
|
||||
public interface b1<A> {
|
||||
boolean callback(A arg1);
|
||||
}
|
||||
|
||||
public interface b2<A, B> {
|
||||
boolean callback(A arg1, B arg2);
|
||||
}
|
||||
|
||||
public interface b3<A, B, C> {
|
||||
boolean callback(A arg1, B arg2, C arg3);
|
||||
}
|
||||
|
||||
public interface b4<A, B, C, D> {
|
||||
boolean callback(A arg1, B arg2, C arg3, D arg4);
|
||||
}
|
||||
|
||||
public interface b5<A, B, C, D, E> {
|
||||
boolean callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import android.net.ConnectivityManager;
|
|||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.ColorRes;
|
||||
|
@ -48,6 +49,7 @@ import android.support.annotation.StringRes;
|
|||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.text.Html;
|
||||
import android.text.InputFilter;
|
||||
import android.text.SpannableString;
|
||||
|
@ -74,6 +76,8 @@ import java.io.IOException;
|
|||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
|
@ -94,6 +98,9 @@ public class ContextUtils {
|
|||
return _context;
|
||||
}
|
||||
|
||||
public void freeContextRef() {
|
||||
_context = null;
|
||||
}
|
||||
|
||||
//
|
||||
// Class Methods
|
||||
|
@ -171,16 +178,24 @@ public class ContextUtils {
|
|||
return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor);
|
||||
}
|
||||
|
||||
public String getAndroidVersion() {
|
||||
return Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")";
|
||||
}
|
||||
|
||||
public String getAppVersionName() {
|
||||
try {
|
||||
PackageManager manager = _context.getPackageManager();
|
||||
try {
|
||||
PackageInfo info = manager.getPackageInfo(getPackageIdManifest(), 0);
|
||||
return info.versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return "?";
|
||||
try {
|
||||
PackageInfo info = manager.getPackageInfo(getPackageIdReal(), 0);
|
||||
return info.versionName;
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
public String getAppInstallationSource() {
|
||||
String src = null;
|
||||
|
@ -519,12 +534,76 @@ public class ContextUtils {
|
|||
/**
|
||||
* Get the private directory for the current package (usually /data/data/package.name/)
|
||||
*/
|
||||
public String getAppDataDir() {
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
public File getAppDataPrivateDir() {
|
||||
File filesDir;
|
||||
try {
|
||||
return _context.getPackageManager().getPackageInfo(getPackageIdReal(), 0).applicationInfo.dataDir;
|
||||
filesDir = new File(new File(_context.getPackageManager().getPackageInfo(getPackageIdReal(), 0).applicationInfo.dataDir), "files");
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return _context.getFilesDir().getParent();
|
||||
filesDir = _context.getFilesDir();
|
||||
}
|
||||
if (!filesDir.exists() && filesDir.mkdirs()) ;
|
||||
return filesDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public (accessible) appdata folders
|
||||
*/
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
public List<Pair<File, String>> getAppDataPublicDirs(boolean internalStorageFolder, boolean sdcardFolders, boolean storageNameWithoutType) {
|
||||
List<Pair<File, String>> dirs = new ArrayList<>();
|
||||
for (File externalFileDir : ContextCompat.getExternalFilesDirs(_context, null)) {
|
||||
if (externalFileDir == null || Environment.getExternalStorageDirectory() == null) {
|
||||
continue;
|
||||
}
|
||||
boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
boolean add = (internalStorageFolder && isInt) || (sdcardFolders && !isInt);
|
||||
if (add) {
|
||||
dirs.add(new Pair<>(externalFileDir, getStorageName(externalFileDir, storageNameWithoutType)));
|
||||
if (!externalFileDir.exists() && externalFileDir.mkdirs()) ;
|
||||
}
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
public String getStorageName(File externalFileDir, boolean storageNameWithoutType) {
|
||||
boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||
|
||||
String[] split = externalFileDir.getAbsolutePath().split("/");
|
||||
if (split.length > 2) {
|
||||
return isInt ? (storageNameWithoutType ? "Internal Storage" : "") : (storageNameWithoutType ? split[2] : ("SD Card (" + split[2] + ")"));
|
||||
} else {
|
||||
return "Storage";
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pair<File, String>> getStorages(boolean internalStorageFolder, boolean sdcardFolders) {
|
||||
List<Pair<File, String>> storages = new ArrayList<>();
|
||||
for (Pair<File, String> pair : getAppDataPublicDirs(internalStorageFolder, sdcardFolders, true)) {
|
||||
if (pair.first != null && pair.first.getAbsolutePath().lastIndexOf("/Android/data") > 0) {
|
||||
try {
|
||||
storages.add(new Pair<>(new File(pair.first.getCanonicalPath().replaceFirst("/Android/data.*", "")), pair.second));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return storages;
|
||||
}
|
||||
|
||||
public File getStorageRootFolder(File file) {
|
||||
String filepath;
|
||||
try {
|
||||
filepath = file.getCanonicalPath();
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
for (Pair<File, String> storage : getStorages(false, true)) {
|
||||
//noinspection ConstantConditions
|
||||
if (filepath.startsWith(storage.first.getAbsolutePath())) {
|
||||
return storage.first;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -854,6 +933,18 @@ public class ContextUtils {
|
|||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public Integer parseColor(String colorstr) {
|
||||
if (colorstr == null || colorstr.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Color.parseColor(colorstr);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -405,7 +405,7 @@ public class FileUtils {
|
|||
* Analyze given textfile and retrieve multiple information from it
|
||||
* Information is written back to the {@link AtomicInteger} parameters
|
||||
*/
|
||||
public static void retrieveTextFileSummary(File file, AtomicInteger numCharacters, AtomicInteger numLines) {
|
||||
public static void retrieveTextFileSummary(File file, AtomicInteger numCharacters, AtomicInteger numLines, AtomicInteger numWords) {
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new FileReader(file));
|
||||
|
@ -413,11 +413,15 @@ public class FileUtils {
|
|||
while ((line = br.readLine()) != null) {
|
||||
numLines.getAndIncrement();
|
||||
numCharacters.getAndSet(numCharacters.get() + line.length());
|
||||
if (!line.equals("")) {
|
||||
numWords.getAndSet(numWords.get() + line.split("\\s+").length);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
numCharacters.set(-1);
|
||||
numLines.set(-1);
|
||||
numWords.set(-1);
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.BroadcastReceiver;
|
||||
|
@ -37,19 +39,29 @@ import android.provider.MediaStore;
|
|||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.content.pm.ShortcutInfoCompat;
|
||||
import android.support.v4.content.pm.ShortcutManagerCompat;
|
||||
import android.support.v4.graphics.drawable.IconCompat;
|
||||
import android.support.v4.provider.DocumentFile;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
|
@ -65,15 +77,17 @@ import static android.app.Activity.RESULT_OK;
|
|||
* Also allows to parse/fetch information out of shared information.
|
||||
* (M)Permissions are not checked, wrap ShareUtils methods if neccessary
|
||||
*/
|
||||
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "SameParameterValue", "unused", "deprecation", "ConstantConditions", "ObsoleteSdkInt", "SpellCheckingInspection"})
|
||||
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "SameParameterValue", "unused", "deprecation", "ConstantConditions", "ObsoleteSdkInt", "SpellCheckingInspection", "JavadocReference"})
|
||||
public class ShareUtil {
|
||||
public final static String EXTRA_FILEPATH = "real_file_path_2";
|
||||
public final static SimpleDateFormat SDF_RFC3339_ISH = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm", Locale.getDefault());
|
||||
public final static SimpleDateFormat SDF_SHORT = new SimpleDateFormat("yyMMdd-HHmm", Locale.getDefault());
|
||||
public final static String MIME_TEXT_PLAIN = "text/plain";
|
||||
public final static String PREF_KEY__SAF_TREE_URI = "pref_key__saf_tree_uri";
|
||||
|
||||
public final static int REQUEST_CAMERA_PICTURE = 50001;
|
||||
public final static int REQUEST_PICK_PICTURE = 50002;
|
||||
public final static int REQUEST_SAF = 50003;
|
||||
|
||||
protected static String _lastCameraPictureFilepath;
|
||||
|
||||
|
@ -86,6 +100,10 @@ public class ShareUtil {
|
|||
_chooserTitle = "➥";
|
||||
}
|
||||
|
||||
public void freeContextRef() {
|
||||
_context = null;
|
||||
}
|
||||
|
||||
public String getFileProviderAuthority() {
|
||||
if (TextUtils.isEmpty(_fileProviderAuthority)) {
|
||||
throw new RuntimeException("Error at ShareUtil.getFileProviderAuthority(): No FileProvider authority provided");
|
||||
|
@ -513,6 +531,15 @@ public class ShareUtil {
|
|||
fileStr = fileStr.substring(prefix.length());
|
||||
}
|
||||
}
|
||||
|
||||
// external/ prefix for External storage
|
||||
if (fileStr.startsWith((tmps = "external/"))) {
|
||||
File f = new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + fileStr.substring(tmps.length())));
|
||||
if (f.exists()) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
// Next/OwnCloud Fileprovider
|
||||
for (String fp : new String[]{"org.nextcloud.files", "org.nextcloud.beta.files", "org.owncloud.files"}) {
|
||||
if (fileProvider.equals(fp) && fileStr.startsWith(tmps = "external_files/")) {
|
||||
|
@ -527,6 +554,7 @@ public class ShareUtil {
|
|||
if (fileProvider.equals("com.mi.android.globalFileexplorer.myprovider") && fileStr.startsWith(tmps = "external_files")) {
|
||||
return new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + fileStr.substring(tmps.length())));
|
||||
}
|
||||
|
||||
// URI Encoded paths with full path after content://package/
|
||||
if (fileStr.startsWith("/") || fileStr.startsWith("%2F")) {
|
||||
tmpf = new File(Uri.decode(fileStr));
|
||||
|
@ -557,7 +585,11 @@ public class ShareUtil {
|
|||
throw new RuntimeException("Error: ShareUtil.requestGalleryPicture needs an Activity Context.");
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
try {
|
||||
((Activity) _context).startActivityForResult(intent, REQUEST_PICK_PICTURE);
|
||||
} catch (Exception ex) {
|
||||
Toast.makeText(_context, "No gallery app installed!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -565,7 +597,7 @@ public class ShareUtil {
|
|||
* Result ({@link String}) will be available from {@link Activity#onActivityResult(int, int, Intent)}.
|
||||
* It has set resultCode to {@link Activity#RESULT_OK} with same requestCode, if successfully
|
||||
* The requested image savepath has to be stored at caller side (not contained in intent),
|
||||
* it can be retrieved using {@link #extractResultFromActivityResult(int, int, Intent)},
|
||||
* it can be retrieved using {@link #extractResultFromActivityResult(int, int, Intent, Activity...)}
|
||||
* returns null if an error happened.
|
||||
*
|
||||
* @param target Path to file to write to, if folder the filename gets app_name + millis + random filename. If null DCIM folder is used.
|
||||
|
@ -621,7 +653,9 @@ public class ShareUtil {
|
|||
* Forward all arguments from activity. Only requestCodes from {@link ShareUtil} get analyzed.
|
||||
* Also may forward results via local broadcast
|
||||
*/
|
||||
public Object extractResultFromActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
@SuppressLint("ApplySharedPref")
|
||||
public Object extractResultFromActivityResult(int requestCode, int resultCode, Intent data, Activity... activityOrNull) {
|
||||
Activity activity = greedyGetActivity(activityOrNull);
|
||||
switch (requestCode) {
|
||||
case REQUEST_CAMERA_PICTURE: {
|
||||
String picturePath = (resultCode == RESULT_OK) ? _lastCameraPictureFilepath : null;
|
||||
|
@ -676,6 +710,18 @@ public class ShareUtil {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQUEST_SAF: {
|
||||
if (resultCode == RESULT_OK && data != null && data.getData() != null) {
|
||||
Uri treeUri = data.getData();
|
||||
PreferenceManager.getDefaultSharedPreferences(_context).edit().putString(PREF_KEY__SAF_TREE_URI, treeUri.toString()).commit();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
activity.getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
}
|
||||
return treeUri;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -822,4 +868,264 @@ public class ShareUtil {
|
|||
customTabIntent.setPackage(pkg);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* 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
|
||||
*/
|
||||
public void requestStorageAccessFramework(Activity... activity) {
|
||||
Activity a = greedyGetActivity(activity);
|
||||
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.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
|
||||
);
|
||||
a.startActivityForResult(intent, REQUEST_SAF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage access framework tree uri. The user must have granted access via {@link #requestStorageAccessFramework(Activity...)}
|
||||
*
|
||||
* @return Uri or null if not granted yet
|
||||
*/
|
||||
public Uri getStorageAccessFrameworkTreeUri() {
|
||||
String treeStr = PreferenceManager.getDefaultSharedPreferences(_context).getString(PREF_KEY__SAF_TREE_URI, null);
|
||||
if (!TextUtils.isEmpty(treeStr)) {
|
||||
try {
|
||||
return Uri.parse(treeStr);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mounted storage folder root (by tree uri). The user must have granted access via {@link #requestStorageAccessFramework(Activity...)}
|
||||
*
|
||||
* @return File or null if SD not mounted
|
||||
*/
|
||||
public File getStorageAccessFolder() {
|
||||
Uri safUri = getStorageAccessFrameworkTreeUri();
|
||||
if (safUri != null) {
|
||||
String safUriStr = safUri.toString();
|
||||
ContextUtils cu = new ContextUtils(_context);
|
||||
for (Pair<File, String> storage : cu.getStorages(false, true)) {
|
||||
@SuppressWarnings("ConstantConditions") String storageFolderName = storage.first.getName();
|
||||
if (safUriStr.contains(storageFolderName)) {
|
||||
return storage.first;
|
||||
}
|
||||
}
|
||||
cu.freeContextRef();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not a file is under a storage access folder (external storage / SD)
|
||||
*
|
||||
* @param file The file object (file/folder)
|
||||
* @return Wether or not the file is under storage access folder
|
||||
*/
|
||||
public boolean isUnderStorageAccessFolder(File file) {
|
||||
if (file != null) {
|
||||
ContextUtils cu = new ContextUtils(_context);
|
||||
for (Pair<File, String> storage : cu.getStorages(false, true)) {
|
||||
if (file.getAbsolutePath().startsWith(storage.first.getAbsolutePath())) {
|
||||
cu.freeContextRef();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cu.freeContextRef();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Greedy extract Activity from parameter or convert context if it's a activity
|
||||
*/
|
||||
private Activity greedyGetActivity(Activity... activity) {
|
||||
if (activity != null && activity.length != 0 && activity[0] != null) {
|
||||
return activity[0];
|
||||
}
|
||||
if (_context instanceof Activity) {
|
||||
return (Activity) _context;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not a file can be written.
|
||||
* Requires storage access framework permission for external storage (SD)
|
||||
*
|
||||
* @param file The file object (file/folder)
|
||||
* @param isDir Wether or not the given file parameter is a directory
|
||||
* @return Wether or not the file can be written
|
||||
*/
|
||||
public boolean canWriteFile(File file, boolean isDir) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
} else if (file.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
|
||||
boolean s1 = isDir && file.getParentFile().canWrite();
|
||||
return !isDir && file.getParentFile() != null ? file.getParentFile().canWrite() : file.canWrite();
|
||||
} else {
|
||||
DocumentFile dof = getDocumentFile(file, isDir);
|
||||
return dof != null && dof.canWrite();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link DocumentFile} object out of a normal java {@link File}.
|
||||
* When used on a external storage (SD), use {@link #requestStorageAccessFramework(Activity...)}
|
||||
* first to get access. Otherwise this will fail.
|
||||
*
|
||||
* @param file The file/folder to convert
|
||||
* @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
|
||||
*/
|
||||
public DocumentFile getDocumentFile(File file, boolean isDir) {
|
||||
// On older versions use fromFile
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||
return DocumentFile.fromFile(file);
|
||||
}
|
||||
|
||||
// Get ContextUtils to find storageRootFolder
|
||||
ContextUtils cu = new ContextUtils(_context);
|
||||
File baseFolderFile = cu.getStorageRootFolder(file);
|
||||
cu.freeContextRef();
|
||||
|
||||
String baseFolder = baseFolderFile == null ? null : baseFolderFile.getAbsolutePath();
|
||||
boolean originalDirectory = false;
|
||||
if (baseFolder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String relPath = null;
|
||||
try {
|
||||
String fullPath = file.getCanonicalPath();
|
||||
if (!baseFolder.equals(fullPath)) {
|
||||
relPath = fullPath.substring(baseFolder.length() + 1);
|
||||
} else {
|
||||
originalDirectory = true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} catch (Exception ignored) {
|
||||
originalDirectory = true;
|
||||
}
|
||||
Uri treeUri;
|
||||
if ((treeUri = getStorageAccessFrameworkTreeUri()) == null) {
|
||||
return null;
|
||||
}
|
||||
DocumentFile dof = DocumentFile.fromTreeUri(_context, treeUri);
|
||||
if (originalDirectory) {
|
||||
return dof;
|
||||
}
|
||||
String[] parts = relPath.split("\\/");
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
DocumentFile nextDof = dof.findFile(parts[i]);
|
||||
if (nextDof == null) {
|
||||
nextDof = ((i < parts.length - 1) || isDir) ? dof.createDirectory(parts[i]) : dof.createFile("image", parts[i]);
|
||||
}
|
||||
dof = nextDof;
|
||||
}
|
||||
return dof;
|
||||
}
|
||||
|
||||
public void showMountSdDialog(@StringRes int title, @StringRes int description, @DrawableRes int mountDescriptionGraphic, Activity... activityOrNull) {
|
||||
Activity activity = greedyGetActivity(activityOrNull);
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Image viewer
|
||||
ImageView imv = new ImageView(activity);
|
||||
imv.setImageResource(mountDescriptionGraphic);
|
||||
imv.setAdjustViewBounds(true);
|
||||
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(activity);
|
||||
dialog.setView(imv);
|
||||
dialog.setTitle(title);
|
||||
dialog.setMessage(_context.getString(description) + "\n\n");
|
||||
dialog.setNegativeButton(android.R.string.cancel, null);
|
||||
dialog.setPositiveButton(android.R.string.yes, (dialogInterface, i) -> requestStorageAccessFramework(activity));
|
||||
AlertDialog dialogi = dialog.create();
|
||||
dialogi.show();
|
||||
}
|
||||
|
||||
public void writeFile(File file, boolean isDirectory, Callback.a2<Boolean, FileOutputStream> writeFileCallback) {
|
||||
try {
|
||||
FileOutputStream fileOutputStream = null;
|
||||
ParcelFileDescriptor pfd = null;
|
||||
if (file.canWrite()) {
|
||||
if (isDirectory) {
|
||||
file.mkdirs();
|
||||
} else {
|
||||
fileOutputStream = new FileOutputStream(file);
|
||||
}
|
||||
} else {
|
||||
DocumentFile dof = getDocumentFile(file, isDirectory);
|
||||
if (dof != null && dof.getUri() != null && dof.canWrite()) {
|
||||
if (isDirectory) {
|
||||
// Nothing to do
|
||||
} else {
|
||||
pfd = _context.getContentResolver().openFileDescriptor(dof.getUri(), "w");
|
||||
fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (writeFileCallback != null) {
|
||||
writeFileCallback.callback(fileOutputStream != null || (isDirectory && file.exists()), fileOutputStream);
|
||||
}
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
}
|
||||
if (pfd != null) {
|
||||
pfd.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call telephone number.
|
||||
* Non direct call, opens up the dialer and pre-sets the telephone number. User needs to press manually.
|
||||
* Direct call requires M permission granted, also add permissions to manifest:
|
||||
* <uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
*
|
||||
* @param telNo The telephone number to call
|
||||
* @param directCall Direct call number if possible
|
||||
*/
|
||||
@SuppressWarnings("SimplifiableConditionalExpression")
|
||||
public void callTelephoneNumber(String telNo, boolean... directCall) {
|
||||
Activity activity = greedyGetActivity();
|
||||
if (activity == null) {
|
||||
throw new RuntimeException("Error: ShareUtil::callTelephoneNumber needs to be contstructed with activity context");
|
||||
}
|
||||
boolean ldirectCall = (directCall != null && directCall.length > 0) ? directCall[0] : true;
|
||||
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23 && ldirectCall && activity != null) {
|
||||
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.CALL_PHONE}, 4001);
|
||||
ldirectCall = false;
|
||||
} else {
|
||||
try {
|
||||
Intent callIntent = new Intent(Intent.ACTION_CALL);
|
||||
callIntent.setData(Uri.parse("tel:" + telNo));
|
||||
activity.startActivity(callIntent);
|
||||
} catch (Exception ignored) {
|
||||
ldirectCall = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Show dialer up with telephone number pre-inserted
|
||||
if (!ldirectCall) {
|
||||
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", telNo, null));
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ import java.text.SimpleDateFormat
|
|||
|
||||
buildscript {
|
||||
ext {
|
||||
version_gradle_tools = "3.2.1"
|
||||
version_plugin_kotlin = "1.3.11"
|
||||
version_gradle_tools = "3.4.2"
|
||||
version_plugin_kotlin = "1.3.41"
|
||||
enable_plugin_kotlin = false
|
||||
|
||||
version_compileSdk = 28
|
||||
|
@ -79,7 +79,7 @@ static String findUsedAndroidLocales() {
|
|||
Set<String> langs = new HashSet<>()
|
||||
new File('.').eachFileRecurse(groovy.io.FileType.DIRECTORIES) {
|
||||
final foldername = it.name
|
||||
if (foldername.startsWith('values-') && !it.canonicalPath.contains("build" + File.separator + "intermediates")) {
|
||||
if (foldername.startsWith('values-') && !it.canonicalPath.contains("build" + File.separator + "intermediates") && !it.canonicalPath.contains("gradle" + File.separator + "daemon")) {
|
||||
new File(it.toString()).eachFileRecurse(groovy.io.FileType.FILES) {
|
||||
if (it.name.toLowerCase().endsWith(".xml") && it.getCanonicalFile().getText('UTF-8').contains("<string")) {
|
||||
langs.add(foldername.replace("values-", ""))
|
||||
|
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,6 @@
|
|||
#Fri Jul 26 02:59:10 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
||||
|
|
Loading…
Reference in a new issue