/******************************************************************************* * * 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("
")) != -1) { stringBuilder.replace(start, start + 4, "\n"); } while ((start = stringBuilder.indexOf("
")) != -1) { stringBuilder.replace(start, start + 5, "\n"); } } ArrayList bolds = new ArrayList<>(); if ((flag & FLAG_TAG_BOLD) != 0) { while ((start = stringBuilder.indexOf("")) != -1) { stringBuilder.replace(start, start + 3, ""); end = stringBuilder.indexOf(""); if (end == -1) { end = stringBuilder.indexOf(""); } stringBuilder.replace(end, end + 4, ""); bolds.add(start); bolds.add(end); } } ArrayList colors = new ArrayList<>(); if ((flag & FLAG_TAG_COLOR) != 0) { // used eg. by the message preview when following a mailto:-link while ((start = stringBuilder.indexOf("", start); int color = Color.parseColor(stringBuilder.substring(start, end)); stringBuilder.replace(start, end + 1, ""); end = stringBuilder.indexOf(""); 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(); } }