mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-07 03:05:18 +08:00
Ensure termux files directory is accessible before bootstrap installation and provide better info when running as secondary user/profile
Termux will check if termux files directory `/data/data/com.termux/files` has rwx permission access before installing bootstrap or starting terminal. Missing permission will automatically be set if possible. The `/data/data/com.termux` directory will also be created if it did not already exist, like if android did not already create it. Users will now also be shown a crash notification if they attempt to start termux as a secondary user or in a work profile with info of the "alternate" termux files directory `/data/user/<id>/com.termux` set by android and the profile owner app if running under work profile (not secondary user). A notification will also be shown if the termux files directory (not "alternate") is not accessible. Related #2168
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package com.termux.shared.file;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.termux.shared.models.errors.Error;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
|
||||
@@ -9,6 +12,7 @@ import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TermuxFileUtils {
|
||||
|
||||
/**
|
||||
* Replace "$PREFIX/" or "~/" prefix with termux absolute paths.
|
||||
*
|
||||
@@ -120,4 +124,41 @@ public class TermuxFileUtils {
|
||||
ignoreErrorsIfPathIsInParentDirPath, ignoreIfNotExecutable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the existence and permissions of {@link TermuxConstants#TERMUX_FILES_DIR_PATH}.
|
||||
*
|
||||
* The directory will not be created manually but by calling {@link Context#getFilesDir()}
|
||||
* so that android itself creates it. The `/data/data/[package_name]` directory cannot be
|
||||
* created by an app itself. Note that the path returned by {@link Context#getFilesDir()} will
|
||||
* be under `/data/user/[id]/[package_name]` instead of `/data/data/[package_name]`
|
||||
* defined by default by {@link TermuxConstants#TERMUX_FILES_DIR_PATH}, where id will be 0 for
|
||||
* primary user and a higher number for other users/profiles. If app is running under work profile
|
||||
* or secondary user, then {@link TermuxConstants#TERMUX_FILES_DIR_PATH} will not be accessible
|
||||
* and will not be automatically created, unless there is a bind mount from `/data/user/[id]`
|
||||
* to `/data/data`, ideally in the right namespace.
|
||||
*
|
||||
* The permissions set to directory will be {@link FileUtils#APP_WORKING_DIRECTORY_PERMISSIONS}.
|
||||
*
|
||||
* https://source.android.com/devices/tech/admin/multi-user
|
||||
*
|
||||
* @param context The {@link Context} for operations.
|
||||
* @param createDirectoryIfMissing The {@code boolean} that decides if directory file
|
||||
* should be created if its missing.
|
||||
* @param setMissingPermissions The {@code boolean} that decides if permissions are to be
|
||||
* automatically set.
|
||||
* @return Returns the {@code error} if path is not a directory file, failed to create it,
|
||||
* or validating permissions failed, otherwise {@code null}.
|
||||
*/
|
||||
public static Error isTermuxFilesDirectoryAccessible(@NonNull final Context context, boolean createDirectoryIfMissing, boolean setMissingPermissions) {
|
||||
if (createDirectoryIfMissing)
|
||||
context.getFilesDir();
|
||||
|
||||
if (setMissingPermissions)
|
||||
FileUtils.setMissingFilePermissions("Termux files directory", TermuxConstants.TERMUX_FILES_DIR_PATH,
|
||||
FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS);
|
||||
|
||||
return FileUtils.checkMissingFilePermissions("Termux files directory", TermuxConstants.TERMUX_FILES_DIR_PATH,
|
||||
FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,12 @@
|
||||
package com.termux.shared.packages;
|
||||
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -14,6 +17,7 @@ import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -163,7 +167,7 @@ public class PackageUtils {
|
||||
* Get the {@code SHA-256 digest} of signing certificate for the package associated with the {@code context}.
|
||||
*
|
||||
* @param context The {@link Context} for the package.
|
||||
* @return Returns the{@code SHA-256 digest}. This will be {@code null} if an exception is raised.
|
||||
* @return Returns the {@code SHA-256 digest}. This will be {@code null} if an exception is raised.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getSigningCertificateSHA256DigestForPackage(@NonNull final Context context) {
|
||||
@@ -184,4 +188,53 @@ public class PackageUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the serial number for the current user.
|
||||
*
|
||||
* @param context The {@link Context} for operations.
|
||||
* @return Returns the serial number. This will be {@code null} if failed to get it.
|
||||
*/
|
||||
@Nullable
|
||||
public static Long getSerialNumberForCurrentUser(@NonNull Context context) {
|
||||
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
|
||||
if (userManager == null) return null;
|
||||
return userManager.getSerialNumberForUser(android.os.Process.myUserHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user is the primary user. This is done by checking if the the serial
|
||||
* number for the current user equals 0.
|
||||
*
|
||||
* @param context The {@link Context} for operations.
|
||||
* @return Returns {@code true} if the current user is the primary user, otherwise [@code false}.
|
||||
*/
|
||||
public static boolean isCurrentUserThePrimaryUser(@NonNull Context context) {
|
||||
Long userId = getSerialNumberForCurrentUser(context);
|
||||
return userId != null && userId == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the profile owner package name for the current user.
|
||||
*
|
||||
* @param context The {@link Context} for operations.
|
||||
* @return Returns the profile owner package name. This will be {@code null} if failed to get it
|
||||
* or no profile owner for the current user.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getProfileOwnerPackageNameForUser(@NonNull Context context) {
|
||||
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
if (devicePolicyManager == null) return null;
|
||||
List<ComponentName> activeAdmins = devicePolicyManager.getActiveAdmins();
|
||||
if (activeAdmins != null){
|
||||
for (ComponentName admin:activeAdmins){
|
||||
String packageName = admin.getPackageName();
|
||||
if(devicePolicyManager.isProfileOwnerApp(packageName))
|
||||
return packageName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -40,6 +40,17 @@ public class AndroidUtils {
|
||||
AndroidUtils.appendPropertyToMarkdown(markdownString,"TARGET_SDK", PackageUtils.getTargetSDKForPackage(context));
|
||||
AndroidUtils.appendPropertyToMarkdown(markdownString,"IS_DEBUG_BUILD", PackageUtils.isAppForPackageADebugBuild(context));
|
||||
|
||||
String filesDir = context.getFilesDir().getAbsolutePath();
|
||||
if (!filesDir.equals("/data/user/0/" + context.getPackageName() + "/files") &&
|
||||
!filesDir.equals("/data/data/" + context.getPackageName() + "/files"))
|
||||
AndroidUtils.appendPropertyToMarkdown(markdownString,"FILES_DIR", filesDir);
|
||||
|
||||
Long userId = PackageUtils.getSerialNumberForCurrentUser(context);
|
||||
if (userId == null || userId != 0)
|
||||
AndroidUtils.appendPropertyToMarkdown(markdownString,"USER_ID", userId);
|
||||
|
||||
AndroidUtils.appendPropertyToMarkdownIfSet(markdownString,"PROFILE_OWNER", PackageUtils.getProfileOwnerPackageNameForUser(context));
|
||||
|
||||
return markdownString.toString();
|
||||
}
|
||||
|
||||
@@ -139,7 +150,7 @@ public class AndroidUtils {
|
||||
return systemProperties;
|
||||
}
|
||||
|
||||
private static String getSystemPropertyWithAndroidAPI(@NonNull String property) {
|
||||
public static String getSystemPropertyWithAndroidAPI(@NonNull String property) {
|
||||
try {
|
||||
return System.getProperty(property);
|
||||
} catch (Exception e) {
|
||||
@@ -148,17 +159,17 @@ public class AndroidUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendPropertyToMarkdownIfSet(StringBuilder markdownString, String label, Object value) {
|
||||
public 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));
|
||||
}
|
||||
|
||||
static void appendPropertyToMarkdown(StringBuilder markdownString, String label, Object value) {
|
||||
public static void appendPropertyToMarkdown(StringBuilder markdownString, String label, Object value) {
|
||||
markdownString.append("\n").append(getPropertyMarkdown(label, value));
|
||||
}
|
||||
|
||||
private static String getPropertyMarkdown(String label, Object value) {
|
||||
public static String getPropertyMarkdown(String label, Object value) {
|
||||
return MarkdownUtils.getSingleLineMarkdownStringEntry(label, value, "-");
|
||||
}
|
||||
|
||||
|
@@ -8,9 +8,11 @@ import android.content.pm.ResolveInfo;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.termux.shared.R;
|
||||
import com.termux.shared.file.TermuxFileUtils;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.markdown.MarkdownUtils;
|
||||
import com.termux.shared.models.ExecutionCommand;
|
||||
import com.termux.shared.models.errors.Error;
|
||||
import com.termux.shared.packages.PackageUtils;
|
||||
import com.termux.shared.shell.TermuxShellEnvironmentClient;
|
||||
import com.termux.shared.shell.TermuxTask;
|
||||
@@ -210,6 +212,13 @@ public class TermuxUtils {
|
||||
|
||||
markdownString.append((AndroidUtils.getAppInfoMarkdownString(context)));
|
||||
|
||||
Error error;
|
||||
error = TermuxFileUtils.isTermuxFilesDirectoryAccessible(context, true, true);
|
||||
if (error != null) {
|
||||
AndroidUtils.appendPropertyToMarkdown(markdownString, "TERMUX_FILES_DIR", TermuxConstants.TERMUX_FILES_DIR_PATH);
|
||||
AndroidUtils.appendPropertyToMarkdown(markdownString, "IS_TERMUX_FILES_DIR_ACCESSIBLE", "false - " + Error.getMinimalErrorString(error));
|
||||
}
|
||||
|
||||
String signingCertificateSHA256Digest = PackageUtils.getSigningCertificateSHA256DigestForPackage(context);
|
||||
if (signingCertificateSHA256Digest != null) {
|
||||
AndroidUtils.appendPropertyToMarkdown(markdownString,"APK_RELEASE", getAPKRelease(signingCertificateSHA256Digest));
|
||||
|
Reference in New Issue
Block a user