/* * This is the source code of Telegram for Android v. 5.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * * Copyright Nikolai Kudashov, 2013-2018. */ package org.telegram.messenger; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.PowerManager; import android.os.SystemClock; import android.provider.CallLog; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.Settings; import androidx.core.content.FileProvider; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.ViewPager; import android.telephony.TelephonyManager; import android.text.Layout; import android.text.Selection; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.URLSpan; import android.text.util.Linkify; import android.util.DisplayMetrics; import android.util.Log; import android.util.StateSet; import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.Display; import android.view.MotionEvent; import android.view.Gravity; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.webkit.MimeTypeMap; import android.widget.EdgeEffect; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ScrollView; import android.widget.TextView; import com.android.internal.telephony.ITelephony; import com.google.android.gms.auth.api.phone.SmsRetriever; import com.google.android.gms.auth.api.phone.SmsRetrieverClient; import com.google.android.gms.tasks.Task; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.browser.Browser; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.TextDetailSettingsCell; import org.telegram.ui.Components.BackgroundGradientDrawable; import org.telegram.ui.Components.ForegroundColorSpanThemable; import org.telegram.ui.Components.ForegroundDetector; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PickerBottomLayout; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.ShareAlert; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.ThemePreviewActivity; import org.telegram.ui.WallpapersListActivity; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.IDN; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; public class AndroidUtilities { private static final Hashtable typefaceCache = new Hashtable<>(); private static int prevOrientation = -10; private static boolean waitingForSms = false; private static boolean waitingForCall = false; private static final Object smsLock = new Object(); private static final Object callLock = new Object(); public static int statusBarHeight = 0; public static boolean firstConfigurationWas; public static float density = 1; public static Point displaySize = new Point(); public static float screenRefreshRate = 60; public static int roundMessageSize; public static int roundMessageInset; public static boolean incorrectDisplaySizeFix; public static Integer photoSize = null; public static DisplayMetrics displayMetrics = new DisplayMetrics(); public static int leftBaseline; public static boolean usingHardwareInput; public static boolean isInMultiwindow; public static DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); public static AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator(); public static OvershootInterpolator overshootInterpolator = new OvershootInterpolator(); private static Boolean isTablet = null; private static int adjustOwnerClassGuid = 0; private static Paint roundPaint; private static RectF bitmapRect; public static final RectF rectTmp = new RectF(); public static Pattern WEB_URL = null; 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 + ")"); WEB_URL = Pattern.compile( "((?:(http|https|Http|Https|ton|tg):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" + "(?:" + DOMAIN_NAME + ")" + "(?:\\:\\d{1,5})?)" // plus option port number + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?" + "(?:\\b|$)"); } catch (Exception e) { FileLog.e(e); } } static { leftBaseline = isTablet() ? 80 : 72; checkDisplaySize(ApplicationLoader.applicationContext, null); } private static int[] documentIcons = { R.drawable.media_doc_blue, R.drawable.media_doc_green, R.drawable.media_doc_red, R.drawable.media_doc_yellow }; private static int[] documentMediaIcons = { R.drawable.media_doc_blue_b, R.drawable.media_doc_green_b, R.drawable.media_doc_red_b, R.drawable.media_doc_yellow_b }; private static boolean containsUnsupportedCharacters(String text) { if (text.contains("\u202C")) { return true; } if (text.contains("\u202D")) { return true; } if (text.contains("\u202E")) { return true; } return false; } public static CharSequence ellipsizeCenterEnd(CharSequence str, String query, int availableWidth, TextPaint textPaint, int maxSymbols) { try { int lastIndex = str.length(); int startHighlightedIndex = str.toString().toLowerCase().indexOf(query); if (lastIndex > maxSymbols) { str = str.subSequence(Math.max(0, startHighlightedIndex - maxSymbols / 2), Math.min(lastIndex, startHighlightedIndex + maxSymbols / 2)); startHighlightedIndex -= Math.max(0, startHighlightedIndex - maxSymbols / 2); lastIndex = str.length(); } StaticLayout staticLayout = new StaticLayout(str, textPaint, Integer.MAX_VALUE, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); float endOfTextX = staticLayout.getLineWidth(0); if (endOfTextX + textPaint.measureText("...") < availableWidth) { return str; } int i = startHighlightedIndex + 1; while (i < str.length() - 1 && !Character.isWhitespace(str.charAt(i))) { i++; } int endHighlightedIndex = i; float endOfHighlight = staticLayout.getPrimaryHorizontal(endHighlightedIndex); if (staticLayout.isRtlCharAt(endHighlightedIndex)) { endOfHighlight = endOfTextX - endOfHighlight; } if (endOfHighlight < availableWidth) { return str; } float x = endOfHighlight - availableWidth + textPaint.measureText("...") * 2 + availableWidth * 0.1f; if (str.length() - endHighlightedIndex > 20) { x += availableWidth * 0.1f; } if (x > 0) { int charOf = staticLayout.getOffsetForHorizontal(0, x); int k = 0; if (charOf > str.length() - 1) { charOf = str.length() - 1; } while (!Character.isWhitespace(str.charAt(charOf)) && k < 10) { k++; charOf++; if (charOf > str.length() - 1) { charOf = staticLayout.getOffsetForHorizontal(0, x); break; } } CharSequence sub; if (k >= 10) { x = staticLayout.getPrimaryHorizontal(startHighlightedIndex + 1) - availableWidth * 0.3f; sub = str.subSequence(staticLayout.getOffsetForHorizontal(0, x), str.length()); } else { if (charOf > 0 && charOf < str.length() - 2 && Character.isWhitespace(str.charAt(charOf))) { charOf++; } sub = str.subSequence(charOf, str.length()); } return SpannableStringBuilder.valueOf("...").append(sub); } } catch (Exception e) { FileLog.e(e); } return str; } public static CharSequence highlightText(CharSequence str, ArrayList query) { if (query == null) { return null; } int emptyCount = 0; for (int i = 0; i < query.size(); i++) { CharSequence strTmp = highlightText(str, query.get(i)); if (strTmp != null) { str = strTmp; } else { emptyCount++; } } if (emptyCount == query.size()) { return null; } return str; } public static CharSequence highlightText(CharSequence str, String query) { if (TextUtils.isEmpty(query) || TextUtils.isEmpty(str)) { return null; } String s = str.toString().toLowerCase(); SpannableStringBuilder spannableStringBuilder = SpannableStringBuilder.valueOf(str); int i = s.indexOf(query); while (i >= 0) { try { spannableStringBuilder.setSpan(new ForegroundColorSpanThemable(Theme.key_windowBackgroundWhiteBlueText4), i, Math.min(i + query.length(), str.length()), 0); } catch (Exception e) { FileLog.e(e); } i = s.indexOf(query, i + 1); } return spannableStringBuilder; } public static Activity findActivity(Context context) { if (context instanceof Activity) { return (Activity) context; } if (context instanceof ContextThemeWrapper) { return findActivity(((ContextThemeWrapper) context).getBaseContext()); } return null; } private static class LinkSpec { String url; int start; int end; } private static Boolean standaloneApp; public static boolean isStandaloneApp() { if (standaloneApp == null) { standaloneApp = "org.telegram.messenger.web".equals(ApplicationLoader.applicationContext.getPackageName()); } return standaloneApp; } private static Boolean betaApp; public static boolean isBetaApp() { if (betaApp == null) { betaApp = "org.telegram.messenger.beta".equals(ApplicationLoader.applicationContext.getPackageName()); } return betaApp; } private static String makeUrl(String url, String[] prefixes, Matcher matcher) { boolean hasPrefix = false; for (int i = 0; i < prefixes.length; i++) { if (url.regionMatches(true, 0, prefixes[i], 0, prefixes[i].length())) { hasPrefix = true; if (!url.regionMatches(false, 0, prefixes[i], 0, prefixes[i].length())) { url = prefixes[i] + url.substring(prefixes[i].length()); } break; } } if (!hasPrefix && prefixes.length > 0) { url = prefixes[0] + url; } return url; } private static void gatherLinks(ArrayList links, Spannable s, Pattern pattern, String[] schemes, Linkify.MatchFilter matchFilter, boolean internalOnly) { if (TextUtils.indexOf(s, '─') >= 0) { s = new SpannableStringBuilder(s.toString().replace('─', ' ')); } Matcher m = pattern.matcher(s); while (m.find()) { int start = m.start(); int end = m.end(); if (matchFilter == null || matchFilter.acceptMatch(s, start, end)) { LinkSpec spec = new LinkSpec(); String url = makeUrl(m.group(0), schemes, m); if (internalOnly && !Browser.isInternalUrl(url, true, null)) { continue; } spec.url = url; spec.start = start; spec.end = end; links.add(spec); } } } public static final Linkify.MatchFilter sUrlMatchFilter = (s, start, end) -> { if (start == 0) { return true; } if (s.charAt(start - 1) == '@') { return false; } return true; }; public static boolean addLinks(Spannable text, int mask) { return addLinks(text, mask, false); } public static boolean addLinks(Spannable text, int mask, boolean internalOnly) { if (text == null || containsUnsupportedCharacters(text.toString()) || mask == 0) { return false; } final URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class); for (int i = old.length - 1; i >= 0; i--) { text.removeSpan(old[i]); } final ArrayList links = new ArrayList<>(); if (!internalOnly && (mask & Linkify.PHONE_NUMBERS) != 0) { Linkify.addLinks(text, Linkify.PHONE_NUMBERS); } if ((mask & Linkify.WEB_URLS) != 0) { gatherLinks(links, text, LinkifyPort.WEB_URL, new String[]{"http://", "https://", "ton://", "tg://"}, sUrlMatchFilter, internalOnly); } pruneOverlaps(links); if (links.size() == 0) { return false; } for (int a = 0, N = links.size(); a < N; a++) { LinkSpec link = links.get(a); URLSpan[] oldSpans = text.getSpans(link.start, link.end, URLSpan.class); if (oldSpans != null && oldSpans.length > 0) { for (int b = 0; b < oldSpans.length; b++) { text.removeSpan(oldSpans[b]); } } text.setSpan(new URLSpan(link.url), link.start, link.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return true; } private static void pruneOverlaps(ArrayList links) { Comparator c = (a, b) -> { if (a.start < b.start) { return -1; } if (a.start > b.start) { return 1; } if (a.end < b.end) { return 1; } if (a.end > b.end) { return -1; } return 0; }; Collections.sort(links, c); int len = links.size(); int i = 0; while (i < len - 1) { LinkSpec a = links.get(i); LinkSpec b = links.get(i + 1); int remove = -1; if ((a.start <= b.start) && (a.end > b.start)) { if (b.end <= a.end) { remove = i + 1; } else if ((a.end - a.start) > (b.end - b.start)) { remove = i + 1; } else if ((a.end - a.start) < (b.end - b.start)) { remove = i; } if (remove != -1) { links.remove(remove); len--; continue; } } i++; } } public static void fillStatusBarHeight(Context context) { if (context == null || AndroidUtilities.statusBarHeight > 0) { return; } int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { AndroidUtilities.statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); } } public static int getThumbForNameOrMime(String name, String mime, boolean media) { if (name != null && name.length() != 0) { int color = -1; if (name.contains(".doc") || name.contains(".txt") || name.contains(".psd")) { color = 0; } else if (name.contains(".xls") || name.contains(".csv")) { color = 1; } else if (name.contains(".pdf") || name.contains(".ppt") || name.contains(".key")) { color = 2; } else if (name.contains(".zip") || name.contains(".rar") || name.contains(".ai") || name.contains(".mp3") || name.contains(".mov") || name.contains(".avi")) { color = 3; } if (color == -1) { int idx; String ext = (idx = name.lastIndexOf('.')) == -1 ? "" : name.substring(idx + 1); if (ext.length() != 0) { color = ext.charAt(0) % documentIcons.length; } else { color = name.charAt(0) % documentIcons.length; } } return media ? documentMediaIcons[color] : documentIcons[color]; } return media ? documentMediaIcons[0] : documentIcons[0]; } public static int calcBitmapColor(Bitmap bitmap) { try { Bitmap b = Bitmaps.createScaledBitmap(bitmap, 1, 1, true); if (b != null) { int bitmapColor = b.getPixel(0, 0); if (bitmap != b) { b.recycle(); } return bitmapColor; } } catch (Exception e) { FileLog.e(e); } return 0; } public static int[] calcDrawableColor(Drawable drawable) { int bitmapColor = 0xff000000; int[] result = new int[4]; try { if (drawable instanceof BitmapDrawable) { Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); bitmapColor = calcBitmapColor(bitmap); } else if (drawable instanceof ColorDrawable) { bitmapColor = ((ColorDrawable) drawable).getColor(); } else if (drawable instanceof BackgroundGradientDrawable) { int[] colors = ((BackgroundGradientDrawable) drawable).getColorsList(); if (colors != null) { if (colors.length > 1) { bitmapColor = getAverageColor(colors[0], colors[1]); } else if (colors.length > 0) { bitmapColor = colors[0]; } } } } catch (Exception e) { FileLog.e(e); } double[] hsv = rgbToHsv((bitmapColor >> 16) & 0xff, (bitmapColor >> 8) & 0xff, bitmapColor & 0xff); hsv[1] = Math.min(1.0, hsv[1] + 0.05 + 0.1 * (1.0 - hsv[1])); double v = Math.max(0, hsv[2] * 0.65); int[] rgb = hsvToRgb(hsv[0], hsv[1], v); result[0] = Color.argb(0x66, rgb[0], rgb[1], rgb[2]); result[1] = Color.argb(0x88, rgb[0], rgb[1], rgb[2]); double v2 = Math.max(0, hsv[2] * 0.72); rgb = hsvToRgb(hsv[0], hsv[1], v2); result[2] = Color.argb(0x66, rgb[0], rgb[1], rgb[2]); result[3] = Color.argb(0x88, rgb[0], rgb[1], rgb[2]); return result; } public static double[] rgbToHsv(int color) { return rgbToHsv(Color.red(color), Color.green(color), Color.blue(color)); } public static double[] rgbToHsv(int r, int g, int b) { double rf = r / 255.0; double gf = g / 255.0; double bf = b / 255.0; double max = (rf > gf && rf > bf) ? rf : Math.max(gf, bf); double min = (rf < gf && rf < bf) ? rf : Math.min(gf, bf); double h, s; double d = max - min; s = max == 0 ? 0 : d / max; if (max == min) { h = 0; } else { if (rf > gf && rf > bf) { h = (gf - bf) / d + (gf < bf ? 6 : 0); } else if (gf > bf) { h = (bf - rf) / d + 2; } else { h = (rf - gf) / d + 4; } h /= 6; } return new double[]{h, s, max}; } public static int hsvToColor(double h, double s, double v) { int[] rgb = hsvToRgb(h, s, v); return Color.argb(0xff, rgb[0], rgb[1], rgb[2]); } public static int[] hsvToRgb(double h, double s, double v) { double r = 0, g = 0, b = 0; double i = (int) Math.floor(h * 6); double f = h * 6 - i; double p = v * (1 - s); double q = v * (1 - f * s); double t = v * (1 - (1 - f) * s); switch ((int) i % 6) { case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = t; break; case 3: r = p; g = q; b = v; break; case 4: r = t; g = p; b = v; break; case 5: r = v; g = p; b = q; break; } return new int[]{(int) (r * 255), (int) (g * 255), (int) (b * 255)}; } 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 requestAdjustNothing(Activity activity, int classGuid) { if (activity == null || isTablet()) { return; } activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); adjustOwnerClassGuid = classGuid; } public static void setAdjustResizeToNothing(Activity activity, int classGuid) { if (activity == null || isTablet()) { return; } if (adjustOwnerClassGuid == 0 || adjustOwnerClassGuid == classGuid) { activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); } } 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 void createEmptyFile(File f) { try { if (f.exists()) { return; } FileWriter writer = new FileWriter(f); writer.flush(); writer.close(); } catch (Throwable e) { FileLog.e(e); } } public static boolean isGoogleMapsInstalled(final BaseFragment fragment) { try { ApplicationLoader.applicationContext.getPackageManager().getApplicationInfo("com.google.android.apps.maps", 0); return true; } catch (PackageManager.NameNotFoundException e) { if (fragment.getParentActivity() == null) { return false; } AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); builder.setMessage(LocaleController.getString("InstallGoogleMaps", R.string.InstallGoogleMaps)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), (dialogInterface, i) -> { try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.google.android.apps.maps")); fragment.getParentActivity().startActivityForResult(intent, 500); } catch (Exception e1) { FileLog.e(e1); } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); fragment.showDialog(builder.create()); return false; } } public static int[] toIntArray(List integers) { int[] ret = new int[integers.size()]; for (int i = 0; i < ret.length; i++) { ret[i] = integers.get(i); } return ret; } public static boolean isInternalUri(Uri uri) { return isInternalUri(uri, 0); } public static boolean isInternalUri(int fd) { return isInternalUri(null, fd); } private static boolean isInternalUri(Uri uri, int fd) { String pathString; if (uri != null) { pathString = uri.getPath(); if (pathString == null) { return false; } // Allow sending VoIP logs from cache/voip_logs if (pathString.matches(Pattern.quote(new File(ApplicationLoader.applicationContext.getCacheDir(), "voip_logs").getAbsolutePath()) + "/\\d+\\.log")) { return false; } int tries = 0; while (true) { if (pathString != null && pathString.length() > 4096) { return true; } String newPath; try { newPath = Utilities.readlink(pathString); } catch (Throwable e) { return true; } if (newPath == null || newPath.equals(pathString)) { break; } pathString = newPath; tries++; if (tries >= 10) { return true; } } } else { pathString = ""; int tries = 0; while (true) { if (pathString != null && pathString.length() > 4096) { return true; } String newPath; try { newPath = Utilities.readlinkFd(fd); } catch (Throwable e) { return true; } if (newPath == null || newPath.equals(pathString)) { break; } pathString = newPath; tries++; if (tries >= 10) { return true; } } } if (pathString != null) { try { String path = new File(pathString).getCanonicalPath(); if (path != null) { pathString = path; } } catch (Exception e) { pathString.replace("/./", "/"); //igonre } } if (pathString.endsWith(".attheme")) { return false; } return pathString != null && pathString.toLowerCase().contains("/data/data/" + ApplicationLoader.applicationContext.getPackageName()); } @SuppressLint("WrongConstant") 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) { FileLog.e(e); } } @SuppressLint("WrongConstant") public static void unlockOrientation(Activity activity) { if (activity == null) { return; } try { if (prevOrientation != -10) { activity.setRequestedOrientation(prevOrientation); prevOrientation = -10; } } catch (Exception e) { FileLog.e(e); } } private static class VcardData { String name; ArrayList phones = new ArrayList<>(); StringBuilder vcard = new StringBuilder(); } public static class VcardItem { public ArrayList vcardData = new ArrayList<>(); public String fullData = ""; public int type; public boolean checked = true; public String[] getRawValue() { int idx = fullData.indexOf(':'); if (idx < 0) { return new String[0]; } String valueType = fullData.substring(0, idx); String value = fullData.substring(idx + 1); String nameEncoding = null; String nameCharset = "UTF-8"; String[] params = valueType.split(";"); for (int a = 0; a < params.length; a++) { String[] args2 = params[a].split("="); if (args2.length != 2) { continue; } if (args2[0].equals("CHARSET")) { nameCharset = args2[1]; } else if (args2[0].equals("ENCODING")) { nameEncoding = args2[1]; } } String[] args = value.split(";"); boolean added = false; for (int a = 0; a < args.length; a++) { if (TextUtils.isEmpty(args[a])) { continue; } if (nameEncoding != null && nameEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { byte[] bytes = decodeQuotedPrintable(getStringBytes(args[a])); if (bytes != null && bytes.length != 0) { try { args[a] = new String(bytes, nameCharset); } catch (Exception ignore) { } } } } return args; } public String getValue(boolean format) { StringBuilder result = new StringBuilder(); int idx = fullData.indexOf(':'); if (idx < 0) { return ""; } if (result.length() > 0) { result.append(", "); } String valueType = fullData.substring(0, idx); String value = fullData.substring(idx + 1); String nameEncoding = null; String nameCharset = "UTF-8"; String[] params = valueType.split(";"); for (int a = 0; a < params.length; a++) { String[] args2 = params[a].split("="); if (args2.length != 2) { continue; } if (args2[0].equals("CHARSET")) { nameCharset = args2[1]; } else if (args2[0].equals("ENCODING")) { nameEncoding = args2[1]; } } String[] args = value.split(";"); boolean added = false; for (int a = 0; a < args.length; a++) { if (TextUtils.isEmpty(args[a])) { continue; } if (nameEncoding != null && nameEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { byte[] bytes = decodeQuotedPrintable(getStringBytes(args[a])); if (bytes != null && bytes.length != 0) { try { args[a] = new String(bytes, nameCharset); } catch (Exception ignore) { } } } if (added && result.length() > 0) { result.append(" "); } result.append(args[a]); if (!added) { added = args[a].length() > 0; } } if (format) { if (type == 0) { return PhoneFormat.getInstance().format(result.toString()); } else if (type == 5) { String[] date = result.toString().split("T"); if (date.length > 0) { date = date[0].split("-"); if (date.length == 3) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, Utilities.parseInt(date[0])); calendar.set(Calendar.MONTH, Utilities.parseInt(date[1]) - 1); calendar.set(Calendar.DAY_OF_MONTH, Utilities.parseInt(date[2])); return LocaleController.getInstance().formatterYearMax.format(calendar.getTime()); } } } } return result.toString(); } public String getRawType(boolean first) { int idx = fullData.indexOf(':'); if (idx < 0) { return ""; } String value = fullData.substring(0, idx); if (type == 20) { value = value.substring(2); String[] args = value.split(";"); if (first) { value = args[0]; } else if (args.length > 1) { value = args[args.length - 1]; } else { value = ""; } } else { String[] args = value.split(";"); for (int a = 0; a < args.length; a++) { if (args[a].indexOf('=') >= 0) { continue; } value = args[a]; } return value; } return value; } public String getType() { if (type == 5) { return LocaleController.getString("ContactBirthday", R.string.ContactBirthday); } else if (type == 6) { if ("ORG".equalsIgnoreCase(getRawType(true))) { return LocaleController.getString("ContactJob", R.string.ContactJob); } else { return LocaleController.getString("ContactJobTitle", R.string.ContactJobTitle); } } int idx = fullData.indexOf(':'); if (idx < 0) { return ""; } String value = fullData.substring(0, idx); if (type == 20) { value = value.substring(2); String[] args = value.split(";"); value = args[0]; } else { String[] args = value.split(";"); for (int a = 0; a < args.length; a++) { if (args[a].indexOf('=') >= 0) { continue; } value = args[a]; } if (value.startsWith("X-")) { value = value.substring(2); } switch (value) { case "PREF": value = LocaleController.getString("PhoneMain", R.string.PhoneMain); break; case "HOME": value = LocaleController.getString("PhoneHome", R.string.PhoneHome); break; case "MOBILE": case "CELL": value = LocaleController.getString("PhoneMobile", R.string.PhoneMobile); break; case "OTHER": value = LocaleController.getString("PhoneOther", R.string.PhoneOther); break; case "WORK": value = LocaleController.getString("PhoneWork", R.string.PhoneWork); break; } } value = value.substring(0, 1).toUpperCase() + value.substring(1).toLowerCase(); return value; } } public static byte[] getStringBytes(String src) { try { return src.getBytes("UTF-8"); } catch (Exception ignore) { } return new byte[0]; } public static ArrayList loadVCardFromStream(Uri uri, int currentAccount, boolean asset, ArrayList items, String name) { ArrayList result = null; try { InputStream stream; if (asset) { AssetFileDescriptor fd = ApplicationLoader.applicationContext.getContentResolver().openAssetFileDescriptor(uri, "r"); stream = fd.createInputStream(); } else { ContentResolver cr = ApplicationLoader.applicationContext.getContentResolver(); stream = cr.openInputStream(uri); } ArrayList vcardDatas = new ArrayList<>(); VcardData currentData = null; BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); String line; String originalLine; String pendingLine = null; boolean currentIsPhoto = false; VcardItem currentItem = null; while ((originalLine = line = bufferedReader.readLine()) != null) { if (originalLine.startsWith("PHOTO")) { currentIsPhoto = true; continue; } else { if (originalLine.indexOf(':') >= 0) { currentItem = null; currentIsPhoto = false; if (originalLine.startsWith("BEGIN:VCARD")) { vcardDatas.add(currentData = new VcardData()); currentData.name = name; } else if (originalLine.startsWith("END:VCARD")) { } else if (items != null) { if (originalLine.startsWith("TEL")) { currentItem = new VcardItem(); currentItem.type = 0; } else if (originalLine.startsWith("EMAIL")) { currentItem = new VcardItem(); currentItem.type = 1; } else if (originalLine.startsWith("ADR") || originalLine.startsWith("LABEL") || originalLine.startsWith("GEO")) { currentItem = new VcardItem(); currentItem.type = 2; } else if (originalLine.startsWith("URL")) { currentItem = new VcardItem(); currentItem.type = 3; } else if (originalLine.startsWith("NOTE")) { currentItem = new VcardItem(); currentItem.type = 4; } else if (originalLine.startsWith("BDAY")) { currentItem = new VcardItem(); currentItem.type = 5; } else if (originalLine.startsWith("ORG") || originalLine.startsWith("TITLE") || originalLine.startsWith("ROLE")) { if (currentItem == null) { currentItem = new VcardItem(); currentItem.type = 6; } } else if (originalLine.startsWith("X-ANDROID")) { currentItem = new VcardItem(); currentItem.type = -1; } else if (originalLine.startsWith("X-PHONETIC")) { currentItem = null; } else if (originalLine.startsWith("X-")) { currentItem = new VcardItem(); currentItem.type = 20; } if (currentItem != null && currentItem.type >= 0) { items.add(currentItem); } } } } if (!currentIsPhoto && currentData != null) { if (currentItem == null) { if (currentData.vcard.length() > 0) { currentData.vcard.append('\n'); } currentData.vcard.append(originalLine); } else { currentItem.vcardData.add(originalLine); } } if (pendingLine != null) { pendingLine += line; line = pendingLine; pendingLine = null; } if (line.contains("=QUOTED-PRINTABLE") && line.endsWith("=")) { pendingLine = line.substring(0, line.length() - 1); continue; } if (!currentIsPhoto && currentData != null && currentItem != null) { currentItem.fullData = line; } int idx = line.indexOf(":"); String[] args; if (idx >= 0) { args = new String[]{ line.substring(0, idx), line.substring(idx + 1).trim() }; } else { args = new String[]{line.trim()}; } if (args.length < 2 || currentData == null) { continue; } if (args[0].startsWith("FN") || args[0].startsWith("ORG") && TextUtils.isEmpty(currentData.name)) { String nameEncoding = null; String nameCharset = null; String[] params = args[0].split(";"); for (String param : params) { String[] args2 = param.split("="); if (args2.length != 2) { continue; } if (args2[0].equals("CHARSET")) { nameCharset = args2[1]; } else if (args2[0].equals("ENCODING")) { nameEncoding = args2[1]; } } currentData.name = args[1]; if (nameEncoding != null && nameEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { byte[] bytes = decodeQuotedPrintable(getStringBytes(currentData.name)); if (bytes != null && bytes.length != 0) { String decodedName = new String(bytes, nameCharset); if (decodedName != null) { currentData.name = decodedName; } } } } else if (args[0].startsWith("TEL")) { currentData.phones.add(args[1]); } } try { bufferedReader.close(); stream.close(); } catch (Exception e) { FileLog.e(e); } for (int a = 0; a < vcardDatas.size(); a++) { VcardData vcardData = vcardDatas.get(a); if (vcardData.name != null && !vcardData.phones.isEmpty()) { if (result == null) { result = new ArrayList<>(); } String phoneToUse = vcardData.phones.get(0); for (int b = 0; b < vcardData.phones.size(); b++) { String phone = vcardData.phones.get(b); String sphone = phone.substring(Math.max(0, phone.length() - 7)); if (ContactsController.getInstance(currentAccount).contactsByShortPhone.get(sphone) != null) { phoneToUse = phone; break; } } TLRPC.User user = new TLRPC.TL_userContact_old2(); user.phone = phoneToUse; user.first_name = vcardData.name; user.last_name = ""; user.id = 0; TLRPC.TL_restrictionReason reason = new TLRPC.TL_restrictionReason(); reason.text = vcardData.vcard.toString(); reason.platform = ""; reason.reason = ""; user.restriction_reason.add(reason); result.add(user); } } } catch (Throwable e) { FileLog.e(e); } return result; } public static Typeface getTypeface(String assetPath) { synchronized (typefaceCache) { if (!typefaceCache.containsKey(assetPath)) { try { Typeface t; if (Build.VERSION.SDK_INT >= 26) { Typeface.Builder builder = new Typeface.Builder(ApplicationLoader.applicationContext.getAssets(), assetPath); if (assetPath.contains("medium")) { builder.setWeight(700); } if (assetPath.contains("italic")) { builder.setItalic(true); } t = builder.build(); } else { t = Typeface.createFromAsset(ApplicationLoader.applicationContext.getAssets(), assetPath); } typefaceCache.put(assetPath, t); } catch (Exception e) { if (BuildVars.LOGS_ENABLED) { FileLog.e("Could not get typeface '" + assetPath + "' because " + e.getMessage()); } return null; } } return typefaceCache.get(assetPath); } } public static boolean isWaitingForSms() { boolean value; synchronized (smsLock) { value = waitingForSms; } return value; } public static void setWaitingForSms(boolean value) { synchronized (smsLock) { waitingForSms = value; try { if (waitingForSms) { SmsRetrieverClient client = SmsRetriever.getClient(ApplicationLoader.applicationContext); Task task = client.startSmsRetriever(); task.addOnSuccessListener(aVoid -> { if (BuildVars.DEBUG_VERSION) { FileLog.d("sms listener registered"); } }); } } catch (Throwable e) { FileLog.e(e); } } } public static int getShadowHeight() { if (density >= 4.0f) { return 3; } else if (density >= 2.0f) { return 2; } else { return 1; } } public static boolean isWaitingForCall() { boolean value; synchronized (callLock) { value = waitingForCall; } return value; } private static CallReceiver callReceiver; public static void setWaitingForCall(boolean value) { synchronized (callLock) { try { if (value) { if (callReceiver == null) { final IntentFilter filter = new IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED); ApplicationLoader.applicationContext.registerReceiver(callReceiver = new CallReceiver(), filter); } } else { if (callReceiver != null) { ApplicationLoader.applicationContext.unregisterReceiver(callReceiver); callReceiver = null; } } } catch (Exception ignore) { } waitingForCall = value; } } public static boolean showKeyboard(View view) { if (view == null) { return false; } try { InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); return inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } catch (Exception e) { FileLog.e(e); } return false; } public static String[] getCurrentKeyboardLanguage() { try { InputMethodManager inputManager = (InputMethodManager) ApplicationLoader.applicationContext.getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodSubtype inputMethodSubtype = inputManager.getCurrentInputMethodSubtype(); String locale = null; if (inputMethodSubtype != null) { if (Build.VERSION.SDK_INT >= 24) { locale = inputMethodSubtype.getLanguageTag(); } if (TextUtils.isEmpty(locale)) { locale = inputMethodSubtype.getLocale(); } } else { inputMethodSubtype = inputManager.getLastInputMethodSubtype(); if (inputMethodSubtype != null) { if (Build.VERSION.SDK_INT >= 24) { locale = inputMethodSubtype.getLanguageTag(); } if (TextUtils.isEmpty(locale)) { locale = inputMethodSubtype.getLocale(); } } } if (TextUtils.isEmpty(locale)) { locale = LocaleController.getSystemLocaleStringIso639(); String locale2; LocaleController.LocaleInfo localeInfo = LocaleController.getInstance().getCurrentLocaleInfo(); locale2 = localeInfo.getBaseLangCode(); if (TextUtils.isEmpty(locale2)) { locale2 = localeInfo.getLangCode(); } if (locale.contains(locale2) || locale2.contains(locale)) { if (!locale.contains("en")) { locale2 = "en"; } else { locale2 = null; } } if (!TextUtils.isEmpty(locale2)) { return new String[]{locale.replace('_', '-'), locale2}; } else { return new String[]{locale.replace('_', '-')}; } } else { return new String[]{locale.replace('_', '-')}; } } catch (Exception ignore) { } return new String[]{"en"}; } 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) { FileLog.e(e); } } public static ArrayList getDataDirs() { ArrayList result = null; if (Build.VERSION.SDK_INT >= 19) { File[] dirs = ApplicationLoader.applicationContext.getExternalFilesDirs(null); if (dirs != null) { for (int a = 0; a < dirs.length; a++) { if (dirs[a] == null) { continue; } String path = dirs[a].getAbsolutePath(); if (result == null) { result = new ArrayList<>(); } result.add(dirs[a]); } } } if (result == null) { result = new ArrayList<>(); } if (result.isEmpty()) { result.add(Environment.getExternalStorageDirectory()); } return result; } public static ArrayList getRootDirs() { ArrayList result = null; if (Build.VERSION.SDK_INT >= 19) { File[] dirs = ApplicationLoader.applicationContext.getExternalFilesDirs(null); if (dirs != null) { for (int a = 0; a < dirs.length; a++) { if (dirs[a] == null) { continue; } String path = dirs[a].getAbsolutePath(); int idx = path.indexOf("/Android"); if (idx >= 0) { if (result == null) { result = new ArrayList<>(); } result.add(new File(path.substring(0, idx))); } } } } if (result == null) { result = new ArrayList<>(); } if (result.isEmpty()) { result.add(Environment.getExternalStorageDirectory()); } return result; } public static File getCacheDir() { String state = null; try { state = Environment.getExternalStorageState(); } catch (Exception e) { FileLog.e(e); } if (state == null || state.startsWith(Environment.MEDIA_MOUNTED)) { try { File file; if (Build.VERSION.SDK_INT >= 19) { File[] dirs = ApplicationLoader.applicationContext.getExternalCacheDirs(); file = dirs[0]; if (!TextUtils.isEmpty(SharedConfig.storageCacheDir)) { for (int a = 0; a < dirs.length; a++) { if (dirs[a] != null && dirs[a].getAbsolutePath().startsWith(SharedConfig.storageCacheDir)) { file = dirs[a]; break; } } } } else { file = ApplicationLoader.applicationContext.getExternalCacheDir(); } if (file != null) { return file; } } catch (Exception e) { FileLog.e(e); } } try { File file = ApplicationLoader.applicationContext.getCacheDir(); if (file != null) { return file; } } catch (Exception e) { FileLog.e(e); } return new File(""); } public static int dp(float value) { if (value == 0) { return 0; } return (int) Math.ceil(density * value); } public static int dpr(float value) { if (value == 0) { return 0; } return Math.round(density * value); } public static int dp2(float value) { if (value == 0) { return 0; } return (int) Math.floor(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(Context context, Configuration newConfiguration) { try { float oldDensity = density; density = context.getResources().getDisplayMetrics().density; float newDensity = density; if (firstConfigurationWas && Math.abs(oldDensity - newDensity) > 0.001) { Theme.reloadAllResources(context); } firstConfigurationWas = true; Configuration configuration = newConfiguration; if (configuration == null) { configuration = context.getResources().getConfiguration(); } usingHardwareInput = configuration.keyboard != Configuration.KEYBOARD_NOKEYS && configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); if (manager != null) { Display display = manager.getDefaultDisplay(); if (display != null) { display.getMetrics(displayMetrics); display.getSize(displaySize); screenRefreshRate = display.getRefreshRate(); } } if (configuration.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { int newSize = (int) Math.ceil(configuration.screenWidthDp * density); if (Math.abs(displaySize.x - newSize) > 3) { displaySize.x = newSize; } } if (configuration.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { int newSize = (int) Math.ceil(configuration.screenHeightDp * density); if (Math.abs(displaySize.y - newSize) > 3) { displaySize.y = newSize; } } if (roundMessageSize == 0) { if (AndroidUtilities.isTablet()) { roundMessageSize = (int) (AndroidUtilities.getMinTabletSide() * 0.6f); } else { roundMessageSize = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.6f); } roundMessageInset = dp(2); } if (BuildVars.LOGS_ENABLED) { FileLog.e("density = " + density + " display size = " + displaySize.x + " " + displaySize.y + " " + displayMetrics.xdpi + "x" + displayMetrics.ydpi); } } catch (Exception e) { FileLog.e(e); } } public static double fixLocationCoord(double value) { return ((long) (value * 1000000)) / 1000000.0; } public static String formapMapUrl(int account, double lat, double lon, int width, int height, boolean marker, int zoom, int provider) { int scale = Math.min(2, (int) Math.ceil(AndroidUtilities.density)); if (provider == -1) { provider = MessagesController.getInstance(account).mapProvider; } if (provider == 1 || provider == 3) { String lang = null; String[] availableLangs = new String[]{"ru_RU", "tr_TR"}; LocaleController.LocaleInfo localeInfo = LocaleController.getInstance().getCurrentLocaleInfo(); for (int a = 0; a < availableLangs.length; a++) { if (availableLangs[a].toLowerCase().contains(localeInfo.shortName)) { lang = availableLangs[a]; } } if (lang == null) { lang = "en_US"; } if (marker) { return String.format(Locale.US, "https://static-maps.yandex.ru/1.x/?ll=%.6f,%.6f&z=%d&size=%d,%d&l=map&scale=%d&pt=%.6f,%.6f,vkbkm&lang=%s", lon, lat, zoom, width * scale, height * scale, scale, lon, lat, lang); } else { return String.format(Locale.US, "https://static-maps.yandex.ru/1.x/?ll=%.6f,%.6f&z=%d&size=%d,%d&l=map&scale=%d&lang=%s", lon, lat, zoom, width * scale, height * scale, scale, lang); } } else { String k = MessagesController.getInstance(account).mapKey; if (!TextUtils.isEmpty(k)) { if (marker) { return String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&maptype=roadmap&scale=%d&markers=color:red%%7Csize:mid%%7C%.6f,%.6f&sensor=false&key=%s", lat, lon, zoom, width, height, scale, lat, lon, k); } else { return String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&maptype=roadmap&scale=%d&key=%s", lat, lon, zoom, width, height, scale, k); } } else { if (marker) { return String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&maptype=roadmap&scale=%d&markers=color:red%%7Csize:mid%%7C%.6f,%.6f&sensor=false", lat, lon, zoom, width, height, scale, lat, lon); } else { return String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%.6f,%.6f&zoom=%d&size=%dx%d&maptype=roadmap&scale=%d", lat, lon, zoom, width, height, scale); } } } } public static float getPixelsInCM(float cm, boolean isX) { return (cm / 2.54f) * (isX ? displayMetrics.xdpi : displayMetrics.ydpi); } public static int getMyLayerVersion(int layer) { return layer & 0xffff; } public static int getPeerLayerVersion(int layer) { return (layer >> 16) & 0xffff; } public static int setMyLayerVersion(int layer, int version) { return layer & 0xffff0000 | version; } public static int setPeerLayerVersion(int layer, int version) { return layer & 0x0000ffff | (version << 16); } 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) { photoSize = 1280; } 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) { FileLog.e(e); } }*/ private static Runnable unregisterRunnable; private static boolean hasCallPermissions = Build.VERSION.SDK_INT >= 23; @SuppressWarnings("unchecked") public static void endIncomingCall() { if (!hasCallPermissions) { return; } try { TelephonyManager tm = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); Class c = Class.forName(tm.getClass().getName()); Method m = c.getDeclaredMethod("getITelephony"); m.setAccessible(true); ITelephony telephonyService = (ITelephony) m.invoke(tm); telephonyService = (ITelephony) m.invoke(tm); telephonyService.silenceRinger(); telephonyService.endCall(); } catch (Throwable e) { FileLog.e(e); } } public static String obtainLoginPhoneCall(String pattern) { if (!hasCallPermissions) { return null; } try (Cursor cursor = ApplicationLoader.applicationContext.getContentResolver().query( CallLog.Calls.CONTENT_URI, new String[]{CallLog.Calls.NUMBER, CallLog.Calls.DATE}, CallLog.Calls.TYPE + " IN (" + CallLog.Calls.MISSED_TYPE + "," + CallLog.Calls.INCOMING_TYPE + "," + CallLog.Calls.REJECTED_TYPE + ")", null, "date DESC LIMIT 5")) { while (cursor.moveToNext()) { String number = cursor.getString(0); long date = cursor.getLong(1); if (BuildVars.LOGS_ENABLED) { FileLog.e("number = " + number); } if (Math.abs(System.currentTimeMillis() - date) >= 60 * 60 * 1000) { continue; } if (checkPhonePattern(pattern, number)) { return number; } } } catch (Exception e) { FileLog.e(e); } return null; } public static boolean checkPhonePattern(String pattern, String phone) { if (TextUtils.isEmpty(pattern) || pattern.equals("*")) { return true; } String[] args = pattern.split("\\*"); phone = PhoneFormat.stripExceptNumbers(phone); int checkStart = 0; int index; for (int a = 0; a < args.length; a++) { String arg = args[a]; if (!TextUtils.isEmpty(arg)) { if ((index = phone.indexOf(arg, checkStart)) == -1) { return false; } checkStart = index + arg.length(); } } return true; } private static Field mAttachInfoField; private static Field mStableInsetsField; 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 { if (Build.VERSION.SDK_INT >= 23) { WindowInsets insets = view.getRootWindowInsets(); return insets != null ? insets.getStableInsetBottom() : 0; } else { if (mAttachInfoField == null) { mAttachInfoField = View.class.getDeclaredField("mAttachInfo"); mAttachInfoField.setAccessible(true); } Object mAttachInfo = mAttachInfoField.get(view); if (mAttachInfo != null) { if (mStableInsetsField == null) { mStableInsetsField = mAttachInfo.getClass().getDeclaredField("mStableInsets"); mStableInsetsField.setAccessible(true); } Rect insets = (Rect) mStableInsetsField.get(mAttachInfo); return insets.bottom; } } } catch (Exception e) { FileLog.e(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()); FileLog.e(e); } } } catch (Exception e) { FileLog.e(e); } return size; } public static void setEnabled(View view, boolean enabled) { if (view == null) { return; } view.setEnabled(enabled); if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) { setEnabled(viewGroup.getChildAt(i), enabled); } } } 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; } public static void setViewPagerEdgeEffectColor(ViewPager viewPager, int color) { if (Build.VERSION.SDK_INT >= 21) { try { Field field = ViewPager.class.getDeclaredField("mLeftEdge"); field.setAccessible(true); EdgeEffect mLeftEdge = (EdgeEffect) field.get(viewPager); if (mLeftEdge != null) { mLeftEdge.setColor(color); } field = ViewPager.class.getDeclaredField("mRightEdge"); field.setAccessible(true); EdgeEffect mRightEdge = (EdgeEffect) field.get(viewPager); if (mRightEdge != null) { mRightEdge.setColor(color); } } catch (Exception ignore) { } } } public static void setScrollViewEdgeEffectColor(HorizontalScrollView scrollView, int color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { scrollView.setEdgeEffectColor(color); } else if (Build.VERSION.SDK_INT >= 21) { try { Field field = HorizontalScrollView.class.getDeclaredField("mEdgeGlowLeft"); field.setAccessible(true); EdgeEffect mEdgeGlowTop = (EdgeEffect) field.get(scrollView); if (mEdgeGlowTop != null) { mEdgeGlowTop.setColor(color); } field = HorizontalScrollView.class.getDeclaredField("mEdgeGlowRight"); field.setAccessible(true); EdgeEffect mEdgeGlowBottom = (EdgeEffect) field.get(scrollView); if (mEdgeGlowBottom != null) { mEdgeGlowBottom.setColor(color); } } catch (Exception e) { FileLog.e(e); } } } public static void setScrollViewEdgeEffectColor(ScrollView scrollView, int color) { if (Build.VERSION.SDK_INT >= 29) { scrollView.setTopEdgeEffectColor(color); scrollView.setBottomEdgeEffectColor(color); } else if (Build.VERSION.SDK_INT >= 21) { try { Field field = ScrollView.class.getDeclaredField("mEdgeGlowTop"); field.setAccessible(true); EdgeEffect mEdgeGlowTop = (EdgeEffect) field.get(scrollView); if (mEdgeGlowTop != null) { mEdgeGlowTop.setColor(color); } field = ScrollView.class.getDeclaredField("mEdgeGlowBottom"); field.setAccessible(true); EdgeEffect mEdgeGlowBottom = (EdgeEffect) field.get(scrollView); if (mEdgeGlowBottom != null) { mEdgeGlowBottom.setColor(color); } } catch (Exception ignore) { } } } @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_URL = 8; public static final int FLAG_TAG_ALL = FLAG_TAG_BR | FLAG_TAG_BOLD | FLAG_TAG_URL; public static SpannableStringBuilder replaceTags(String str) { return replaceTags(str, FLAG_TAG_ALL); } public static SpannableStringBuilder replaceTags(String str, int flag, Object... args) { 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); } while ((start = stringBuilder.indexOf("**")) != -1) { stringBuilder.replace(start, start + 2, ""); end = stringBuilder.indexOf("**"); if (end >= 0) { stringBuilder.replace(end, end + 2, ""); bolds.add(start); bolds.add(end); } } } if ((flag & FLAG_TAG_URL) != 0) { while ((start = stringBuilder.indexOf("**")) != -1) { stringBuilder.replace(start, start + 2, ""); end = stringBuilder.indexOf("**"); if (end >= 0) { stringBuilder.replace(end, end + 2, ""); bolds.add(start); bolds.add(end); } } } SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(stringBuilder); for (int a = 0; a < bolds.size() / 2; a++) { spannableStringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), bolds.get(a * 2), bolds.get(a * 2 + 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return spannableStringBuilder; } catch (Exception e) { FileLog.e(e); } return new SpannableStringBuilder(str); } public static class LinkMovementMethodMy extends LinkMovementMethod { @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { try { boolean result = super.onTouchEvent(widget, buffer, event); if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { Selection.removeSelection(buffer); } return result; } catch (Exception e) { FileLog.e(e); } return false; } } public static boolean needShowPasscode() { return needShowPasscode(false); } public static boolean needShowPasscode(boolean reset) { boolean wasInBackground = ForegroundDetector.getInstance().isWasInBackground(reset); if (reset) { ForegroundDetector.getInstance().resetBackgroundVar(); } int uptime = (int) (SystemClock.elapsedRealtime() / 1000); if (BuildVars.LOGS_ENABLED && reset && SharedConfig.passcodeHash.length() > 0) { FileLog.d("wasInBackground = " + wasInBackground + " appLocked = " + SharedConfig.appLocked + " autoLockIn = " + SharedConfig.autoLockIn + " lastPauseTime = " + SharedConfig.lastPauseTime + " uptime = " + uptime); } return SharedConfig.passcodeHash.length() > 0 && wasInBackground && (SharedConfig.appLocked || SharedConfig.autoLockIn != 0 && SharedConfig.lastPauseTime != 0 && !SharedConfig.appLocked && (SharedConfig.lastPauseTime + SharedConfig.autoLockIn) <= uptime || uptime + 5 < SharedConfig.lastPauseTime); } public static void shakeView(final View view, final float x, final int num) { if (view == null) { return; } if (num == 6) { view.setTranslationX(0); return; } AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(view, "translationX", dp(x))); animatorSet.setDuration(50); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { shakeView(view, num == 5 ? 0 : -x, num + 1); } }); animatorSet.start(); } /*public static String ellipsize(String text, int maxLines, int maxWidth, TextPaint paint) { if (text == null || paint == null) { return null; } int count; int offset = 0; StringBuilder result = null; TextView for (int a = 0; a < maxLines; a++) { count = paint.breakText(text, true, maxWidth, null); if (a != maxLines - 1) { if (result == null) { result = new StringBuilder(count * maxLines + 1); } boolean foundSpace = false; for (int c = count - 1; c >= offset; c--) { if (text.charAt(c) == ' ') { foundSpace = true; result.append(text.substring(offset, c - 1)); offset = c - 1; } } if (!foundSpace) { offset = count; } text = text.substring(0, offset); } else if (maxLines == 1) { return text.substring(0, count); } else { result.append(text.substring(0, count)); } } return result.toString(); }*/ /*public static void turnOffHardwareAcceleration(Window window) { if (window == null || Build.MODEL == null) { return; } if (Build.MODEL.contains("GT-S5301") || Build.MODEL.contains("GT-S5303") || Build.MODEL.contains("GT-B5330") || Build.MODEL.contains("GT-S5302") || Build.MODEL.contains("GT-S6012B") || Build.MODEL.contains("MegaFon_SP-AI")) { window.clearFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); } }*/ public static void startAppCenter(Activity context) { } private static long lastUpdateCheckTime; public static void checkForUpdates() { } 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) { FileLog.e(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) { FileLog.e(e); } } private static File getAlbumDir(boolean secretChat) { if (secretChat || Build.VERSION.SDK_INT >= 23 && ApplicationLoader.applicationContext.checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { return FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE); } File storageDir = null; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Telegram"); if (!storageDir.mkdirs()) { if (!storageDir.exists()) { if (BuildVars.LOGS_ENABLED) { FileLog.d("failed to create directory"); } return null; } } } else { if (BuildVars.LOGS_ENABLED) { FileLog.d("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) { FileLog.e(e); } return null; } public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { final String column = "_data"; final String[] projection = { column }; try (Cursor 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 ignore) { } 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() { return generatePicturePath(false, null); } public static File generatePicturePath(boolean secretChat, String ext) { try { File storageDir = getAlbumDir(secretChat); Date date = new Date(); date.setTime(System.currentTimeMillis() + Utilities.random.nextInt(1000) + 1); String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US).format(date); return new File(storageDir, "IMG_" + timeStamp + "." + (TextUtils.isEmpty(ext) ? "jpg" : ext)); } catch (Exception e) { FileLog.e(e); } return null; } public static CharSequence generateSearchName(String name, String name2, String q) { if (name == null && name2 == null || TextUtils.isEmpty(q)) { return ""; } SpannableStringBuilder builder = new SpannableStringBuilder(); String wholeString = name; if (wholeString == null || wholeString.length() == 0) { wholeString = name2; } else if (name2 != null && name2.length() != 0) { wholeString += " " + name2; } wholeString = wholeString.trim(); String lower = " " + wholeString.toLowerCase(); int index; int lastIndex = 0; while ((index = lower.indexOf(" " + q, lastIndex)) != -1) { int idx = index - (index == 0 ? 0 : 1); int end = q.length() + (index == 0 ? 0 : 1) + idx; if (lastIndex != 0 && lastIndex != idx + 1) { builder.append(wholeString.substring(lastIndex, idx)); } else if (lastIndex == 0 && idx != 0) { builder.append(wholeString.substring(0, idx)); } String query = wholeString.substring(idx, Math.min(wholeString.length(), end)); if (query.startsWith(" ")) { builder.append(" "); } query = query.trim(); int start = builder.length(); builder.append(query); builder.setSpan(new ForegroundColorSpanThemable(Theme.key_windowBackgroundWhiteBlueText4), start, start + query.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); lastIndex = end; } if (lastIndex != -1 && lastIndex < wholeString.length()) { builder.append(wholeString.substring(lastIndex)); } return builder; } public static boolean isAirplaneModeOn() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { return Settings.System.getInt(ApplicationLoader.applicationContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) != 0; } else { return Settings.Global.getInt(ApplicationLoader.applicationContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) != 0; } } public static File generateVideoPath() { return generateVideoPath(false); } public static File generateVideoPath(boolean secretChat) { try { File storageDir = getAlbumDir(secretChat); Date date = new Date(); date.setTime(System.currentTimeMillis() + Utilities.random.nextInt(1000) + 1); String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US).format(date); return new File(storageDir, "VID_" + timeStamp + ".mp4"); } catch (Exception e) { FileLog.e(e); } return null; } public static String formatFileSize(long size) { return formatFileSize(size, false); } public static String formatFileSize(long size, boolean removeZero) { if (size < 1024) { return String.format("%d B", size); } else if (size < 1024 * 1024) { float value = size / 1024.0f; if (removeZero && (value - (int) value) * 10 == 0) { return String.format("%d KB", (int) value); } else { return String.format("%.1f KB", value); } } else if (size < 1024 * 1024 * 1024) { float value = size / 1024.0f / 1024.0f; if (removeZero && (value - (int) value) * 10 == 0) { return String.format("%d MB", (int) value); } else { return String.format("%.1f MB", value); } } else { float value = size / 1024.0f / 1024.0f / 1024.0f; if (removeZero && (value - (int) value) * 10 == 0) { return String.format("%d GB", (int) value); } else { return String.format("%.1f GB", value); } } } public static String formatShortDuration(int duration) { return formatDuration(duration, false); } public static String formatLongDuration(int duration) { return formatDuration(duration, true); } public static String formatDuration(int duration, boolean isLong) { int h = duration / 3600; int m = duration / 60 % 60; int s = duration % 60; if (h == 0) { if (isLong) { return String.format(Locale.US, "%02d:%02d", m, s); } else { return String.format(Locale.US, "%d:%02d", m, s); } } else { return String.format(Locale.US, "%d:%02d:%02d", h, m, s); } } public static String formatFullDuration(int duration) { int h = duration / 3600; int m = duration / 60 % 60; int s = duration % 60; if (duration < 0) { return String.format(Locale.US, "-%02d:%02d:%02d", Math.abs(h), Math.abs(m), Math.abs(s)); } else { return String.format(Locale.US, "%02d:%02d:%02d", h, m, s); } } public static String formatDurationNoHours(int duration, boolean isLong) { int m = duration / 60; int s = duration % 60; if (isLong) { return String.format(Locale.US, "%02d:%02d", m, s); } else { return String.format(Locale.US, "%d:%02d", m, s); } } public static String formatShortDuration(int progress, int duration) { return formatDuration(progress, duration, false); } public static String formatLongDuration(int progress, int duration) { return formatDuration(progress, duration, true); } public static String formatDuration(int progress, int duration, boolean isLong) { int h = duration / 3600; int m = duration / 60 % 60; int s = duration % 60; int ph = progress / 3600; int pm = progress / 60 % 60; int ps = progress % 60; if (duration == 0) { if (ph == 0) { if (isLong) { return String.format(Locale.US, "%02d:%02d / -:--", pm, ps); } else { return String.format(Locale.US, "%d:%02d / -:--", pm, ps); } } else { return String.format(Locale.US, "%d:%02d:%02d / -:--", ph, pm, ps); } } else { if (ph == 0 && h == 0) { if (isLong) { return String.format(Locale.US, "%02d:%02d / %02d:%02d", pm, ps, m, s); } else { return String.format(Locale.US, "%d:%02d / %d:%02d", pm, ps, m, s); } } else { return String.format(Locale.US, "%d:%02d:%02d / %d:%02d:%02d", ph, pm, ps, h, m, s); } } } public static String formatVideoDuration(int progress, int duration) { int h = duration / 3600; int m = duration / 60 % 60; int s = duration % 60; int ph = progress / 3600; int pm = progress / 60 % 60; int ps = progress % 60; if (ph == 0 && h == 0) { return String.format(Locale.US, "%02d:%02d / %02d:%02d", pm, ps, m, s); } else { if (h == 0) { return String.format(Locale.US, "%d:%02d:%02d / %02d:%02d", ph, pm, ps, m, s); } else if (ph == 0) { return String.format(Locale.US, "%02d:%02d / %d:%02d:%02d", pm, ps, h, m, s); } else { return String.format(Locale.US, "%d:%02d:%02d / %d:%02d:%02d", ph, pm, ps, h, m, s); } } } public static String formatCount(int count) { if (count < 1000) return Integer.toString(count); ArrayList strings = new ArrayList<>(); while (count != 0) { int mod = count % 1000; count /= 1000; if (count > 0) { strings.add(String.format(Locale.ENGLISH, "%03d", mod)); } else { strings.add(Integer.toString(mod)); } } StringBuilder stringBuilder = new StringBuilder(); for (int i = strings.size() - 1; i >= 0; i--) { stringBuilder.append(strings.get(i)); if (i != 0) { stringBuilder.append(","); } } return stringBuilder.toString(); } public static final String[] numbersSignatureArray = {"", "K", "M", "G", "T", "P"}; public static String formatWholeNumber(int v, int dif) { if (v == 0) { return "0"; } float num_ = v; int count = 0; if (dif == 0) dif = v; if (dif < 1000) { return AndroidUtilities.formatCount(v); } while (dif >= 1000 && count < numbersSignatureArray.length - 1) { dif /= 1000; num_ /= 1000; count++; } if (num_ < 0.1) { return "0"; } else { if ((num_ * 10)== (int) (num_ * 10)) { return String.format(Locale.ENGLISH, "%s%s", AndroidUtilities.formatCount((int) num_), numbersSignatureArray[count]); } else { return String.format(Locale.ENGLISH, "%.1f%s", (int) (num_ * 10) / 10f, numbersSignatureArray[count]); } } } 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) { FileLog.e(e); return null; } } else { buffer.write(b); } } byte[] array = buffer.toByteArray(); try { buffer.close(); } catch (Exception e) { FileLog.e(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 (sourceFile.equals(destFile)) { return true; } if (!destFile.exists()) { destFile.createNewFile(); } try (FileInputStream source = new FileInputStream(sourceFile); FileOutputStream destination = new FileOutputStream(destFile)) { destination.getChannel().transferFrom(source.getChannel(), 0, source.getChannel().size()); } catch (Exception e) { FileLog.e(e); return false; } return true; } public static byte[] calcAuthKeyHash(byte[] auth_key) { byte[] sha1 = Utilities.computeSHA1(auth_key); byte[] key_hash = new byte[16]; System.arraycopy(sha1, 0, key_hash, 0, 16); return key_hash; } public static void openDocument(MessageObject message, Activity activity, BaseFragment parentFragment) { if (message == null) { return; } TLRPC.Document document = message.getDocument(); if (document == null) { return; } File f = null; String fileName = message.messageOwner.media != null ? FileLoader.getAttachFileName(document) : ""; if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { f = new File(message.messageOwner.attachPath); } if (f == null || f != null && !f.exists()) { f = FileLoader.getPathToMessage(message.messageOwner); } if (f != null && f.exists()) { if (parentFragment != null && f.getName().toLowerCase().endsWith("attheme")) { Theme.ThemeInfo themeInfo = Theme.applyThemeFile(f, message.getDocumentName(), null, true); if (themeInfo != null) { parentFragment.presentFragment(new ThemePreviewActivity(themeInfo)); } else { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.getString("IncorrectTheme", R.string.IncorrectTheme)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); parentFragment.showDialog(builder.create()); } } else { String realMimeType = null; try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); MimeTypeMap myMime = MimeTypeMap.getSingleton(); int idx = fileName.lastIndexOf('.'); if (idx != -1) { String ext = fileName.substring(idx + 1); realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); if (realMimeType == null) { realMimeType = document.mime_type; if (realMimeType == null || realMimeType.length() == 0) { realMimeType = null; } } } if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), realMimeType != null ? realMimeType : "text/plain"); } else { intent.setDataAndType(Uri.fromFile(f), realMimeType != null ? realMimeType : "text/plain"); } if (realMimeType != null) { try { activity.startActivityForResult(intent, 500); } catch (Exception e) { if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), "text/plain"); } else { intent.setDataAndType(Uri.fromFile(f), "text/plain"); } activity.startActivityForResult(intent, 500); } } else { activity.startActivityForResult(intent, 500); } } catch (Exception e) { if (activity == null) { return; } AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); builder.setMessage(LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, message.getDocument().mime_type)); if (parentFragment != null) { parentFragment.showDialog(builder.create()); } else { builder.show(); } } } } } public static boolean openForView(MessageObject message, final Activity activity) { File f = null; String fileName = message.getFileName(); if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { f = new File(message.messageOwner.attachPath); } if (f == null || !f.exists()) { f = FileLoader.getPathToMessage(message.messageOwner); } if (f != null && f.exists()) { String realMimeType = null; Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); MimeTypeMap myMime = MimeTypeMap.getSingleton(); int idx = fileName.lastIndexOf('.'); if (idx != -1) { String ext = fileName.substring(idx + 1); realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); if (realMimeType == null) { if (message.type == 9 || message.type == 0) { realMimeType = message.getDocument().mime_type; } if (realMimeType == null || realMimeType.length() == 0) { realMimeType = null; } } } if (Build.VERSION.SDK_INT >= 26 && realMimeType != null && realMimeType.equals("application/vnd.android.package-archive") && !ApplicationLoader.applicationContext.getPackageManager().canRequestPackageInstalls()) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.getString("ApkRestricted", R.string.ApkRestricted)); builder.setPositiveButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), (dialogInterface, i) -> { try { activity.startActivity(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + activity.getPackageName()))); } catch (Exception e) { FileLog.e(e); } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.show(); return true; } if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), realMimeType != null ? realMimeType : "text/plain"); } else { intent.setDataAndType(Uri.fromFile(f), realMimeType != null ? realMimeType : "text/plain"); } if (realMimeType != null) { try { activity.startActivityForResult(intent, 500); } catch (Exception e) { if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), "text/plain"); } else { intent.setDataAndType(Uri.fromFile(f), "text/plain"); } activity.startActivityForResult(intent, 500); } } else { activity.startActivityForResult(intent, 500); } return true; } return false; } public static CharSequence replaceNewLines(CharSequence original) { if (original instanceof StringBuilder) { StringBuilder stringBuilder = (StringBuilder) original; for (int a = 0, N = original.length(); a < N; a++) { if (original.charAt(a) == '\n') { stringBuilder.setCharAt(a, ' '); } } } else if (original instanceof SpannableStringBuilder) { SpannableStringBuilder stringBuilder = (SpannableStringBuilder) original; for (int a = 0, N = original.length(); a < N; a++) { if (original.charAt(a) == '\n') { stringBuilder.replace(a, a + 1, " "); } } } return original.toString().replace('\n', ' '); } public static boolean openForView(TLObject media, Activity activity) { if (media == null || activity == null) { return false; } String fileName = FileLoader.getAttachFileName(media); File f = FileLoader.getPathToAttach(media, true); if (f != null && f.exists()) { String realMimeType = null; Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); MimeTypeMap myMime = MimeTypeMap.getSingleton(); int idx = fileName.lastIndexOf('.'); if (idx != -1) { String ext = fileName.substring(idx + 1); realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); if (realMimeType == null) { if (media instanceof TLRPC.TL_document) { realMimeType = ((TLRPC.TL_document) media).mime_type; } if (realMimeType == null || realMimeType.length() == 0) { realMimeType = null; } } } if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), realMimeType != null ? realMimeType : "text/plain"); } else { intent.setDataAndType(Uri.fromFile(f), realMimeType != null ? realMimeType : "text/plain"); } if (realMimeType != null) { try { activity.startActivityForResult(intent, 500); } catch (Exception e) { if (Build.VERSION.SDK_INT >= 24) { intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), "text/plain"); } else { intent.setDataAndType(Uri.fromFile(f), "text/plain"); } activity.startActivityForResult(intent, 500); } } else { activity.startActivityForResult(intent, 500); } return true; } else { return false; } } public static boolean isBannedForever(TLRPC.TL_chatBannedRights rights) { return rights == null || Math.abs(rights.until_date - System.currentTimeMillis() / 1000) > 5 * 365 * 24 * 60 * 60; } public static void setRectToRect(Matrix matrix, RectF src, RectF dst, int rotation, boolean translate) { float tx, sx; float ty, sy; boolean xLarger = false; if (rotation == 90 || rotation == 270) { sx = dst.height() / src.width(); sy = dst.width() / src.height(); } else { sx = dst.width() / src.width(); sy = dst.height() / src.height(); } if (sx < sy) { sx = sy; xLarger = true; } else { sy = sx; } if (translate) { matrix.setTranslate(dst.left, dst.top); } if (rotation == 90) { matrix.preRotate(90); matrix.preTranslate(0, -dst.width()); } else if (rotation == 180) { matrix.preRotate(180); matrix.preTranslate(-dst.width(), -dst.height()); } else if (rotation == 270) { matrix.preRotate(270); matrix.preTranslate(-dst.height(), 0); } if (translate) { tx = -src.left * sx; ty = -src.top * sy; } else { tx = dst.left - src.left * sx; ty = dst.top - src.top * sy; } float diff; if (xLarger) { diff = dst.width() - src.width() * sy; } else { diff = dst.height() - src.height() * sy; } diff = diff / 2.0f; if (xLarger) { tx += diff; } else { ty += diff; } matrix.preScale(sx, sy); if (translate) { matrix.preTranslate(tx, ty); } } public static boolean handleProxyIntent(Activity activity, Intent intent) { if (intent == null) { return false; } try { if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { return false; } Uri data = intent.getData(); if (data != null) { String user = null; String password = null; String port = null; String address = null; String secret = null; String scheme = data.getScheme(); if (scheme != null) { if ((scheme.equals("http") || scheme.equals("https"))) { String host = data.getHost().toLowerCase(); if (host.equals("telegram.me") || host.equals("t.me") || host.equals("telegram.dog")) { String path = data.getPath(); if (path != null) { if (path.startsWith("/socks") || path.startsWith("/proxy")) { address = data.getQueryParameter("server"); if (AndroidUtilities.checkHostForPunycode(address)) { address = IDN.toASCII(address, IDN.ALLOW_UNASSIGNED); } port = data.getQueryParameter("port"); user = data.getQueryParameter("user"); password = data.getQueryParameter("pass"); secret = data.getQueryParameter("secret"); } } } } else if (scheme.equals("tg")) { String url = data.toString(); if (url.startsWith("tg:proxy") || url.startsWith("tg://proxy") || url.startsWith("tg:socks") || url.startsWith("tg://socks")) { url = url.replace("tg:proxy", "tg://telegram.org").replace("tg://proxy", "tg://telegram.org").replace("tg://socks", "tg://telegram.org").replace("tg:socks", "tg://telegram.org"); data = Uri.parse(url); address = data.getQueryParameter("server"); if (AndroidUtilities.checkHostForPunycode(address)) { address = IDN.toASCII(address, IDN.ALLOW_UNASSIGNED); } port = data.getQueryParameter("port"); user = data.getQueryParameter("user"); password = data.getQueryParameter("pass"); secret = data.getQueryParameter("secret"); } } } if (!TextUtils.isEmpty(address) && !TextUtils.isEmpty(port)) { if (user == null) { user = ""; } if (password == null) { password = ""; } if (secret == null) { secret = ""; } showProxyAlert(activity, address, port, user, password, secret); return true; } } } catch (Exception ignore) { } return false; } public static boolean shouldEnableAnimation() { if (Build.VERSION.SDK_INT < 26 || Build.VERSION.SDK_INT >= 28) { return true; } PowerManager powerManager = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE); if (powerManager.isPowerSaveMode()) { return false; } float scale = Settings.Global.getFloat(ApplicationLoader.applicationContext.getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f); if (scale <= 0.0f) { return false; } return true; } public static void showProxyAlert(Activity activity, final String address, final String port, final String user, final String password, final String secret) { BottomSheet.Builder builder = new BottomSheet.Builder(activity); final Runnable dismissRunnable = builder.getDismissRunnable(); builder.setApplyTopPadding(false); builder.setApplyBottomPadding(false); LinearLayout linearLayout = new LinearLayout(activity); builder.setCustomView(linearLayout); linearLayout.setOrientation(LinearLayout.VERTICAL); if (!TextUtils.isEmpty(secret)) { TextView titleTextView = new TextView(activity); titleTextView.setText(LocaleController.getString("UseProxyTelegramInfo2", R.string.UseProxyTelegramInfo2)); titleTextView.setTextColor(Theme.getColor(Theme.key_dialogTextGray4)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); titleTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); linearLayout.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 8, 17, 8)); View lineView = new View(activity); lineView.setBackgroundColor(Theme.getColor(Theme.key_divider)); linearLayout.addView(lineView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); } for (int a = 0; a < 5; a++) { String text = null; String detail = null; if (a == 0) { text = address; detail = LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress); } else if (a == 1) { text = "" + port; detail = LocaleController.getString("UseProxyPort", R.string.UseProxyPort); } else if (a == 2) { text = secret; detail = LocaleController.getString("UseProxySecret", R.string.UseProxySecret); } else if (a == 3) { text = user; detail = LocaleController.getString("UseProxyUsername", R.string.UseProxyUsername); } else if (a == 4) { text = password; detail = LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword); } if (TextUtils.isEmpty(text)) { continue; } TextDetailSettingsCell cell = new TextDetailSettingsCell(activity); cell.setTextAndValue(text, detail, true); cell.getTextView().setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); cell.getValueTextView().setTextColor(Theme.getColor(Theme.key_dialogTextGray3)); linearLayout.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); if (a == 2) { break; } } PickerBottomLayout pickerBottomLayout = new PickerBottomLayout(activity, false); pickerBottomLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground)); linearLayout.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); pickerBottomLayout.cancelButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); pickerBottomLayout.cancelButton.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); pickerBottomLayout.cancelButton.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); pickerBottomLayout.cancelButton.setOnClickListener(view -> dismissRunnable.run()); pickerBottomLayout.doneButtonTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); pickerBottomLayout.doneButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); pickerBottomLayout.doneButtonBadgeTextView.setVisibility(View.GONE); pickerBottomLayout.doneButtonTextView.setText(LocaleController.getString("ConnectingConnectProxy", R.string.ConnectingConnectProxy).toUpperCase()); pickerBottomLayout.doneButton.setOnClickListener(v -> { SharedPreferences.Editor editor = MessagesController.getGlobalMainSettings().edit(); editor.putBoolean("proxy_enabled", true); editor.putString("proxy_ip", address); int p = Utilities.parseInt(port); editor.putInt("proxy_port", p); SharedConfig.ProxyInfo info; if (TextUtils.isEmpty(secret)) { editor.remove("proxy_secret"); if (TextUtils.isEmpty(password)) { editor.remove("proxy_pass"); } else { editor.putString("proxy_pass", password); } if (TextUtils.isEmpty(user)) { editor.remove("proxy_user"); } else { editor.putString("proxy_user", user); } info = new SharedConfig.ProxyInfo(address, p, user, password, ""); } else { editor.remove("proxy_pass"); editor.remove("proxy_user"); editor.putString("proxy_secret", secret); info = new SharedConfig.ProxyInfo(address, p, "", "", secret); } editor.commit(); SharedConfig.currentProxy = SharedConfig.addProxy(info); ConnectionsManager.setProxySettings(true, address, p, user, password, secret); NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.proxySettingsChanged); dismissRunnable.run(); }); builder.show(); } @SuppressLint("PrivateApi") public static String getSystemProperty(String key) { try { Class props = Class.forName("android.os.SystemProperties"); return (String) props.getMethod("get", String.class).invoke(null, key); } catch (Exception ignore) { } return null; } public static void fixGoogleMapsBug() { //https://issuetracker.google.com/issues/154855417#comment301 SharedPreferences googleBug = ApplicationLoader.applicationContext.getSharedPreferences("google_bug_154855417", Context.MODE_PRIVATE); if (!googleBug.contains("fixed")) { File corruptedZoomTables = new File(ApplicationLoader.getFilesDirFixed(), "ZoomTables.data"); corruptedZoomTables.delete(); googleBug.edit().putBoolean("fixed", true).apply(); } } public static CharSequence concat(CharSequence... text) { if (text.length == 0) { return ""; } if (text.length == 1) { return text[0]; } boolean spanned = false; for (CharSequence piece : text) { if (piece instanceof Spanned) { spanned = true; break; } } if (spanned) { final SpannableStringBuilder ssb = new SpannableStringBuilder(); for (CharSequence piece : text) { // If a piece is null, we append the string "null" for compatibility with the // behavior of StringBuilder and the behavior of the concat() method in earlier // versions of Android. ssb.append(piece == null ? "null" : piece); } return new SpannedString(ssb); } else { final StringBuilder sb = new StringBuilder(); for (CharSequence piece : text) { sb.append(piece); } return sb.toString(); } } public static float[] RGBtoHSB(int r, int g, int b) { float hue, saturation, brightness; float[] hsbvals = new float[3]; int cmax = Math.max(r, g); if (b > cmax) { cmax = b; } int cmin = Math.min(r, g); if (b < cmin) { cmin = b; } brightness = ((float) cmax) / 255.0f; if (cmax != 0) { saturation = ((float) (cmax - cmin)) / ((float) cmax); } else { saturation = 0; } if (saturation == 0) { hue = 0; } else { float redc = ((float) (cmax - r)) / ((float) (cmax - cmin)); float greenc = ((float) (cmax - g)) / ((float) (cmax - cmin)); float bluec = ((float) (cmax - b)) / ((float) (cmax - cmin)); if (r == cmax) { hue = bluec - greenc; } else if (g == cmax) { hue = 2.0f + redc - bluec; } else { hue = 4.0f + greenc - redc; } hue = hue / 6.0f; if (hue < 0) { hue = hue + 1.0f; } } hsbvals[0] = hue; hsbvals[1] = saturation; hsbvals[2] = brightness; return hsbvals; } public static int HSBtoRGB(float hue, float saturation, float brightness) { int r = 0, g = 0, b = 0; if (saturation == 0) { r = g = b = (int) (brightness * 255.0f + 0.5f); } else { float h = (hue - (float) Math.floor(hue)) * 6.0f; float f = h - (float) java.lang.Math.floor(h); float p = brightness * (1.0f - saturation); float q = brightness * (1.0f - saturation * f); float t = brightness * (1.0f - (saturation * (1.0f - f))); switch ((int) h) { case 0: r = (int) (brightness * 255.0f + 0.5f); g = (int) (t * 255.0f + 0.5f); b = (int) (p * 255.0f + 0.5f); break; case 1: r = (int) (q * 255.0f + 0.5f); g = (int) (brightness * 255.0f + 0.5f); b = (int) (p * 255.0f + 0.5f); break; case 2: r = (int) (p * 255.0f + 0.5f); g = (int) (brightness * 255.0f + 0.5f); b = (int) (t * 255.0f + 0.5f); break; case 3: r = (int) (p * 255.0f + 0.5f); g = (int) (q * 255.0f + 0.5f); b = (int) (brightness * 255.0f + 0.5f); break; case 4: r = (int) (t * 255.0f + 0.5f); g = (int) (p * 255.0f + 0.5f); b = (int) (brightness * 255.0f + 0.5f); break; case 5: r = (int) (brightness * 255.0f + 0.5f); g = (int) (p * 255.0f + 0.5f); b = (int) (q * 255.0f + 0.5f); break; } } return 0xff000000 | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); } public static float computePerceivedBrightness(int color) { return (Color.red(color) * 0.2126f + Color.green(color) * 0.7152f + Color.blue(color) * 0.0722f) / 255f; } public static int getPatternColor(int color) { float[] hsb = RGBtoHSB(Color.red(color), Color.green(color), Color.blue(color)); if (hsb[1] > 0.0f || (hsb[2] < 1.0f && hsb[2] > 0.0f)) { hsb[1] = Math.min(1.0f, hsb[1] + 0.05f + 0.1f * (1.0f - hsb[1])); } if (hsb[2] > 0.5f) { hsb[2] = Math.max(0.0f, hsb[2] * 0.65f); } else { hsb[2] = Math.max(0.0f, Math.min(1.0f, 1.0f - hsb[2] * 0.65f)); } return HSBtoRGB(hsb[0], hsb[1], hsb[2]) & 0x66ffffff; } public static int getPatternSideColor(int color) { float[] hsb = RGBtoHSB(Color.red(color), Color.green(color), Color.blue(color)); hsb[1] = Math.min(1.0f, hsb[1] + 0.05f); if (hsb[2] > 0.5f) { hsb[2] = Math.max(0.0f, hsb[2] * 0.90f); } else{ hsb[2] = Math.max(0.0f, hsb[2] * 0.90f); } return HSBtoRGB(hsb[0], hsb[1], hsb[2]) | 0xff000000; } public static int getWallpaperRotation(int angle, boolean iOS) { if (iOS) { angle += 180; } else { angle -= 180; } while (angle >= 360) { angle -= 360; } while (angle < 0) { angle += 360; } return angle; } public static String getWallPaperUrl(Object object) { String link; if (object instanceof TLRPC.TL_wallPaper) { TLRPC.TL_wallPaper wallPaper = (TLRPC.TL_wallPaper) object; link = "https://" + MessagesController.getInstance(UserConfig.selectedAccount).linkPrefix + "/bg/" + wallPaper.slug; StringBuilder modes = new StringBuilder(); if (wallPaper.settings != null) { if (wallPaper.settings.blur) { modes.append("blur"); } if (wallPaper.settings.motion) { if (modes.length() > 0) { modes.append("+"); } modes.append("motion"); } } if (modes.length() > 0) { link += "?mode=" + modes.toString(); } } else if (object instanceof WallpapersListActivity.ColorWallpaper) { WallpapersListActivity.ColorWallpaper wallPaper = (WallpapersListActivity.ColorWallpaper) object; link = wallPaper.getUrl(); } else { link = null; } return link; } public static float distanceInfluenceForSnapDuration(float f) { f -= 0.5F; f *= 0.47123894F; return (float) Math.sin(f); } public static void makeAccessibilityAnnouncement(CharSequence what) { AccessibilityManager am = (AccessibilityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACCESSIBILITY_SERVICE); if (am.isEnabled()) { AccessibilityEvent ev = AccessibilityEvent.obtain(); ev.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT); ev.getText().add(what); am.sendAccessibilityEvent(ev); } } public static int getOffsetColor(int color1, int color2, float offset, float alpha) { int rF = Color.red(color2); int gF = Color.green(color2); int bF = Color.blue(color2); int aF = Color.alpha(color2); int rS = Color.red(color1); int gS = Color.green(color1); int bS = Color.blue(color1); int aS = Color.alpha(color1); return Color.argb((int) ((aS + (aF - aS) * offset) * alpha), (int) (rS + (rF - rS) * offset), (int) (gS + (gF - gS) * offset), (int) (bS + (bF - bS) * offset)); } public static int indexOfIgnoreCase(final String origin, final String searchStr) { if (searchStr.isEmpty() || origin.isEmpty()) { return origin.indexOf(searchStr); } for (int i = 0; i < origin.length(); i++) { if (i + searchStr.length() > origin.length()) { return -1; } int j = 0; int ii = i; while (ii < origin.length() && j < searchStr.length()) { char c = Character.toLowerCase(origin.charAt(ii)); char c2 = Character.toLowerCase(searchStr.charAt(j)); if (c != c2) { break; } j++; ii++; } if (j == searchStr.length()) { return i; } } return -1; } public static float lerp(float a, float b, float f) { return a + f * (b - a); } public static float lerp(float[] ab, float f) { return lerp(ab[0], ab[1], f); } private static WeakReference flagSecureFragment; public static boolean hasFlagSecureFragment() { return flagSecureFragment != null; } public static void setFlagSecure(BaseFragment parentFragment, boolean set) { if (parentFragment == null || parentFragment.getParentActivity() == null) { return; } if (set) { try { parentFragment.getParentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); flagSecureFragment = new WeakReference<>(parentFragment); } catch (Exception ignore) { } } else if (flagSecureFragment != null && flagSecureFragment.get() == parentFragment) { try { parentFragment.getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } catch (Exception ignore) { } flagSecureFragment = null; } } public static void openSharing(BaseFragment fragment, String url) { if (fragment == null || fragment.getParentActivity() == null) { return; } fragment.showDialog(new ShareAlert(fragment.getParentActivity(), null, url, false, url, false)); } public static boolean allowScreenCapture() { return SharedConfig.passcodeHash.length() == 0 || SharedConfig.allowScreenCapture; } public static File getSharingDirectory() { return new File(FileLoader.getDirectory(FileLoader.MEDIA_DIR_CACHE), "sharing/"); } public static String getCertificateSHA256Fingerprint() { PackageManager pm = ApplicationLoader.applicationContext.getPackageManager(); String packageName = ApplicationLoader.applicationContext.getPackageName(); try { PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); Signature[] signatures = packageInfo.signatures; byte[] cert = signatures[0].toByteArray(); InputStream input = new ByteArrayInputStream(cert); CertificateFactory cf = CertificateFactory.getInstance("X509"); X509Certificate c = (X509Certificate) cf.generateCertificate(input); return Utilities.bytesToHex(Utilities.computeSHA256(c.getEncoded())); } catch (Throwable ignore) { } return ""; } private static char[] characters = new char[] {' ', ' ', '!', '"', '#', '%', '&', '\'', '(', ')', '*', ',', '-', '.', '/', ':', ';', '?', '@', '[', '\\', ']', '_', '{', '}', '¡', '§', '«', '¶', '·', '»', '¿', ';', '·', '՚', '՛', '՜', '՝', '՞', '՟', '։', '֊', '־', '׀', '׃', '׆', '׳', '״', '؉', '؊', '،', '؍', '؛', '؞', '؟', '٪', '٫', '٬', '٭', '۔', '܀', '܁', '܂', '܃', '܄', '܅', '܆', '܇', '܈', '܉', '܊', '܋', '܌', '܍', '߷', '߸', '߹', '࠰', '࠱', '࠲', '࠳', '࠴', '࠵', '࠶', '࠷', '࠸', '࠹', '࠺', '࠻', '࠼', '࠽', '࠾', '࡞', '।', '॥', '॰', '৽', '੶', '૰', '౷', '಄', '෴', '๏', '๚', '๛', '༄', '༅', '༆', '༇', '༈', '༉', '༊', '་', '༌', '།', '༎', '༏', '༐', '༑', '༒', '༔', '༺', '༻', '༼', '༽', '྅', '࿐', '࿑', '࿒', '࿓', '࿔', '࿙', '࿚', '၊', '။', '၌', '၍', '၎', '၏', '჻', '፠', '፡', '።', '፣', '፤', '፥', '፦', '፧', '፨', '᐀', '᙮', '᚛', '᚜', '᛫', '᛬', '᛭', '᜵', '᜶', '។', '៕', '៖', '៘', '៙', '៚', '᠀', '᠁', '᠂', '᠃', '᠄', '᠅', '᠆', '᠇', '᠈', '᠉', '᠊', '᥄', '᥅', '᨞', '᨟', '᪠', '᪡', '᪢', '᪣', '᪤', '᪥', '᪦', '᪨', '᪩', '᪪', '᪫', '᪬', '᪭', '᭚', '᭛', '᭜', '᭝', '᭞', '᭟', '᭠', '᯼', '᯽', '᯾', '᯿', '᰻', '᰼', '᰽', '᰾', '᰿', '᱾', '᱿', '᳀', '᳁', '᳂', '᳃', '᳄', '᳅', '᳆', '᳇', '᳓', '‐', '‑', '‒', '–', '—', '―', '‖', '‗', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '†', '‡', '•', '‣', '․', '‥', '…', '‧', '‰', '‱', '′', '″', '‴', '‵', '‶', '‷', '‸', '‹', '›', '※', '‼', '‽', '‾', '‿', '⁀', '⁁', '⁂', '⁃', '⁅', '⁆', '⁇', '⁈', '⁉', '⁊', '⁋', '⁌', '⁍', '⁎', '⁏', '⁐', '⁑', '⁓', '⁔', '⁕', '⁖', '⁗', '⁘', '⁙', '⁚', '⁛', '⁜', '⁝', '⁞', '⁽', '⁾', '₍', '₎', '⌈', '⌉', '⌊', '⌋', '〈', '〉', '❨', '❩', '❪', '❫', '❬', '❭', '❮', '❯', '❰', '❱', '❲', '❳', '❴', '❵', '⟅', '⟆', '⟦', '⟧', '⟨', '⟩', '⟪', '⟫', '⟬', '⟭', '⟮', '⟯', '⦃', '⦄', '⦅', '⦆', '⦇', '⦈', '⦉', '⦊', '⦋', '⦌', '⦍', '⦎', '⦏', '⦐', '⦑', '⦒', '⦓', '⦔', '⦕', '⦖', '⦗', '⦘', '⧘', '⧙', '⧚', '⧛', '⧼', '⧽', '⳹', '⳺', '⳻', '⳼', '⳾', '⳿', '⵰', '⸀', '⸁', '⸂', '⸃', '⸄', '⸅', '⸆', '⸇', '⸈', '⸉', '⸊', '⸋', '⸌', '⸍', '⸎', '⸏', '⸐', '⸑', '⸒', '⸓', '⸔', '⸕', '⸖', '⸗', '⸘', '⸙', '⸚', '⸛', '⸜', '⸝', '⸞', '⸟', '⸠', '⸡', '⸢', '⸣', '⸤', '⸥', '⸦', '⸧', '⸨', '⸩', '⸪', '⸫', '⸬', '⸭', '⸮', '⸰', '⸱', '⸲', '⸳', '⸴', '⸵', '⸶', '⸷', '⸸', '⸹', '⸺', '⸻', '⸼', '⸽', '⸾', '⸿', '⹀', '⹁', '⹂', '⹃', '⹄', '⹅', '⹆', '⹇', '⹈', '⹉', '⹊', '⹋', '⹌', '⹍', '⹎', '⹏', '、', '。', '〃', '〈', '〉', '《', '》', '「', '」', '『', '』', '【', '】', '〔', '〕', '〖', '〗', '〘', '〙', '〚', '〛', '〜', '〝', '〞', '〟', '〰', '〽', '゠', '・', '꓾', '꓿', '꘍', '꘎', '꘏', '꙳', '꙾', '꛲', '꛳', '꛴', '꛵', '꛶', '꛷', '꡴', '꡵', '꡶', '꡷', '꣎', '꣏', '꣸', '꣹', '꣺', '꣼', '꤮', '꤯', '꥟', '꧁', '꧂', '꧃', '꧄', '꧅', '꧆', '꧇', '꧈', '꧉', '꧊', '꧋', '꧌', '꧍', '꧞', '꧟', '꩜', '꩝', '꩞', '꩟', '꫞', '꫟', '꫰', '꫱', '꯫', '﴾', '﴿', '︐', '︑', '︒', '︓', '︔', '︕', '︖', '︗', '︘', '︙', '︰', '︱', '︲', '︳', '︴', '︵', '︶', '︷', '︸', '︹', '︺', '︻', '︼', '︽', '︾', '︿', '﹀', '﹁', '﹂', '﹃', '﹄', '﹅', '﹆', '﹇', '﹈', '﹉', '﹊', '﹋', '﹌', '﹍', '﹎', '﹏', '﹐', '﹑', '﹒', '﹔', '﹕', '﹖', '﹗', '﹘', '﹙', '﹚', '﹛', '﹜', '﹝', '﹞', '﹟', '﹠', '﹡', '﹣', '﹨', '﹪', '﹫', '!', '"', '#', '%', '&', ''', '(', ')', '*', ',', '-', '.', '/', ':', ';', '?', '@', '[', '\', ']', '_', '{', '}', '⦅', '⦆', '。', '「', '」', '、', '・'}; //private static String[] longCharacters = new String[] {"𐄀", "𐄁", "𐄂", "𐎟", "𐏐", "𐕯", "𐡗", "𐤟", "𐤿", "𐩐", "𐩑", "𐩒", "𐩓", "𐩔", "𐩕", "𐩖", "𐩗", "𐩘", "𐩿", "𐫰", "𐫱", "𐫲", "𐫳", "𐫴", "𐫵", "𐫶", "𐬹", "𐬺", "𐬻", "𐬼", "𐬽", "𐬾", "𐬿", "𐮙", "𐮚", "𐮛", "𐮜", "𐽕", "𐽖", "𐽗", "𐽘", "𐽙", "𑁇", "𑁈", "𑁉", "𑁊", "𑁋", "𑁌", "𑁍", "𑂻", "𑂼", "𑂾", "𑂿", "𑃀", "𑃁", "𑅀", "𑅁", "𑅂", "𑅃", "𑅴", "𑅵", "𑇅", "𑇆", "𑇇", "𑇈", "𑇍", "𑇛", "𑇝", "𑇞", "𑇟", "𑈸", "𑈹", "𑈺", "𑈻", "𑈼", "𑈽", "𑊩", "𑑋", "𑑌", "𑑍", "𑑎", "𑑏", "𑑛", "𑑝", "𑓆", "𑗁", "𑗂", "𑗃", "𑗄", "𑗅", "𑗆", "𑗇", "𑗈", "𑗉", "𑗊", "𑗋", "𑗌", "𑗍", "𑗎", "𑗏", "𑗐", "𑗑", "𑗒", "𑗓", "𑗔", "𑗕", "𑗖", "𑗗", "𑙁", "𑙂", "𑙃", "𑙠", "𑙡", "𑙢", "𑙣", "𑙤", "𑙥", "𑙦", "𑙧", "𑙨", "𑙩", "𑙪", "𑙫", "𑙬", "𑜼", "𑜽", "𑜾", "𑠻", "𑧢", "𑨿", "𑩀", "𑩁", "𑩂", "𑩃", "𑩄", "𑩅", "𑩆", "𑪚", "𑪛", "𑪜", "𑪞", "𑪟", "𑪠", "𑪡", "𑪢", "𑱁", "𑱂", "𑱃", "𑱄", "𑱅", "𑱰", "𑱱", "𑻷", "𑻸", "𑿿", "𒑰", "𒑱", "𒑲", "𒑳", "𒑴", "𖩮", "𖩯", "𖫵", "𖬷", "𖬸", "𖬹", "𖬺", "𖬻", "𖭄", "𖺗", "𖺘", "𖺙", "𖺚", "𖿢", "𛲟", "𝪇", "𝪈", "𝪉", "𝪊", "𝪋", "𞥞", "𞥟"}; private static HashSet charactersMap; public static boolean isPunctuationCharacter(char ch) { if (charactersMap == null) { charactersMap = new HashSet<>(); for (int a = 0; a < characters.length; a++) { charactersMap.add(characters[a]); } } //int len = longCharacters[0].length(); return charactersMap.contains(ch); } public static int getColorDistance(int color1, int color2) { int r1 = Color.red(color1); int g1 = Color.green(color1); int b1 = Color.blue(color1); int r2 = Color.red(color2); int g2 = Color.green(color2); int b2 = Color.blue(color2); int rMean = (r1 + r2) / 2; int r = r1 - r2; int g = g1 - g2; int b = b1 - b2; return (((512 + rMean) * r * r) >> 8) + (4 * g * g) + (((767 - rMean) * b * b) >> 8); } public static int getAverageColor(int color1, int color2) { int r1 = Color.red(color1); int r2 = Color.red(color2); int g1 = Color.green(color1); int g2 = Color.green(color2); int b1 = Color.blue(color1); int b2 = Color.blue(color2); return Color.argb(255, (r1 / 2 + r2 / 2), (g1 / 2 + g2 / 2), (b1 / 2 + b2 / 2)); } public static void setLightStatusBar(Window window, boolean enable) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final View decorView = window.getDecorView(); int flags = decorView.getSystemUiVisibility(); if (enable) { if ((flags & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) == 0) { flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; decorView.setSystemUiVisibility(flags); if (!SharedConfig.noStatusBar) { window.setStatusBarColor(0x0f000000); } } } else { if ((flags & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) { flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; decorView.setSystemUiVisibility(flags); if (!SharedConfig.noStatusBar) { window.setStatusBarColor(0x33000000); } } } } } public static void setLightNavigationBar(Window window, boolean enable) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { final View decorView = window.getDecorView(); int flags = decorView.getSystemUiVisibility(); if (enable) { flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; } else { flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; } decorView.setSystemUiVisibility(flags); } } public static boolean checkHostForPunycode(String url) { boolean hasLatin = false; boolean hasNonLatin = false; try { for (int a = 0, N = url.length(); a < N; a++) { char ch = url.charAt(a); if (ch == '.' || ch == '-' || ch == '/' || ch == '+' || ch >= '0' && ch <= '9') { continue; } if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') { hasLatin = true; } else { hasNonLatin = true; } if (hasLatin && hasNonLatin) { break; } } } catch (Exception e) { FileLog.e(e); } return hasLatin && hasNonLatin; } public static boolean shouldShowUrlInAlert(String url) { try { Uri uri = Uri.parse(url); url = uri.getHost(); return checkHostForPunycode(url); } catch (Exception e) { FileLog.e(e); } return false; } public static void scrollToFragmentRow(ActionBarLayout parentLayout, String rowName) { if (parentLayout == null || rowName == null) { return; } BaseFragment openingFragment = parentLayout.fragmentsStack.get(parentLayout.fragmentsStack.size() - 1); try { Field listViewField = openingFragment.getClass().getDeclaredField("listView"); listViewField.setAccessible(true); RecyclerListView listView = (RecyclerListView) listViewField.get(openingFragment); RecyclerListView.IntReturnCallback callback = () -> { int position = -1; try { Field rowField = openingFragment.getClass().getDeclaredField(rowName); rowField.setAccessible(true); LinearLayoutManager layoutManager = (LinearLayoutManager) listView.getLayoutManager(); position = rowField.getInt(openingFragment); layoutManager.scrollToPositionWithOffset(position, AndroidUtilities.dp(60)); rowField.setAccessible(false); return position; } catch (Throwable ignore) { } return position; }; listView.highlightRow(callback); listViewField.setAccessible(false); } catch (Throwable ignore) { } } public static boolean checkInlinePermissions(Context context) { if (Build.VERSION.SDK_INT < 23 || Settings.canDrawOverlays(context)) { return true; } return false; } public static void updateVisibleRows(RecyclerListView listView) { if (listView == null) { return; } RecyclerView.Adapter adapter = listView.getAdapter(); if (adapter == null) { return; } for (int i = 0; i < listView.getChildCount(); i++) { View child = listView.getChildAt(i); int p = listView.getChildAdapterPosition(child); if (p >= 0) { RecyclerView.ViewHolder holder = listView.getChildViewHolder(child); if (holder == null || holder.shouldIgnore()) { continue; } adapter.onBindViewHolder(holder, p); } } } public static void updateViewVisibilityAnimated(View view, boolean show) { updateViewVisibilityAnimated(view, show, 1f, true); } public static void updateViewVisibilityAnimated(View view, boolean show, float scaleFactor, boolean animated) { if (view.getParent() == null) { animated = false; } if (show && view.getTag() == null) { view.animate().setListener(null).cancel(); if (animated) { if (view.getVisibility() != View.VISIBLE) { view.setVisibility(View.VISIBLE); view.setAlpha(0f); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } view.animate().alpha(1f).scaleY(1f).scaleX(1f).setDuration(150).start(); } else { view.setVisibility(View.VISIBLE); view.setAlpha(1f); view.setScaleX(1f); view.setScaleY(1f); } view.setTag(1); } else if (!show && view.getTag() != null){ view.animate().setListener(null).cancel(); if (animated) { view.animate().alpha(0).scaleY(scaleFactor).scaleX(scaleFactor).setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setVisibility(View.GONE); } }).setDuration(150).start(); } else { view.setVisibility(View.GONE); } view.setTag(null); } else if (!animated) { view.animate().setListener(null).cancel(); view.setVisibility(show ? View.VISIBLE : View.GONE); view.setTag(show ? 1 : null); view.setAlpha(1f); view.setScaleX(1f); view.setScaleY(1f); } } }