package com.termux.shared.termux; import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.Build; import androidx.annotation.NonNull; import com.google.common.base.Joiner; import com.termux.shared.R; import com.termux.shared.logger.Logger; import com.termux.shared.markdown.MarkdownUtils; import com.termux.shared.packages.PackageUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TermuxUtils { /** * Get the {@link Context} for {@link TermuxConstants#TERMUX_PACKAGE_NAME} package. * * @param context The {@link Context} to use to get the {@link Context} of the package. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ public static Context getTermuxPackageContext(@NonNull Context context) { return PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_PACKAGE_NAME); } /** * Get the {@link Context} for {@link TermuxConstants#TERMUX_API_PACKAGE_NAME} package. * * @param context The {@link Context} to use to get the {@link Context} of the package. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ public static Context getTermuxAPIPackageContext(@NonNull Context context) { return PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_API_PACKAGE_NAME); } /** * Get the {@link Context} for {@link TermuxConstants#TERMUX_BOOT_PACKAGE_NAME} package. * * @param context The {@link Context} to use to get the {@link Context} of the package. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ public static Context getTermuxBootPackageContext(@NonNull Context context) { return PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_BOOT_PACKAGE_NAME); } /** * Get the {@link Context} for {@link TermuxConstants#TERMUX_FLOAT_PACKAGE_NAME} package. * * @param context The {@link Context} to use to get the {@link Context} of the package. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ public static Context getTermuxFloatPackageContext(@NonNull Context context) { return PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_FLOAT_PACKAGE_NAME); } /** * Get the {@link Context} for {@link TermuxConstants#TERMUX_STYLING_PACKAGE_NAME} package. * * @param context The {@link Context} to use to get the {@link Context} of the package. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ public static Context getTermuxStylingPackageContext(@NonNull Context context) { return PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_STYLING_PACKAGE_NAME); } /** * Get the {@link Context} for {@link TermuxConstants#TERMUX_TASKER_PACKAGE_NAME} package. * * @param context The {@link Context} to use to get the {@link Context} of the package. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ public static Context getTermuxTaskerPackageContext(@NonNull Context context) { return PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_TASKER_PACKAGE_NAME); } /** * Get the {@link Context} for {@link TermuxConstants#TERMUX_WIDGET_PACKAGE_NAME} package. * * @param context The {@link Context} to use to get the {@link Context} of the package. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ public static Context getTermuxWidgetPackageContext(@NonNull Context context) { return PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_WIDGET_PACKAGE_NAME); } /** * Send the {@link TermuxConstants#BROADCAST_TERMUX_OPENED} broadcast to notify apps that Termux * app has been opened. * * @param context The Context to send the broadcast. */ public static void sendTermuxOpenedBroadcast(@NonNull Context context) { if (context == null) return; Intent broadcast = new Intent(TermuxConstants.BROADCAST_TERMUX_OPENED); List matches = context.getPackageManager().queryBroadcastReceivers(broadcast, 0); // send broadcast to registered Termux receivers // this technique is needed to work around broadcast changes that Oreo introduced for (ResolveInfo info : matches) { Intent explicitBroadcast = new Intent(broadcast); ComponentName cname = new ComponentName(info.activityInfo.applicationInfo.packageName, info.activityInfo.name); explicitBroadcast.setComponent(cname); context.sendBroadcast(explicitBroadcast); } } /** * Get a markdown {@link String} for the app info. If the {@code context} passed is different * from the {@link TermuxConstants#TERMUX_PACKAGE_NAME} package context, then this function * must have been called by a different package like a plugin, so we return info for both packages * if {@code returnTermuxPackageInfoToo} is {@code true}. * * @param currentPackageContext The context of current package. * @param returnTermuxPackageInfoToo If set to {@code true}, then will return info of the * {@link TermuxConstants#TERMUX_PACKAGE_NAME} package as well if its different from current package. * @return Returns the markdown {@link String}. */ public static String getAppInfoMarkdownString(@NonNull final Context currentPackageContext, final boolean returnTermuxPackageInfoToo) { if (currentPackageContext == null) return "null"; StringBuilder markdownString = new StringBuilder(); Context termuxPackageContext = getTermuxPackageContext(currentPackageContext); String termuxPackageName = null; String termuxAppName = null; if(termuxPackageContext != null) { termuxPackageName = PackageUtils.getPackageNameForPackage(termuxPackageContext); termuxAppName = PackageUtils.getAppNameForPackage(termuxPackageContext); } String currentPackageName = PackageUtils.getPackageNameForPackage(currentPackageContext); String currentAppName = PackageUtils.getAppNameForPackage(currentPackageContext); boolean isTermuxPackage = (termuxPackageName != null && termuxPackageName.equals(currentPackageName)); if(returnTermuxPackageInfoToo && !isTermuxPackage) markdownString.append("## ").append(currentAppName).append(" App Info (Current)\n"); else markdownString.append("## ").append(currentAppName).append(" App Info\n"); markdownString.append(getAppInfoMarkdownStringInner(currentPackageContext)); if(returnTermuxPackageInfoToo && !isTermuxPackage) { markdownString.append("\n\n## ").append(termuxAppName).append(" App Info\n"); markdownString.append(getAppInfoMarkdownStringInner(termuxPackageContext)); } markdownString.append("\n##\n"); return markdownString.toString(); } /** * Get a markdown {@link String} for the app info for the package associated with the {@code context}. * * @param context The context for operations for the package. * @return Returns the markdown {@link String}. */ public static String getAppInfoMarkdownStringInner(@NonNull final Context context) { StringBuilder markdownString = new StringBuilder(); appendPropertyToMarkdown(markdownString,"APP_NAME", PackageUtils.getAppNameForPackage(context)); appendPropertyToMarkdown(markdownString,"PACKAGE_NAME", PackageUtils.getPackageNameForPackage(context)); appendPropertyToMarkdown(markdownString,"VERSION_NAME", PackageUtils.getVersionNameForPackage(context)); appendPropertyToMarkdown(markdownString,"VERSION_CODE", PackageUtils.getVersionCodeForPackage(context)); appendPropertyToMarkdown(markdownString,"TARGET_SDK", PackageUtils.getTargetSDKForPackage(context)); appendPropertyToMarkdown(markdownString,"IS_DEBUG_BUILD", PackageUtils.isAppForPackageADebugBuild(context)); return markdownString.toString(); } /** * Get a markdown {@link String} for the device info. * * @param context The context for operations. * @return Returns the markdown {@link String}. */ public static String getDeviceInfoMarkdownString(@NonNull final Context context) { if (context == null) return "null"; // Some properties cannot be read with {@link System#getProperty(String)} but can be read // directly by running getprop command Properties systemProperties = getSystemProperties(); StringBuilder markdownString = new StringBuilder(); markdownString.append("## Device Info"); markdownString.append("\n\n### Software\n"); appendPropertyToMarkdown(markdownString,"OS_VERSION", getSystemPropertyWithAndroidAPI("os.version")); appendPropertyToMarkdown(markdownString, "SDK_INT", Build.VERSION.SDK_INT); // If its a release version if ("REL".equals(Build.VERSION.CODENAME)) appendPropertyToMarkdown(markdownString, "RELEASE", Build.VERSION.RELEASE); else appendPropertyToMarkdown(markdownString, "CODENAME", Build.VERSION.CODENAME); appendPropertyToMarkdown(markdownString, "INCREMENTAL", Build.VERSION.INCREMENTAL); appendPropertyToMarkdownIfSet(markdownString, "SECURITY_PATCH", systemProperties.getProperty("ro.build.version.security_patch")); appendPropertyToMarkdownIfSet(markdownString, "IS_DEBUGGABLE", systemProperties.getProperty("ro.debuggable")); appendPropertyToMarkdownIfSet(markdownString, "IS_EMULATOR", systemProperties.getProperty("ro.boot.qemu")); appendPropertyToMarkdownIfSet(markdownString, "IS_TREBLE_ENABLED", systemProperties.getProperty("ro.treble.enabled")); appendPropertyToMarkdown(markdownString, "TYPE", Build.TYPE); appendPropertyToMarkdown(markdownString, "TAGS", Build.TAGS); markdownString.append("\n\n### Hardware\n"); appendPropertyToMarkdown(markdownString, "MANUFACTURER", Build.MANUFACTURER); appendPropertyToMarkdown(markdownString, "BRAND", Build.BRAND); appendPropertyToMarkdown(markdownString, "MODEL", Build.MODEL); appendPropertyToMarkdown(markdownString, "PRODUCT", Build.PRODUCT); appendPropertyToMarkdown(markdownString, "DISPLAY", Build.DISPLAY); appendPropertyToMarkdown(markdownString, "ID", Build.ID); appendPropertyToMarkdown(markdownString, "BOARD", Build.BOARD); appendPropertyToMarkdown(markdownString, "HARDWARE", Build.HARDWARE); appendPropertyToMarkdown(markdownString, "DEVICE", Build.DEVICE); appendPropertyToMarkdown(markdownString, "SUPPORTED_ABIS", Joiner.on(", ").skipNulls().join(Build.SUPPORTED_ABIS)); markdownString.append("\n##\n"); return markdownString.toString(); } /** * Get a markdown {@link String} for reporting an issue. * * @param context The context for operations. * @return Returns the markdown {@link String}. */ public static String getReportIssueMarkdownString(@NonNull final Context context) { if (context == null) return "null"; StringBuilder markdownString = new StringBuilder(); markdownString.append("## Where To Report An Issue"); markdownString.append("\n\n").append(context.getString(R.string.msg_report_issue)).append("\n"); //markdownString.append("\n\n### Email\n"); //markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_SUPPORT_EMAIL, TermuxConstants.TERMUX_SUPPORT_EMAIL_MAILTO_URL)).append(" "); markdownString.append("\n\n### Reddit\n"); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_REDDIT_SUBREDDIT, TermuxConstants.TERMUX_REDDIT_SUBREDDIT_URL)).append(" "); markdownString.append("\n\n### Github Issues for Termux apps\n"); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_APP_NAME, TermuxConstants.TERMUX_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_API_APP_NAME, TermuxConstants.TERMUX_API_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_BOOT_APP_NAME, TermuxConstants.TERMUX_BOOT_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_FLOAT_APP_NAME, TermuxConstants.TERMUX_FLOAT_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_STYLING_APP_NAME, TermuxConstants.TERMUX_STYLING_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_TASKER_APP_NAME, TermuxConstants.TERMUX_TASKER_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_WIDGET_APP_NAME, TermuxConstants.TERMUX_WIDGET_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n\n### Github Issues for Termux packages\n"); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_GAME_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_GAME_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_SCIENCE_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_SCIENCE_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_ROOT_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_ROOT_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_UNSTABLE_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_UNSTABLE_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_X11_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_X11_PACKAGES_GITHUB_ISSUES_REPO_URL)).append(" "); markdownString.append("\n##\n"); return markdownString.toString(); } public static Properties getSystemProperties() { Properties systemProperties = new Properties(); // getprop commands returns values in the format `[key]: [value]` // Regex matches string starting with a literal `[`, // followed by one or more characters that do not match a closing square bracket as the key, // followed by a literal `]: [`, // followed by one or more characters as the value, // followed by string ending with literal `]` // multiline values will be ignored Pattern propertiesPattern = Pattern.compile("^\\[([^]]+)]: \\[(.+)]$"); try { Process process = new ProcessBuilder() .command("/system/bin/getprop") .redirectErrorStream(true) .start(); InputStream inputStream = process.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line, key, value; while ((line = bufferedReader.readLine()) != null) { Matcher matcher = propertiesPattern.matcher(line); if (matcher.matches()) { key = matcher.group(1); value = matcher.group(2); if(key != null && value != null && !key.isEmpty() && !value.isEmpty()) systemProperties.put(key, value); } } bufferedReader.close(); process.destroy(); } catch (IOException e) { Logger.logStackTraceWithMessage("Failed to get run \"/system/bin/getprop\" to get system properties.", e); } //for (String key : systemProperties.stringPropertyNames()) { // Logger.logVerbose(key + ": " + systemProperties.get(key)); //} return systemProperties; } private static String getSystemPropertyWithAndroidAPI(@NonNull String property) { try { return System.getProperty(property); } catch (Exception e) { Logger.logVerbose("Failed to get system property \"" + property + "\":" + e.getMessage()); return null; } } private static void appendPropertyToMarkdownIfSet(StringBuilder markdownString, String label, Object value) { if(value == null) return; if(value instanceof String && (((String) value).isEmpty()) || "REL".equals(value)) return; markdownString.append("\n").append(getPropertyMarkdown(label, value)); } private static void appendPropertyToMarkdown(StringBuilder markdownString, String label, Object value) { markdownString.append("\n").append(getPropertyMarkdown(label, value)); } private static String getPropertyMarkdown(String label, Object value) { return MarkdownUtils.getSingleLineMarkdownStringEntry(label, value, "-"); } public static String getCurrentTimeStamp() { @SuppressLint("SimpleDateFormat") final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); df.setTimeZone(TimeZone.getTimeZone("UTC")); return df.format(new Date()); } }