diff --git a/termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java b/termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java index fadcb7d9..10886a41 100644 --- a/termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/reflection/ReflectionUtils.java @@ -77,6 +77,8 @@ public class ReflectionUtils { /** * Get a value for a {@link Field} of an object for the specified class. * + * Trying to access {@code null} fields will result in {@link NoSuchFieldException}. + * * @param clazz The {@link Class} to which the object belongs to. * @param fieldName The name of the {@link Field}. * @param object The {@link Object} instance from which to get the field value. diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxBootstrap.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxBootstrap.java index 50ccef15..add3f040 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxBootstrap.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxBootstrap.java @@ -1,20 +1,29 @@ package com.termux.shared.termux; +import android.content.Context; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants.TERMUX_APP; public class TermuxBootstrap { private static final String LOG_TAG = "TermuxBootstrap"; + /** The field name used by Termux app to store package variant in + * {@link TERMUX_APP#BUILD_CONFIG_CLASS_NAME} class. */ + public static final String BUILD_CONFIG_FIELD_TERMUX_PACKAGE_VARIANT = "TERMUX_PACKAGE_VARIANT"; + + /** The {@link PackageManager} for the bootstrap in the app APK added in app/build.gradle. */ public static PackageManager TERMUX_APP_PACKAGE_MANAGER; /** The {@link PackageVariant} for the bootstrap in the app APK added in app/build.gradle. */ public static PackageVariant TERMUX_APP_PACKAGE_VARIANT; - /** Set name as app wide night mode value. */ + /** Set {@link #TERMUX_APP_PACKAGE_VARIANT} and {@link #TERMUX_APP_PACKAGE_MANAGER} from {@code packageVariantName} passed. */ public static void setTermuxPackageManagerAndVariant(@Nullable String packageVariantName) { TERMUX_APP_PACKAGE_VARIANT = PackageVariant.variantOf(packageVariantName); if (TERMUX_APP_PACKAGE_VARIANT == null) { @@ -34,6 +43,42 @@ public class TermuxBootstrap { Logger.logVerbose(LOG_TAG, "Set TERMUX_APP_PACKAGE_MANAGER to \"" + TERMUX_APP_PACKAGE_MANAGER + "\""); } + /** + * Set {@link #TERMUX_APP_PACKAGE_VARIANT} and {@link #TERMUX_APP_PACKAGE_MANAGER} with the + * {@link #BUILD_CONFIG_FIELD_TERMUX_PACKAGE_VARIANT} field value from the + * {@link TERMUX_APP#BUILD_CONFIG_CLASS_NAME} class of the Termux app APK installed on the device. + * This can only be used by apps that share `sharedUserId` with the Termux app and can be used + * by plugin apps. + * + * @param currentPackageContext The context of current package. + */ + public static void setTermuxPackageManagerAndVariantFromTermuxApp(@NonNull Context currentPackageContext) { + String packageVariantName = getTermuxAppBuildConfigPackageVariantFromTermuxApp(currentPackageContext); + if (packageVariantName != null) { + TermuxBootstrap.setTermuxPackageManagerAndVariant(packageVariantName); + } else { + Logger.logError(LOG_TAG, "Failed to set TERMUX_APP_PACKAGE_VARIANT and TERMUX_APP_PACKAGE_MANAGER from the termux app"); + } + } + + /** + * Get {@link #BUILD_CONFIG_FIELD_TERMUX_PACKAGE_VARIANT} field value from the + * {@link TERMUX_APP#BUILD_CONFIG_CLASS_NAME} class of the Termux app APK installed on the device. + * This can only be used by apps that share `sharedUserId` with the Termux app. + * + * @param currentPackageContext The context of current package. + * @return Returns the field value, otherwise {@code null} if an exception was raised or failed + * to get termux app package context. + */ + public static String getTermuxAppBuildConfigPackageVariantFromTermuxApp(@NonNull Context currentPackageContext) { + try { + return (String) TermuxUtils.getTermuxAppAPKBuildConfigClassField(currentPackageContext, BUILD_CONFIG_FIELD_TERMUX_PACKAGE_VARIANT); + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get \"" + BUILD_CONFIG_FIELD_TERMUX_PACKAGE_VARIANT + "\" value from \"" + TERMUX_APP.BUILD_CONFIG_CLASS_NAME + "\" class", e); + return null; + } + } + /** Is {@link PackageManager#APT} set as {@link #TERMUX_APP_PACKAGE_MANAGER}. */ diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 75c91b6c..ab142bf7 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -11,7 +11,7 @@ import java.util.Formatter; import java.util.List; /* - * Version: v0.44.0 + * Version: v0.45.0 * SPDX-License-Identifier: MIT * * Changelog @@ -248,6 +248,9 @@ import java.util.List; * * - 0.44.0 (2022-05-29) * - Changed `TERMUX_APP.APPS_DIR_PATH` basename from `termux-app` to `com.termux`. + * + * - 0.45.0 (2022-06-01) + * - Added `TERMUX_APP.BUILD_CONFIG_CLASS_NAME`. */ /** @@ -904,6 +907,9 @@ public final class TermuxConstants { /** termux-am socket file path */ public static final String TERMUX_AM_SOCKET_FILE_PATH = APPS_DIR_PATH + "/termux-am/am.sock"; // Default: "/data/data/com.termux/files/apps/com.termux/termux-am/am.sock" + /** Termux app BuildConfig class name */ + public static final String BUILD_CONFIG_CLASS_NAME = TERMUX_PACKAGE_NAME + ".BuildConfig"; // Default: "com.termux.BuildConfig" + /** Termux app core activity name. */ public static final String TERMUX_ACTIVITY_NAME = TERMUX_PACKAGE_NAME + ".app.TermuxActivity"; // Default: "com.termux.app.TermuxActivity" diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java index cc60b1cf..42208ac1 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java @@ -14,6 +14,7 @@ import com.termux.shared.R; import com.termux.shared.android.AndroidUtils; import com.termux.shared.data.DataUtils; import com.termux.shared.file.FileUtils; +import com.termux.shared.reflection.ReflectionUtils; import com.termux.shared.shell.command.runner.app.AppShell; import com.termux.shared.termux.file.TermuxFileUtils; import com.termux.shared.logger.Logger; @@ -21,6 +22,7 @@ import com.termux.shared.markdown.MarkdownUtils; import com.termux.shared.shell.command.ExecutionCommand; import com.termux.shared.errors.Error; import com.termux.shared.android.PackageUtils; +import com.termux.shared.termux.TermuxConstants.TERMUX_APP; import com.termux.shared.termux.shell.TermuxShellEnvironmentClient; import org.apache.commons.io.IOUtils; @@ -218,6 +220,59 @@ public class TermuxUtils { + /** + * Get a field value from the {@link TERMUX_APP#BUILD_CONFIG_CLASS_NAME} class of the Termux app + * APK installed on the device. + * This can only be used by apps that share `sharedUserId` with the Termux app. + * + * This is a wrapper for {@link #getTermuxAppAPKClassField(Context, String, String)}. + * + * @param currentPackageContext The context of current package. + * @param fieldName The name of the field to get. + * @return Returns the field value, otherwise {@code null} if an exception was raised or failed + * to get termux app package context. + */ + public static Object getTermuxAppAPKBuildConfigClassField(@NonNull Context currentPackageContext, + @NonNull String fieldName) { + return getTermuxAppAPKClassField(currentPackageContext, TERMUX_APP.BUILD_CONFIG_CLASS_NAME, fieldName); + } + + /** + * Get a field value from a class of the Termux app APK installed on the device. + * This can only be used by apps that share `sharedUserId` with the Termux app. + * + * This is done by getting first getting termux app package context and then getting in class + * loader (instead of current app's) that contains termux app class info, and then using that to + * load the required class and then getting required field from it. + * + * Note that the value returned is from the APK file and not the current value loaded in Termux + * app process, so only default values will be returned. + * + * Trying to access {@code null} fields will result in {@link NoSuchFieldException}. + * + * @param currentPackageContext The context of current package. + * @param clazzName The name of the class from which to get the field. + * @param fieldName The name of the field to get. + * @return Returns the field value, otherwise {@code null} if an exception was raised or failed + * to get termux app package context. + */ + public static Object getTermuxAppAPKClassField(@NonNull Context currentPackageContext, + @NonNull String clazzName, @NonNull String fieldName) { + try { + Context termuxPackageContext = TermuxUtils.getTermuxPackageContextWithCode(currentPackageContext); + if (termuxPackageContext == null) + return null; + + Class clazz = termuxPackageContext.getClassLoader().loadClass(clazzName); + return ReflectionUtils.invokeField(clazz, fieldName, null).value; + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get \"" + fieldName + "\" value from \"" + clazzName + "\" class", e); + return null; + } + } + + + /** * Send the {@link TermuxConstants#BROADCAST_TERMUX_OPENED} broadcast to notify apps that Termux * app has been opened.