diff --git a/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java b/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java index 1e980f8b..1d3f016b 100644 --- a/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java +++ b/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java @@ -93,8 +93,8 @@ public class SimpleMarkdownParser { .replaceAll("!\\[(.*?)\\]\\((.*?)\\)", "$1") // img .replaceAll("<(http|https):\\/\\/(.*)>", "$1://$2") // a href (DEP: img) .replaceAll("\\[(.*?)\\]\\((.*?)\\)", "$1") // a href (DEP: img) - .replaceAll("(?m)^([-*] )(.*)$", " $2 ") // unordered list + end line - .replaceAll("(?m)^ (-|\\*) ([^<]*)$", "   $2 ") // unordered list2 + end line + .replaceAll("(?m)^[-*] (.*)$", " $1 ") // unordered list + end line + .replaceAll("(?m)^ [-*] (.*)$", "   $1 ") // unordered list2 + end line .replaceAll("`([^<]*)`", "$1") // code .replace("\\*", "●") // temporary replace escaped star symbol .replaceAll("(?m)\\*\\*(.*)\\*\\*", "$1") // bold (DEP: temp star) @@ -111,6 +111,7 @@ public class SimpleMarkdownParser { public String filter(String text) { text = text .replace("New:", "New:") + .replace("New features:", "New:") .replace("Added:", "Added:") .replace("Add:", "Add:") .replace("Fixed:", "Fixed:") diff --git a/app/src/main/java/net/gsantner/opoc/preference/SharedPreferencesPropertyBackend.java b/app/src/main/java/net/gsantner/opoc/preference/SharedPreferencesPropertyBackend.java index 8fb486a3..2d1edbd9 100644 --- a/app/src/main/java/net/gsantner/opoc/preference/SharedPreferencesPropertyBackend.java +++ b/app/src/main/java/net/gsantner/opoc/preference/SharedPreferencesPropertyBackend.java @@ -44,6 +44,7 @@ import android.text.TextUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.List; @@ -512,4 +513,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend= 23 || begin < 0) ? 0 : begin; + end = (end >= 23 || end < 0) ? 0 : end; + int h = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); + return h >= begin && h <= end; + } } diff --git a/app/src/main/java/net/gsantner/opoc/ui/SearchOrCustomTextDialog.java b/app/src/main/java/net/gsantner/opoc/ui/SearchOrCustomTextDialog.java index f83d4e6e..8223c35b 100644 --- a/app/src/main/java/net/gsantner/opoc/ui/SearchOrCustomTextDialog.java +++ b/app/src/main/java/net/gsantner/opoc/ui/SearchOrCustomTextDialog.java @@ -11,6 +11,7 @@ package net.gsantner.opoc.ui; import android.app.Activity; +import android.graphics.Typeface; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -23,6 +24,7 @@ import android.text.TextWatcher; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.ArrayAdapter; import android.widget.Filter; import android.widget.LinearLayout; @@ -76,10 +78,10 @@ public class SearchOrCustomTextDialog { TextView textView = (TextView) super.getView(pos, convertView, parent); String text = textView.getText().toString(); - textView.setTextColor(dopt.textColor); - if (dopt.highlightData.contains(text)) { - textView.setTextColor(dopt.highlightColor); - } + boolean hl = dopt.highlightData.contains(text); + textView.setTextColor(hl ? dopt.highlightColor : dopt.textColor); + textView.setTypeface(null, hl ? Typeface.BOLD : Typeface.NORMAL); + return textView; } @@ -184,6 +186,9 @@ public class SearchOrCustomTextDialog { return false; }); + if (dialog.getWindow() != null) { + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } dialog.show(); } } diff --git a/app/src/main/java/net/gsantner/opoc/util/ActivityUtils.java b/app/src/main/java/net/gsantner/opoc/util/ActivityUtils.java index 2114f202..23a9212d 100644 --- a/app/src/main/java/net/gsantner/opoc/util/ActivityUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/ActivityUtils.java @@ -3,17 +3,20 @@ * Maintained by Gregor Santner, 2016- * https://gsantner.net/ * - * License: Apache 2.0 / Commercial - * https://github.com/gsantner/opoc/#licensing - * https://www.apache.org/licenses/LICENSE-2.0 + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * https://github.com/gsantner/opoc/#licensing * #########################################################*/ package net.gsantner.opoc.util; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.support.annotation.StringRes; @@ -174,4 +177,12 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils { _activity.getWindow().setStatusBarColor(color); } } + + public void 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); + } } diff --git a/app/src/main/java/net/gsantner/opoc/util/Callback.java b/app/src/main/java/net/gsantner/opoc/util/Callback.java index 697e7d77..f7933e29 100644 --- a/app/src/main/java/net/gsantner/opoc/util/Callback.java +++ b/app/src/main/java/net/gsantner/opoc/util/Callback.java @@ -3,9 +3,9 @@ * Maintained by Gregor Santner, 2018- * https://gsantner.net/ * - * License: Apache 2.0 / Commercial - * https://github.com/gsantner/opoc/#licensing - * https://www.apache.org/licenses/LICENSE-2.0 + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * https://github.com/gsantner/opoc/#licensing * #########################################################*/ package net.gsantner.opoc.util; diff --git a/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java b/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java index 1ade61f9..ed90becb 100644 --- a/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java @@ -3,9 +3,9 @@ * Maintained by Gregor Santner, 2016- * https://gsantner.net/ * - * License: Apache 2.0 / Commercial - * https://github.com/gsantner/opoc/#licensing - * https://www.apache.org/licenses/LICENSE-2.0 + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * https://github.com/gsantner/opoc/#licensing * #########################################################*/ package net.gsantner.opoc.util; @@ -167,7 +167,7 @@ public class ContextUtils { public String getAppVersionName() { try { PackageManager manager = _context.getPackageManager(); - PackageInfo info = manager.getPackageInfo(getPackageName(), 0); + PackageInfo info = manager.getPackageInfo(getPackageIdManifest(), 0); return info.versionName; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); @@ -178,7 +178,7 @@ public class ContextUtils { public String getAppInstallationSource() { String src = null; try { - src = _context.getPackageManager().getInstallerPackageName(getPackageName()); + src = _context.getPackageManager().getInstallerPackageName(getPackageIdManifest()); } catch (Exception ignored) { } if (TextUtils.isEmpty(src)) { @@ -224,13 +224,20 @@ public class ContextUtils { } /** - * Get this apps package name. The builtin method may fail when used with flavors + * Get the apps base packagename, which is equal with all build flavors and variants */ - public String getPackageName() { + public String getPackageIdManifest() { String pkg = rstr("manifest_package_id"); return pkg != null ? pkg : _context.getPackageName(); } + /** + * Get this apps package name, returns the flavor specific package name. + */ + public String getPackageIdReal() { + return _context.getPackageName(); + } + /** * Get field from ${applicationId}.BuildConfig * May be helpful in libraries, where a access to @@ -240,7 +247,7 @@ public class ContextUtils { * Falls back to applicationId of the app which may differ from manifest. */ public Object getBuildConfigValue(String fieldName) { - String pkg = getPackageName() + ".BuildConfig"; + String pkg = getPackageIdManifest() + ".BuildConfig"; try { Class c = Class.forName(pkg); return c.getField(fieldName).get(null); diff --git a/app/src/main/java/net/gsantner/opoc/util/FileUtils.java b/app/src/main/java/net/gsantner/opoc/util/FileUtils.java index 5e02d17f..642699c1 100644 --- a/app/src/main/java/net/gsantner/opoc/util/FileUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/FileUtils.java @@ -3,9 +3,9 @@ * Maintained by Gregor Santner, 2017- * https://gsantner.net/ * - * License: Apache 2.0 / Commercial - * https://github.com/gsantner/opoc/#licensing - * https://www.apache.org/licenses/LICENSE-2.0 + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * https://github.com/gsantner/opoc/#licensing * #########################################################*/ package net.gsantner.opoc.util; @@ -397,7 +397,8 @@ public class FileUtils { } public static boolean isTextFile(File file) { - return getMimeType(file).startsWith("text/"); + String mime = getMimeType(file); + return mime != null && mime.startsWith("text/"); } /** diff --git a/app/src/main/java/net/gsantner/opoc/util/NetworkUtils.java b/app/src/main/java/net/gsantner/opoc/util/NetworkUtils.java index 6190188e..50d11664 100644 --- a/app/src/main/java/net/gsantner/opoc/util/NetworkUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/NetworkUtils.java @@ -3,9 +3,9 @@ * Maintained by Gregor Santner, 2017- * https://gsantner.net/ * - * License: Apache 2.0 / Commercial - * https://github.com/gsantner/opoc/#licensing - * https://www.apache.org/licenses/LICENSE-2.0 + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * https://github.com/gsantner/opoc/#licensing * #########################################################*/ package net.gsantner.opoc.util; diff --git a/app/src/main/java/net/gsantner/opoc/util/PermissionChecker.java b/app/src/main/java/net/gsantner/opoc/util/PermissionChecker.java index 36265218..6c0efd72 100644 --- a/app/src/main/java/net/gsantner/opoc/util/PermissionChecker.java +++ b/app/src/main/java/net/gsantner/opoc/util/PermissionChecker.java @@ -3,9 +3,9 @@ * Maintained by Gregor Santner, 2017- * https://gsantner.net/ * - * License: Apache 2.0 / Commercial - * https://github.com/gsantner/opoc/#licensing - * https://www.apache.org/licenses/LICENSE-2.0 + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * https://github.com/gsantner/opoc/#licensing * #########################################################*/ package net.gsantner.opoc.util; diff --git a/app/src/main/java/net/gsantner/opoc/util/ShareUtil.java b/app/src/main/java/net/gsantner/opoc/util/ShareUtil.java index 9b8d9fe8..4b4368cf 100644 --- a/app/src/main/java/net/gsantner/opoc/util/ShareUtil.java +++ b/app/src/main/java/net/gsantner/opoc/util/ShareUtil.java @@ -3,17 +3,22 @@ * Maintained by Gregor Santner, 2017- * https://gsantner.net/ * - * License: Apache 2.0 / Commercial - * https://github.com/gsantner/opoc/#licensing - * https://www.apache.org/licenses/LICENSE-2.0 + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * https://github.com/gsantner/opoc/#licensing * #########################################################*/ package net.gsantner.opoc.util; import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; @@ -21,14 +26,18 @@ import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.Handler; +import android.os.ParcelFileDescriptor; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintJob; import android.print.PrintManager; +import android.provider.CalendarContract; +import android.provider.MediaStore; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; 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; @@ -38,16 +47,22 @@ import android.view.View; import android.webkit.WebView; import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Random; +import static android.app.Activity.RESULT_OK; + /** - * A utility class to ease information sharing on Android - * Also allows to parse/fetch information out of shared information + * A utility class to ease information sharing on Android. + * 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"}) public class ShareUtil { @@ -56,6 +71,10 @@ public class ShareUtil { public final static SimpleDateFormat SDF_SHORT = new SimpleDateFormat("yyMMdd-HHmm", Locale.getDefault()); public final static String MIME_TEXT_PLAIN = "text/plain"; + public final static int REQUEST_CAMERA_PICTURE = 50001; + public final static int REQUEST_PICK_PICTURE = 50002; + + protected static String _lastCameraPictureFilepath; protected Context _context; protected String _fileProviderAuthority; @@ -173,13 +192,45 @@ public class ShareUtil { * @param file The file to share * @param mimeType The files mime type */ - public void shareStream(File file, String mimeType) { - Uri fileUri = FileProvider.getUriForFile(_context, getFileProviderAuthority(), file); + public boolean shareStream(File file, String mimeType) { Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_STREAM, fileUri); intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath()); intent.setType(mimeType); - showChooser(intent, null); + + try { + Uri fileUri = FileProvider.getUriForFile(_context, getFileProviderAuthority(), file); + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + showChooser(intent, null); + return true; + } catch (Exception e) { // FileUriExposed(API24) / IllegalArgument + return false; + } + } + + /** + * Start calendar application to add new event, with given details prefilled + */ + public void createCalendarAppointment(@Nullable String title, @Nullable String description, @Nullable String location, @Nullable Long... startAndEndTime) { + Intent intent = new Intent(Intent.ACTION_INSERT).setData(CalendarContract.Events.CONTENT_URI); + if (title != null) { + intent.putExtra(CalendarContract.Events.TITLE, title); + } + if (description != null) { + description = description.length() > 800 ? description.substring(0, 800) : description; + intent.putExtra(CalendarContract.Events.DESCRIPTION, description); + } + if (location != null) { + intent.putExtra(CalendarContract.Events.EVENT_LOCATION, location); + } + if (startAndEndTime != null) { + if (startAndEndTime.length > 0 && startAndEndTime[0] > 0) { + intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startAndEndTime[0]); + } + if (startAndEndTime.length > 1 && startAndEndTime[1] > 0) { + intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, startAndEndTime[1]); + } + } + _context.startActivity(intent); } /** @@ -187,15 +238,29 @@ public class ShareUtil { * * @param file The file to share */ - public void viewFileInOtherApp(File file, @Nullable String type) { - Uri fileUri = FileProvider.getUriForFile(_context, getFileProviderAuthority(), file); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_STREAM, fileUri); - intent.setData(fileUri); - intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath()); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setDataAndType(fileUri, type); - showChooser(intent, null); + public boolean viewFileInOtherApp(File file, @Nullable String type) { + // On some specific devices the first won't work + Uri fileUri = null; + try { + fileUri = FileProvider.getUriForFile(_context, getFileProviderAuthority(), file); + } catch (Exception ignored) { + try { + fileUri = Uri.fromFile(file); + } catch (Exception ignored2) { + } + } + + if (fileUri != null) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + intent.setData(fileUri); + intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath()); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(fileUri, type); + showChooser(intent, null); + return true; + } + return false; } /** @@ -473,4 +538,228 @@ public class ShareUtil { } return null; } + + /** + * Request a picture from gallery + * Result will be available from {@link Activity#onActivityResult(int, int, Intent)}. + * It will return the path to the image if locally stored. If retrieved from e.g. a cloud + * service, the image will get copied to app-cache folder and it's path returned. + */ + public void requestGalleryPicture() { + if (!(_context instanceof Activity)) { + throw new RuntimeException("Error: ShareUtil.requestGalleryPicture needs an Activity Context."); + } + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + ((Activity) _context).startActivityForResult(intent, REQUEST_PICK_PICTURE); + } + + /** + * Request a picture from camera-like apps + * 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)}, + * 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. + */ + public String requestCameraPicture(File target) { + if (!(_context instanceof Activity)) { + throw new RuntimeException("Error: ShareUtil.requestCameraPicture needs an Activity Context."); + } + String cameraPictureFilepath = null; + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (takePictureIntent.resolveActivity(_context.getPackageManager()) != null) { + File photoFile; + try { + // Create an image file name + if (target != null && !target.isDirectory()) { + photoFile = target; + } else { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", Locale.getDefault()); + 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()); + photoFile = new File(storageDir, imageFileName + ".jpg"); + if (!photoFile.getParentFile().exists() && !photoFile.getParentFile().mkdirs()) { + photoFile = File.createTempFile(imageFileName + "_", ".jpg", storageDir); + } + } + + //noinspection StatementWithEmptyBody + if (!photoFile.getParentFile().exists() && photoFile.getParentFile().mkdirs()) ; + + // Save a file: path for use with ACTION_VIEW intents + cameraPictureFilepath = photoFile.getAbsolutePath(); + } catch (IOException ex) { + return null; + } + + // Continue only if the File was successfully created + if (photoFile != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + Uri uri = FileProvider.getUriForFile(_context, getFileProviderAuthority(), photoFile); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + } else { + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); + } + ((Activity) _context).startActivityForResult(takePictureIntent, REQUEST_CAMERA_PICTURE); + } + } + _lastCameraPictureFilepath = cameraPictureFilepath; + return cameraPictureFilepath; + } + + /** + * Extract result data from {@link Activity#onActivityResult(int, int, Intent)}. + * 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) { + switch (requestCode) { + case REQUEST_CAMERA_PICTURE: { + String picturePath = (resultCode == RESULT_OK) ? _lastCameraPictureFilepath : null; + if (picturePath != null) { + sendLocalBroadcastWithStringExtra(REQUEST_CAMERA_PICTURE + "", EXTRA_FILEPATH, picturePath); + } + return picturePath; + } + case REQUEST_PICK_PICTURE: { + if (resultCode == RESULT_OK && data != null) { + Uri selectedImage = data.getData(); + String[] filePathColumn = {MediaStore.Images.Media.DATA}; + String picturePath = null; + + Cursor cursor = _context.getContentResolver().query(selectedImage, filePathColumn, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + for (String column : filePathColumn) { + int curColIndex = cursor.getColumnIndex(column); + if (curColIndex == -1) { + continue; + } + picturePath = cursor.getString(curColIndex); + if (!TextUtils.isEmpty(picturePath)) { + break; + } + } + cursor.close(); + } + + // Retrieve image from file descriptor / Cloud, e.g.: Google Drive, Picasa + if (picturePath == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + try { + ParcelFileDescriptor parcelFileDescriptor = _context.getContentResolver().openFileDescriptor(selectedImage, "r"); + if (parcelFileDescriptor != null) { + FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + FileInputStream input = new FileInputStream(fileDescriptor); + + // Create temporary file in cache directory + picturePath = File.createTempFile("image", "tmp", _context.getCacheDir()).getAbsolutePath(); + FileUtils.writeFile(new File(picturePath), FileUtils.readCloseBinaryStream(input)); + } + } catch (IOException ignored) { + // nothing we can do here, null value will be handled below + } + } + + // Return path to picture on success, else null + if (picturePath != null) { + sendLocalBroadcastWithStringExtra(REQUEST_CAMERA_PICTURE + "", EXTRA_FILEPATH, picturePath); + } + return picturePath; + } + break; + } + } + return null; + } + + /** + * 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. + */ + public void sendLocalBroadcastWithStringExtra(String action, String extra, CharSequence value) { + Intent intent = new Intent(action); + intent.putExtra(extra, value); + LocalBroadcastManager.getInstance(_context).sendBroadcast(intent); + } + + /** + * Receive broadcast results via a callback method + * + * @param callback Function to call with received {@link Intent} + * @param autoUnregister wether or not to automatically unregister receiver after first match + * @param filterActions All {@link IntentFilter} actions to filter for + * @return The created instance. Has to be unregistered on {@link Activity} lifecycle events. + */ + public BroadcastReceiver receiveResultFromLocalBroadcast(Callback.a2 callback, boolean autoUnregister, String... filterActions) { + IntentFilter intentFilter = new IntentFilter(); + for (String filterAction : filterActions) { + intentFilter.addAction(filterAction); + } + final BroadcastReceiver br = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + if (autoUnregister) { + LocalBroadcastManager.getInstance(_context).unregisterReceiver(this); + } + try { + callback.callback(intent, this); + } catch (Exception ignored) { + } + } + } + }; + LocalBroadcastManager.getInstance(_context).registerReceiver(br, intentFilter); + return br; + } + + /** + * Request edit of image (by image editor/viewer - for example to crop image) + * + * @param file File that should be edited + */ + public void requestPictureEdit(File file) { + Uri uri = getUriByFileProviderAuthority(file); + int flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION; + + Intent intent = new Intent(Intent.ACTION_EDIT); + intent.setDataAndType(uri, "image/*"); + intent.addFlags(flags); + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath()); + + for (ResolveInfo resolveInfo : _context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)) { + String packageName = resolveInfo.activityInfo.packageName; + _context.grantUriPermission(packageName, uri, flags); + } + _context.startActivity(Intent.createChooser(intent, null)); + } + + /** + * Get content://media/ Uri for given file, or null if not indexed + * + * @param file Target file + * @param mode 1 for picture, 2 for video, anything else for other + * @return + */ + public Uri getMediaUri(File file, int mode) { + Uri uri = MediaStore.Files.getContentUri("external"); + uri = (mode != 0) ? (mode == 1 ? MediaStore.Images.Media.EXTERNAL_CONTENT_URI : MediaStore.Video.Media.EXTERNAL_CONTENT_URI) : uri; + + Cursor cursor = null; + try { + cursor = _context.getContentResolver().query(uri, new String[]{MediaStore.Images.Media._ID}, MediaStore.Images.Media.DATA + "= ?", new String[]{file.getAbsolutePath()}, null); + if (cursor != null && cursor.moveToFirst()) { + int mediaid = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID)); + return Uri.withAppendedPath(uri, mediaid + ""); + } + } catch (Exception ignored) { + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } } diff --git a/build.gradle b/build.gradle index 4afa4183..937b7fc1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,11 @@ /*####################################################### -* -* Maintained by Gregor Santner, 2017- -* https://gsantner.net/ -* -* License: Apache 2.0 / Commercial -* https://github.com/gsantner/opoc/#licensing -* https://www.apache.org/licenses/LICENSE-2.0 -* + * + * Maintained by Gregor Santner, 2017- + * https://gsantner.net/ + * + * License of this file: Apache 2.0 (Commercial upon request) + * https://www.apache.org/licenses/LICENSE-2.0 + * #########################################################*/ import java.text.SimpleDateFormat