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("!\\[(.*?)\\]\\((.*?)\\)", "") // 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