mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
1040 lines
40 KiB
Java
1040 lines
40 KiB
Java
/*******************************************************************************
|
|
*
|
|
* Messenger Android Frontend
|
|
* (C) 2013-2016 Nikolai Kudashov
|
|
* (C) 2017 Björn Petersen
|
|
* Contact: r10s@b44t.com, http://b44t.com
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see http://www.gnu.org/licenses/ .
|
|
*
|
|
******************************************************************************/
|
|
|
|
package com.b44t.messenger;
|
|
|
|
import android.Manifest;
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorSet;
|
|
import android.animation.ObjectAnimator;
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Activity;
|
|
import android.app.DownloadManager;
|
|
import android.content.ContentUris;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Configuration;
|
|
import android.database.Cursor;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Color;
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.graphics.Typeface;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Environment;
|
|
import android.provider.DocumentsContract;
|
|
import android.provider.MediaStore;
|
|
import android.text.SpannableStringBuilder;
|
|
import android.text.Spanned;
|
|
import android.text.style.ForegroundColorSpan;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.Log;
|
|
import android.util.StateSet;
|
|
import android.view.Display;
|
|
import android.view.Surface;
|
|
import android.view.View;
|
|
import android.view.WindowManager;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.webkit.MimeTypeMap;
|
|
import android.widget.AbsListView;
|
|
import android.widget.EdgeEffect;
|
|
import android.widget.EditText;
|
|
import android.widget.ListView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.b44t.ui.Components.ForegroundDetector;
|
|
import com.b44t.ui.Components.TypefaceSpan;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.nio.channels.FileChannel;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.Hashtable;
|
|
import java.util.Locale;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class AndroidUtilities {
|
|
|
|
private static int prevOrientation = -10;
|
|
|
|
public static int statusBarHeight = 0;
|
|
public static float density = 1;
|
|
public static Point displaySize = new Point();
|
|
public static Integer photoSize = null;
|
|
public static DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
public static int leftBaseline;
|
|
public static boolean usingHardwareInput;
|
|
private static Boolean isTablet = null;
|
|
private static int adjustOwnerClassGuid = 0;
|
|
|
|
static {
|
|
try {
|
|
final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
|
|
final Pattern IP_ADDRESS = Pattern.compile(
|
|
"((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
|
|
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
|
|
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
|
|
+ "|[1-9][0-9]|[0-9]))");
|
|
final String IRI = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
|
|
final String GOOD_GTLD_CHAR = "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
|
|
final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
|
|
final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
|
|
final Pattern DOMAIN_NAME = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
static {
|
|
density = ApplicationLoader.applicationContext.getResources().getDisplayMetrics().density;
|
|
leftBaseline = isTablet() ? 80 : 72;
|
|
checkDisplaySize();
|
|
}
|
|
|
|
public static void requestAdjustResize(Activity activity, int classGuid) {
|
|
if (activity == null || isTablet()) {
|
|
return;
|
|
}
|
|
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
|
adjustOwnerClassGuid = classGuid;
|
|
}
|
|
|
|
public static void removeAdjustResize(Activity activity, int classGuid) {
|
|
if (activity == null || isTablet()) {
|
|
return;
|
|
}
|
|
if (adjustOwnerClassGuid == classGuid) {
|
|
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
|
|
}
|
|
}
|
|
|
|
public static boolean isInternalUri(Uri uri) {
|
|
String pathString = uri.getPath();
|
|
if (pathString == null) {
|
|
return false;
|
|
}
|
|
while (true) {
|
|
String newPath = Utilities.readlink(pathString);
|
|
if (newPath == null || newPath.equals(pathString)) {
|
|
break;
|
|
}
|
|
pathString = newPath;
|
|
}
|
|
return pathString != null && pathString.toLowerCase().contains("/data/data/" + ApplicationLoader.applicationContext.getPackageName() + "/files");
|
|
}
|
|
|
|
public static void lockOrientation(Activity activity) {
|
|
if (activity == null || prevOrientation != -10) {
|
|
return;
|
|
}
|
|
try {
|
|
prevOrientation = activity.getRequestedOrientation();
|
|
WindowManager manager = (WindowManager)activity.getSystemService(Activity.WINDOW_SERVICE);
|
|
if (manager != null && manager.getDefaultDisplay() != null) {
|
|
int rotation = manager.getDefaultDisplay().getRotation();
|
|
int orientation = activity.getResources().getConfiguration().orientation;
|
|
|
|
if (rotation == Surface.ROTATION_270) {
|
|
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
} else {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
|
|
}
|
|
} else if (rotation == Surface.ROTATION_90) {
|
|
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
|
|
} else {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
}
|
|
} else if (rotation == Surface.ROTATION_0) {
|
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
} else {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
}
|
|
} else {
|
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
|
|
} else {
|
|
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
public static void unlockOrientation(Activity activity) {
|
|
if (activity == null) {
|
|
return;
|
|
}
|
|
try {
|
|
if (prevOrientation != -10) {
|
|
activity.setRequestedOrientation(prevOrientation);
|
|
prevOrientation = -10;
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
public static void showKeyboard(View view) {
|
|
if (view == null) {
|
|
return;
|
|
}
|
|
try {
|
|
InputMethodManager inputManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
public static void hideKeyboard(View view) {
|
|
if (view == null) {
|
|
return;
|
|
}
|
|
try {
|
|
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
if (!imm.isActive()) {
|
|
return;
|
|
}
|
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
public static File getCacheDir() {
|
|
String state = null;
|
|
try {
|
|
state = Environment.getExternalStorageState();
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
if (state == null || state.startsWith(Environment.MEDIA_MOUNTED)) {
|
|
try {
|
|
File file = ApplicationLoader.applicationContext.getExternalCacheDir();
|
|
if (file != null) {
|
|
return file;
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
try {
|
|
File file = ApplicationLoader.applicationContext.getCacheDir();
|
|
if (file != null) {
|
|
return file;
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return new File("");
|
|
}
|
|
*/
|
|
|
|
public static int dp(float value) {
|
|
if (value == 0) {
|
|
return 0;
|
|
}
|
|
return (int) Math.ceil(density * value);
|
|
}
|
|
|
|
public static int compare(int lhs, int rhs) {
|
|
if (lhs == rhs) {
|
|
return 0;
|
|
} else if (lhs > rhs) {
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public static float dpf2(float value) {
|
|
if (value == 0) {
|
|
return 0;
|
|
}
|
|
return density * value;
|
|
}
|
|
|
|
public static void checkDisplaySize() {
|
|
try {
|
|
Configuration configuration = ApplicationLoader.applicationContext.getResources().getConfiguration();
|
|
usingHardwareInput = configuration.keyboard != Configuration.KEYBOARD_NOKEYS && configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
|
|
WindowManager manager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE);
|
|
if (manager != null) {
|
|
Display display = manager.getDefaultDisplay();
|
|
if (display != null) {
|
|
display.getMetrics(displayMetrics);
|
|
display.getSize(displaySize);
|
|
Log.i("DeltaChat", "Display size: " + displaySize.x + " x " + displaySize.y + ", DPI: " + displayMetrics.xdpi + " x " + displayMetrics.ydpi);
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
}
|
|
}
|
|
|
|
public static float getPixelsInCM(float cm, boolean isX) {
|
|
return (cm / 2.54f) * (isX ? displayMetrics.xdpi : displayMetrics.ydpi);
|
|
}
|
|
|
|
public static void runOnUIThread(Runnable runnable) {
|
|
runOnUIThread(runnable, 0);
|
|
}
|
|
|
|
public static void runOnUIThread(Runnable runnable, long delay) {
|
|
if (delay == 0) {
|
|
ApplicationLoader.applicationHandler.post(runnable);
|
|
} else {
|
|
ApplicationLoader.applicationHandler.postDelayed(runnable, delay);
|
|
}
|
|
}
|
|
|
|
public static void cancelRunOnUIThread(Runnable runnable) {
|
|
ApplicationLoader.applicationHandler.removeCallbacks(runnable);
|
|
}
|
|
|
|
public static boolean isTablet() {
|
|
if (isTablet == null) {
|
|
isTablet = ApplicationLoader.applicationContext.getResources().getBoolean(R.bool.isTablet);
|
|
}
|
|
return isTablet;
|
|
}
|
|
|
|
public static boolean isSmallTablet() {
|
|
float minSide = Math.min(displaySize.x, displaySize.y) / density;
|
|
return minSide <= 700;
|
|
}
|
|
|
|
public static int getMinTabletSide() {
|
|
if (!isSmallTablet()) {
|
|
int smallSide = Math.min(displaySize.x, displaySize.y);
|
|
int leftSide = smallSide * 35 / 100;
|
|
if (leftSide < dp(320)) {
|
|
leftSide = dp(320);
|
|
}
|
|
return smallSide - leftSide;
|
|
} else {
|
|
int smallSide = Math.min(displaySize.x, displaySize.y);
|
|
int maxSide = Math.max(displaySize.x, displaySize.y);
|
|
int leftSide = maxSide * 35 / 100;
|
|
if (leftSide < dp(320)) {
|
|
leftSide = dp(320);
|
|
}
|
|
return Math.min(smallSide, maxSide - leftSide);
|
|
}
|
|
}
|
|
|
|
public static int getPhotoSize() {
|
|
if (photoSize == null) {
|
|
if (Build.VERSION.SDK_INT >= 16) {
|
|
photoSize = 1280;
|
|
} else {
|
|
photoSize = 800;
|
|
}
|
|
}
|
|
return photoSize;
|
|
}
|
|
|
|
public static void clearCursorDrawable(EditText editText) {
|
|
if (editText == null) {
|
|
return;
|
|
}
|
|
try {
|
|
Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
|
|
mCursorDrawableRes.setAccessible(true);
|
|
mCursorDrawableRes.setInt(editText, 0);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
private static Intent createShortcutIntent(int did, Bitmap bitmap) {
|
|
Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class);
|
|
|
|
MrChat mrChat = MrMailbox.getChat(did);
|
|
if( mrChat.getId() == 0 ) { return null; }
|
|
String name = mrChat.getName();
|
|
|
|
shortcutIntent.setAction("com.b44t.messenger.openchat" + did);
|
|
shortcutIntent.addFlags(0x4000000);
|
|
|
|
Intent addIntent = new Intent();
|
|
addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
|
|
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
|
|
addIntent.putExtra("duplicate", false);
|
|
if( bitmap != null ) {
|
|
addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
|
|
}
|
|
return addIntent;
|
|
}
|
|
|
|
public static void installShortcut(int did, Bitmap bitmap) {
|
|
try {
|
|
Intent addIntent = createShortcutIntent(did, bitmap);
|
|
addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
|
|
ApplicationLoader.applicationContext.sendBroadcast(addIntent);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
/* -- uninstalling shortcuts would be needed if a chat is deleted. However,
|
|
-- com.b44t.messenger.openchatNNN just opens the overview in this case, which is okay for the moment.
|
|
-- uninstalling shortcuts would required the permission com.android.launcher.permission.UNINSTALL_SHORTCUT
|
|
public static void uninstallShortcut(int did) {
|
|
try {
|
|
Intent addIntent = createShortcutIntent(did, null);
|
|
addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
|
|
ApplicationLoader.applicationContext.sendBroadcast(addIntent);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}*/
|
|
|
|
public static int getViewInset(View view) {
|
|
if (view == null || Build.VERSION.SDK_INT < 21 || view.getHeight() == AndroidUtilities.displaySize.y || view.getHeight() == AndroidUtilities.displaySize.y - statusBarHeight) {
|
|
return 0;
|
|
}
|
|
try {
|
|
Field mAttachInfoField = View.class.getDeclaredField("mAttachInfo");
|
|
mAttachInfoField.setAccessible(true);
|
|
Object mAttachInfo = mAttachInfoField.get(view);
|
|
if (mAttachInfo != null) {
|
|
Field mStableInsetsField = mAttachInfo.getClass().getDeclaredField("mStableInsets");
|
|
mStableInsetsField.setAccessible(true);
|
|
Rect insets = (Rect)mStableInsetsField.get(mAttachInfo);
|
|
return insets.bottom;
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public static Point getRealScreenSize() {
|
|
Point size = new Point();
|
|
try {
|
|
WindowManager windowManager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE);
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
windowManager.getDefaultDisplay().getRealSize(size);
|
|
} else {
|
|
try {
|
|
Method mGetRawW = Display.class.getMethod("getRawWidth");
|
|
Method mGetRawH = Display.class.getMethod("getRawHeight");
|
|
size.set((Integer) mGetRawW.invoke(windowManager.getDefaultDisplay()), (Integer) mGetRawH.invoke(windowManager.getDefaultDisplay()));
|
|
} catch (Exception e) {
|
|
size.set(windowManager.getDefaultDisplay().getWidth(), windowManager.getDefaultDisplay().getHeight());
|
|
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return size;
|
|
}
|
|
|
|
public static CharSequence getTrimmedString(CharSequence src) {
|
|
if (src == null || src.length() == 0) {
|
|
return src;
|
|
}
|
|
while (src.length() > 0 && (src.charAt(0) == '\n' || src.charAt(0) == ' ')) {
|
|
src = src.subSequence(1, src.length());
|
|
}
|
|
while (src.length() > 0 && (src.charAt(src.length() - 1) == '\n' || src.charAt(src.length() - 1) == ' ')) {
|
|
src = src.subSequence(0, src.length() - 1);
|
|
}
|
|
return src;
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
public static void clearDrawableAnimation(View view) {
|
|
if (Build.VERSION.SDK_INT < 21 || view == null) {
|
|
return;
|
|
}
|
|
Drawable drawable;
|
|
if (view instanceof ListView) {
|
|
drawable = ((ListView) view).getSelector();
|
|
if (drawable != null) {
|
|
drawable.setState(StateSet.NOTHING);
|
|
}
|
|
} else {
|
|
drawable = view.getBackground();
|
|
if (drawable != null) {
|
|
drawable.setState(StateSet.NOTHING);
|
|
drawable.jumpToCurrentState();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static final int FLAG_TAG_BR = 1;
|
|
public static final int FLAG_TAG_BOLD = 2;
|
|
public static final int FLAG_TAG_COLOR = 4;
|
|
public static final int FLAG_TAG_ALL = FLAG_TAG_BR | FLAG_TAG_BOLD | FLAG_TAG_COLOR;
|
|
|
|
public static SpannableStringBuilder replaceTags(String str) {
|
|
return replaceTags(str, FLAG_TAG_ALL);
|
|
}
|
|
|
|
public static SpannableStringBuilder replaceTags(String str, int flag) {
|
|
try {
|
|
int start;
|
|
int end;
|
|
StringBuilder stringBuilder = new StringBuilder(str);
|
|
if ((flag & FLAG_TAG_BR) != 0) {
|
|
while ((start = stringBuilder.indexOf("<br>")) != -1) {
|
|
stringBuilder.replace(start, start + 4, "\n");
|
|
}
|
|
while ((start = stringBuilder.indexOf("<br/>")) != -1) {
|
|
stringBuilder.replace(start, start + 5, "\n");
|
|
}
|
|
}
|
|
ArrayList<Integer> bolds = new ArrayList<>();
|
|
if ((flag & FLAG_TAG_BOLD) != 0) {
|
|
while ((start = stringBuilder.indexOf("<b>")) != -1) {
|
|
stringBuilder.replace(start, start + 3, "");
|
|
end = stringBuilder.indexOf("</b>");
|
|
if (end == -1) {
|
|
end = stringBuilder.indexOf("<b>");
|
|
}
|
|
stringBuilder.replace(end, end + 4, "");
|
|
bolds.add(start);
|
|
bolds.add(end);
|
|
}
|
|
}
|
|
ArrayList<Integer> colors = new ArrayList<>();
|
|
if ((flag & FLAG_TAG_COLOR) != 0) { // used eg. by the message preview when following a mailto:-link
|
|
while ((start = stringBuilder.indexOf("<c#")) != -1) {
|
|
stringBuilder.replace(start, start + 2, "");
|
|
end = stringBuilder.indexOf(">", start);
|
|
int color = Color.parseColor(stringBuilder.substring(start, end));
|
|
stringBuilder.replace(start, end + 1, "");
|
|
end = stringBuilder.indexOf("</c>");
|
|
stringBuilder.replace(end, end + 4, "");
|
|
colors.add(start);
|
|
colors.add(end);
|
|
colors.add(color);
|
|
}
|
|
}
|
|
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(stringBuilder);
|
|
for (int a = 0; a < bolds.size() / 2; a++) {
|
|
spannableStringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT_BOLD), bolds.get(a * 2), bolds.get(a * 2 + 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
}
|
|
for (int a = 0; a < colors.size() / 3; a++) {
|
|
spannableStringBuilder.setSpan(new ForegroundColorSpan(colors.get(a * 3 + 2)), colors.get(a * 3), colors.get(a * 3 + 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
}
|
|
return spannableStringBuilder;
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return new SpannableStringBuilder(str);
|
|
}
|
|
|
|
public static boolean needShowPasscode(boolean reset) {
|
|
boolean wasInBackground = ForegroundDetector.getInstance().isWasInBackground(reset);
|
|
if (reset) {
|
|
ForegroundDetector.getInstance().resetBackgroundVar();
|
|
}
|
|
return UserConfig.passcodeHash.length() > 0 && wasInBackground &&
|
|
(UserConfig.appLocked || UserConfig.autoLockIn != 0 && UserConfig.lastPauseTime != 0 && !UserConfig.appLocked && (UserConfig.lastPauseTime + UserConfig.autoLockIn) <= MrMailbox.getCurrentTime());
|
|
}
|
|
|
|
public static void shakeView(final View view, final float x, final int num) {
|
|
if (num == 6) {
|
|
view.setTranslationX(0);
|
|
return;
|
|
}
|
|
AnimatorSet animatorSet = new AnimatorSet();
|
|
animatorSet.playTogether(ObjectAnimator.ofFloat(view, "translationX", AndroidUtilities.dp(x)));
|
|
animatorSet.setDuration(50);
|
|
animatorSet.addListener(new AnimatorListenerAdapterProxy() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
shakeView(view, num == 5 ? 0 : -x, num + 1);
|
|
}
|
|
});
|
|
animatorSet.start();
|
|
}
|
|
|
|
public static void showDoneHint(Context context)
|
|
{
|
|
// this hint should be shown when an action is complete and not directly visible on the screen,
|
|
// eg. copy-to-clipboard or item deletion in a long list
|
|
Toast.makeText(context, ApplicationLoader.applicationContext.getString(R.string.DoneHint), Toast.LENGTH_SHORT).show();
|
|
}
|
|
|
|
public static void showErrorHint(Context context)
|
|
{
|
|
// this hint should be shown on errors that normally should not happen.
|
|
showHint(context, ApplicationLoader.applicationContext.getString(R.string.ErrorHint));
|
|
}
|
|
|
|
public static Toast showHint(Context context, String text)
|
|
{
|
|
Toast t = Toast.makeText(context, text, Toast.LENGTH_LONG);
|
|
t.show();
|
|
return t;
|
|
}
|
|
|
|
public static void addToClipboard(CharSequence str) {
|
|
try {
|
|
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
android.content.ClipData clip = android.content.ClipData.newPlainText("label", str);
|
|
clipboard.setPrimaryClip(clip);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
public static void addMediaToGallery(String fromPath) {
|
|
if (fromPath == null) {
|
|
return;
|
|
}
|
|
File f = new File(fromPath);
|
|
Uri contentUri = Uri.fromFile(f);
|
|
addMediaToGallery(contentUri);
|
|
}
|
|
|
|
public static void addMediaToGallery(Uri uri) {
|
|
if (uri == null) {
|
|
return;
|
|
}
|
|
try {
|
|
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
|
mediaScanIntent.setData(uri);
|
|
ApplicationLoader.applicationContext.sendBroadcast(mediaScanIntent);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
|
|
private static File getAlbumDir() {
|
|
if (Build.VERSION.SDK_INT >= 23 && ApplicationLoader.applicationContext.checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
return FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE);
|
|
}
|
|
File storageDir = null;
|
|
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
|
storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Delta Chat");
|
|
if (!storageDir.mkdirs()) {
|
|
if (!storageDir.exists()){
|
|
Log.w("DeltaChat", "failed to create directory");
|
|
return null;
|
|
}
|
|
}
|
|
} else {
|
|
Log.w("DeltaChat", "External storage is not mounted READ/WRITE.");
|
|
}
|
|
|
|
return storageDir;
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
public static String getPath(final Uri uri) {
|
|
try {
|
|
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
|
if (isKitKat && DocumentsContract.isDocumentUri(ApplicationLoader.applicationContext, uri)) {
|
|
if (isExternalStorageDocument(uri)) {
|
|
final String docId = DocumentsContract.getDocumentId(uri);
|
|
final String[] split = docId.split(":");
|
|
final String type = split[0];
|
|
if ("primary".equalsIgnoreCase(type)) {
|
|
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
|
}
|
|
} else if (isDownloadsDocument(uri)) {
|
|
final String id = DocumentsContract.getDocumentId(uri);
|
|
final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
|
return getDataColumn(ApplicationLoader.applicationContext, contentUri, null, null);
|
|
} else if (isMediaDocument(uri)) {
|
|
final String docId = DocumentsContract.getDocumentId(uri);
|
|
final String[] split = docId.split(":");
|
|
final String type = split[0];
|
|
|
|
Uri contentUri = null;
|
|
switch (type) {
|
|
case "image":
|
|
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
|
break;
|
|
case "video":
|
|
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
|
break;
|
|
case "audio":
|
|
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
|
break;
|
|
}
|
|
|
|
final String selection = "_id=?";
|
|
final String[] selectionArgs = new String[] {
|
|
split[1]
|
|
};
|
|
|
|
return getDataColumn(ApplicationLoader.applicationContext, contentUri, selection, selectionArgs);
|
|
}
|
|
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
|
return getDataColumn(ApplicationLoader.applicationContext, uri, null, null);
|
|
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
|
return uri.getPath();
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
|
|
|
|
Cursor cursor = null;
|
|
final String column = "_data";
|
|
final String[] projection = {
|
|
column
|
|
};
|
|
|
|
try {
|
|
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
|
|
if (cursor != null && cursor.moveToFirst()) {
|
|
final int column_index = cursor.getColumnIndexOrThrow(column);
|
|
String value = cursor.getString(column_index);
|
|
if (value.startsWith("content://") || !value.startsWith("/") && !value.startsWith("file://")) {
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
} finally {
|
|
if (cursor != null) {
|
|
cursor.close();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static boolean isExternalStorageDocument(Uri uri) {
|
|
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
|
}
|
|
|
|
public static boolean isDownloadsDocument(Uri uri) {
|
|
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
|
}
|
|
|
|
public static boolean isMediaDocument(Uri uri) {
|
|
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
|
}
|
|
|
|
public static File generatePicturePath() {
|
|
try {
|
|
File storageDir = getAlbumDir();
|
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
|
return new File(storageDir, "IMG_" + timeStamp + ".jpg");
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static File generateVideoPath() {
|
|
try {
|
|
File storageDir = getAlbumDir();
|
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
|
return new File(storageDir, "VID_" + timeStamp + ".mp4");
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static String formatFileSize(long bytes) {
|
|
// We use the SI-prefix with the usual base as defined in the 1990's.
|
|
// The other option would be to show MiB and KiB, however, these abbreviations are not very usual.
|
|
if (bytes < 1000) {
|
|
return String.format("%d Byte", bytes);
|
|
} else if (bytes < (1000 * 1000)-500) { // 999499 Byte=999 KB, 999500 Byte=1,0 MB
|
|
return String.format("%.0f KB", bytes / 1000.0f);
|
|
} else {
|
|
return String.format("%.1f MB", bytes / 1000.0f / 1000.0f);
|
|
}
|
|
}
|
|
|
|
public static byte[] decodeQuotedPrintable(final byte[] bytes) {
|
|
if (bytes == null) {
|
|
return null;
|
|
}
|
|
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
|
for (int i = 0; i < bytes.length; i++) {
|
|
final int b = bytes[i];
|
|
if (b == '=') {
|
|
try {
|
|
final int u = Character.digit((char) bytes[++i], 16);
|
|
final int l = Character.digit((char) bytes[++i], 16);
|
|
buffer.write((char) ((u << 4) + l));
|
|
} catch (Exception e) {
|
|
|
|
return null;
|
|
}
|
|
} else {
|
|
buffer.write(b);
|
|
}
|
|
}
|
|
byte[] array = buffer.toByteArray();
|
|
try {
|
|
buffer.close();
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
return array;
|
|
}
|
|
|
|
public static boolean copyFile(InputStream sourceFile, File destFile) throws IOException {
|
|
OutputStream out = new FileOutputStream(destFile);
|
|
byte[] buf = new byte[4096];
|
|
int len;
|
|
while ((len = sourceFile.read(buf)) > 0) {
|
|
Thread.yield();
|
|
out.write(buf, 0, len);
|
|
}
|
|
out.close();
|
|
return true;
|
|
}
|
|
|
|
public static boolean copyFile(File sourceFile, File destFile) throws IOException {
|
|
if (!destFile.exists()) {
|
|
destFile.createNewFile();
|
|
}
|
|
FileInputStream source = null;
|
|
FileOutputStream destination = null;
|
|
try {
|
|
source = new FileInputStream(sourceFile);
|
|
destination = new FileOutputStream(destFile);
|
|
destination.getChannel().transferFrom(source.getChannel(), 0, source.getChannel().size());
|
|
} catch (Exception e) {
|
|
|
|
return false;
|
|
} finally {
|
|
if (source != null) {
|
|
source.close();
|
|
}
|
|
if (destination != null) {
|
|
destination.close();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* open, view, download files
|
|
**********************************************************************************************/
|
|
|
|
public static String getMimetype(String fileName, String def)
|
|
{
|
|
String mimeType = "application/octet-stream";
|
|
try {
|
|
MimeTypeMap mimeMap = MimeTypeMap.getSingleton();
|
|
int idx = fileName.lastIndexOf('.');
|
|
if (idx != -1) {
|
|
String ext = fileName.substring(idx + 1);
|
|
mimeType = mimeMap.getMimeTypeFromExtension(ext.toLowerCase());
|
|
}
|
|
if (mimeType == null) {
|
|
mimeType = def==null? "application/octet-stream" : def;
|
|
}
|
|
}
|
|
catch(Exception e) {
|
|
}
|
|
return mimeType;
|
|
}
|
|
|
|
public static void openForViewOrShare(Activity activity, int msg_id, String cmd)
|
|
{
|
|
MrMsg msg = MrMailbox.getMsg(msg_id);
|
|
String path = msg.getParam('f', "");
|
|
String mimeType = getMimetype(path, msg.getParam('m', "application/octet-stream"));
|
|
try {
|
|
File file = new File(path);
|
|
if( !file.exists() ) {
|
|
showHint(activity, LocaleController.formatString("FileNotFound", R.string.FileNotFound, path));
|
|
return;
|
|
}
|
|
|
|
Uri uri;
|
|
if (path.startsWith(MrMailbox.getBlobdir())) {
|
|
uri = Uri.parse("content://" + BuildConfig.APPLICATION_ID + ".attachments/" + file.getName());
|
|
} else {
|
|
uri = Uri.fromFile(file);
|
|
}
|
|
|
|
if( cmd == Intent.ACTION_VIEW ) {
|
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
intent.setDataAndType(uri, mimeType);
|
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
activity.startActivity(intent);
|
|
}
|
|
else {
|
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
|
intent.setType(mimeType);
|
|
intent.putExtra(Intent.EXTRA_STREAM, uri);
|
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
activity.startActivity(Intent.createChooser(intent, ApplicationLoader.applicationContext.getString(R.string.Share)));
|
|
}
|
|
|
|
}
|
|
catch (Exception e) {
|
|
showHint(activity, LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, mimeType));
|
|
}
|
|
}
|
|
|
|
public static File getFineFilename(File path, String desiredName)
|
|
{
|
|
// get a fine file name by adding a number to the basename and avoid overwrites
|
|
for( int i = 0; i < 1000; i++ ) {
|
|
String testName = desiredName;
|
|
if( i > 0 ) {
|
|
String baseName=desiredName, ext = "";
|
|
int idx = desiredName.lastIndexOf('.');
|
|
if( idx != -1 ) {
|
|
baseName = desiredName.substring(0, idx);
|
|
ext = desiredName.substring(idx);
|
|
}
|
|
testName = String.format("%s-%d%s", baseName, i, ext);
|
|
}
|
|
File pathNFile = new File(path, testName);
|
|
if (!pathNFile.exists()) {
|
|
return pathNFile;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static void saveMessageFileToExt(final Activity context, int msg_id)
|
|
{
|
|
if (Build.VERSION.SDK_INT >= 23 && context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
context.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4);
|
|
return;
|
|
}
|
|
|
|
MrMsg msg = MrMailbox.getMsg(msg_id);
|
|
final int msg_type = msg.getType();
|
|
String msg_file_path = msg.getParam('f', "");
|
|
final String msg_mime = msg.getParam('m', "application/octet-stream");
|
|
final String msg_file_name = msg.getFilename();
|
|
final File sourceFile = new File(msg_file_path);
|
|
if( !sourceFile.exists() ) {
|
|
showHint(context, LocaleController.formatString("FileNotFound", R.string.FileNotFound, msg_file_path));
|
|
return;
|
|
}
|
|
|
|
final Toast waitingHint = showHint(context, ApplicationLoader.applicationContext.getString(R.string.OneMoment));
|
|
|
|
new Thread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
boolean allOkay = false;
|
|
|
|
try {
|
|
// get destination path
|
|
File destPath = null;
|
|
boolean add_to_download_manager = false;
|
|
if (msg_type == MrMsg.MR_MSG_IMAGE || msg_type == MrMsg.MR_MSG_VIDEO) {
|
|
destPath = AndroidUtilities.getAlbumDir();
|
|
} else if (msg_type == MrMsg.MR_MSG_FILE) {
|
|
destPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
|
add_to_download_manager = true;
|
|
} else if (msg_type == MrMsg.MR_MSG_AUDIO || msg_type == MrMsg.MR_MSG_VOICE ) {
|
|
destPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
|
|
}
|
|
|
|
// create destination path (must be done, see documentation for getExternalStoragePublicDirectory())
|
|
destPath.mkdirs();
|
|
|
|
File destPathNFile = getFineFilename(destPath, msg_file_name);
|
|
if( destPathNFile != null )
|
|
{
|
|
destPathNFile.createNewFile();
|
|
boolean copied = true;
|
|
FileChannel source = null;
|
|
FileChannel destination = null;
|
|
try {
|
|
source = new FileInputStream(sourceFile).getChannel();
|
|
destination = new FileOutputStream(destPathNFile).getChannel();
|
|
long size = source.size();
|
|
for (long a = 0; a < size; a += 4096) {
|
|
destination.transferFrom(source, a, Math.min(4096, size - a));
|
|
}
|
|
} catch (Exception e) {
|
|
copied = false;
|
|
} finally {
|
|
if (source != null) {
|
|
source.close();
|
|
}
|
|
if (destination != null) {
|
|
destination.close();
|
|
}
|
|
}
|
|
|
|
if (copied) {
|
|
if ( add_to_download_manager ) {
|
|
DownloadManager downloadManager = (DownloadManager) ApplicationLoader.applicationContext.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
downloadManager.addCompletedDownload(destPathNFile.getName(), destPathNFile.getName(), false, getMimetype(msg_file_name, msg_mime), destPathNFile.getAbsolutePath(), destPathNFile.length(), true);
|
|
} else {
|
|
addMediaToGallery(Uri.fromFile(destPathNFile));
|
|
}
|
|
allOkay = true;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
;
|
|
}
|
|
|
|
final boolean allOkayFinal = allOkay;
|
|
AndroidUtilities.runOnUIThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
waitingHint.cancel();
|
|
if( allOkayFinal ) {
|
|
showDoneHint(context);
|
|
}
|
|
else {
|
|
showHint(context, ApplicationLoader.applicationContext.getString(R.string.AccessError));
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
}
|