From 60f07718880bdd8d241f3c13126343b0b406c818 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Wed, 12 May 2021 20:21:10 +0200 Subject: [PATCH] [WIP] Introduce an 'updated' APK packages flavor --- app/build.gradle | 83 +++++++++-- .../java/com/termux/app/TermuxInstaller.java | 2 +- .../java/com/termux/app/TermuxService.java | 7 + app/src/updated/AndroidManifest.xml | 19 +++ .../termux/app/TermuxPackageInstaller.java | 138 ++++++++++++++++++ .../app/TermuxPackageInstallerService.java | 56 +++++++ gradle.properties | 1 + 7 files changed, 296 insertions(+), 10 deletions(-) create mode 100644 app/src/updated/AndroidManifest.xml create mode 100644 app/src/updated/java/com/termux/app/TermuxPackageInstaller.java create mode 100644 app/src/updated/java/com/termux/app/TermuxPackageInstallerService.java diff --git a/app/build.gradle b/app/build.gradle index 30124a4a..aae69ede 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,18 @@ plugins { } android { + flavorDimensions "version" + productFlavors { + current { + dimension "version" + targetSdkVersion project.properties.targetSdkVersion.toInteger() + } + updated { + dimension "version" + targetSdkVersion project.properties.updatedTargetSdkVersion.toInteger() + } + } + compileSdkVersion project.properties.compileSdkVersion.toInteger() ndkVersion project.properties.ndkVersion @@ -25,7 +37,6 @@ android { defaultConfig { applicationId "com.termux" minSdkVersion project.properties.minSdkVersion.toInteger() - targetSdkVersion project.properties.targetSdkVersion.toInteger() versionCode 112 versionName "0.112" @@ -104,11 +115,62 @@ task versionName { } } -def downloadBootstrap(String arch, String expectedChecksum, String version) { +def expandBootstrap(File bootstrapZip, String expectedChecksum, String arch) { + def doneMarkerFile = new File(bootstrapZip.getAbsolutePath() + "." + expectedChecksum + ".done") + if (doneMarkerFile.exists()) return + + def archDirName + if (arch == "aarch64") archDirName = "arm64-v8a"; + if (arch == "arm") archDirName = "armeabi-v7a"; + if (arch == "i686") archDirName = "x86"; + if (arch == "x86_64") archDirName = "x86_64"; + + def outputPath = project.getRootDir().getAbsolutePath() + "/app/src/main/jniLibs/" + archDirName + "/" + def outputDir = new File(outputPath).getAbsoluteFile() + if (!outputDir.exists()) outputDir.mkdirs() + + def symlinksFile = new File(outputDir, "libsymlinks.so").getAbsoluteFile() + if (symlinksFile.exists()) symlinksFile.delete(); + + def mappingsFile = new File(outputDir, "libfiles.so").getAbsoluteFile() + if (mappingsFile.exists()) mappingsFile.delete() + mappingsFile.createNewFile() + def mappingsFileWriter = new BufferedWriter(new FileWriter(mappingsFile)) + + def counter = 100 + new java.util.zip.ZipInputStream(new FileInputStream(bootstrapZip)).withCloseable { zipInput -> + def zipEntry + while ((zipEntry = zipInput.getNextEntry()) != null) { + if (zipEntry.getName() == "SYMLINKS.txt") { + zipInput.transferTo(new FileOutputStream(symlinksFile)) + } else if (!zipEntry.isDirectory()) { + def soName = "lib" + counter + ".so" + def targetFile = new File(outputDir, soName).getAbsoluteFile() + + println "target file path is ${targetFile}" + + try { + zipInput.transferTo(new FileOutputStream(targetFile)) + } catch (Exception e) { + println "Error ${e}" + } + + + mappingsFileWriter.writeLine(soName + "←" + zipEntry.getName()) + counter++ + } + } + } + + mappingsFileWriter.close() + doneMarkerFile.createNewFile() +} + +def downloadBootstrap(String arch, String expectedChecksum, String version, boolean isPackagesInApk) { def digest = java.security.MessageDigest.getInstance("SHA-256") - def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip" - def file = new File(projectDir, localUrl) + def file = isPackagesInApk ? new File(project.buildDir, "./gradle/bootstrap-" + arch + "-" + version + ".zip") + : new File(projectDir, "src/main/cpp/bootstrap-" + arch + ".zip"); if (file.exists()) { def buffer = new byte[8192] def input = new FileInputStream(file) @@ -119,9 +181,10 @@ def downloadBootstrap(String arch, String expectedChecksum, String version) { } def checksum = new BigInteger(1, digest.digest()).toString(16) if (checksum == expectedChecksum) { + if (isPackagesInApk) expandBootstrap(file, expectedChecksum, arch) return } else { - logger.quiet("Deleting old local file with wrong hash: " + localUrl) + logger.quiet("Deleting old local file with wrong hash: " + file) file.delete() } } @@ -143,6 +206,7 @@ def downloadBootstrap(String arch, String expectedChecksum, String version) { file.delete() throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum) } + if (isPackagesInApk) expandBootstrap(file, expectedChecksum, arch) } clean { @@ -154,12 +218,13 @@ clean { } task downloadBootstraps() { + boolean isPackagesInApk = getGradle().getStartParameter().getTaskRequests().toString().contains("Updated"); doLast { def version = "2021.04.13-r1" - downloadBootstrap("aarch64", "ff82e5755d947cd1f3e0b30916d125c6ddd8ba3254801ca7499d73653417e158", version) - downloadBootstrap("arm", "53a7df2d6d0a36a8c9ab5259c8b5457c93b8bae8aec2321a470236b6da54e59a", version) - downloadBootstrap("i686", "f0e1399a13ebed6c5229fde161f9848d9f5eeae7b8cd82f31250a813b52e371", version) - downloadBootstrap("x86_64", "e36c4d8c933dc12b3f48937b7747c7a4dcfaa70f0dd89ad5e8b4465930075ae9", version) + downloadBootstrap("aarch64", "ff82e5755d947cd1f3e0b30916d125c6ddd8ba3254801ca7499d73653417e158", version, isPackagesInApk) + downloadBootstrap("arm", "53a7df2d6d0a36a8c9ab5259c8b5457c93b8bae8aec2321a470236b6da54e59a", version, isPackagesInApk) + downloadBootstrap("i686", "f0e1399a13ebed6c5229fde161f9848d9f5eeae7b8cd82f31250a813b52e371", version, isPackagesInApk) + downloadBootstrap("x86_64", "e36c4d8c933dc12b3f48937b7747c7a4dcfaa70f0dd89ad5e8b4465930075ae9", version, isPackagesInApk) } } diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index a600e534..f2b245cd 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -246,7 +246,7 @@ final class TermuxInstaller { }.start(); } - private static void ensureDirectoryExists(Context context, File directory) { + public static void ensureDirectoryExists(Context context, File directory) { String errmsg; errmsg = FileUtils.createDirectoryFile(context, directory.getAbsolutePath()); diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 39c92023..7266d9f1 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -6,6 +6,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ActivityNotFoundException; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -112,6 +113,12 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas public void onCreate() { Logger.logVerbose(LOG_TAG, "onCreate"); runStartForeground(); + + if (getApplicationContext().getApplicationInfo().targetSdkVersion >= 30) { + Intent intent = new Intent(); + intent.setComponent(new ComponentName("com.termux","com.termux.app.TermuxPackageInstallerService")); + startService(intent); + } } @SuppressLint("Wakelock") diff --git a/app/src/updated/AndroidManifest.xml b/app/src/updated/AndroidManifest.xml new file mode 100644 index 00000000..cd937e44 --- /dev/null +++ b/app/src/updated/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/app/src/updated/java/com/termux/app/TermuxPackageInstaller.java b/app/src/updated/java/com/termux/app/TermuxPackageInstaller.java new file mode 100644 index 00000000..24601649 --- /dev/null +++ b/app/src/updated/java/com/termux/app/TermuxPackageInstaller.java @@ -0,0 +1,138 @@ +package com.termux.app; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Process; +import android.system.Os; +import android.util.Log; + +import com.termux.shared.termux.TermuxConstants; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class TermuxPackageInstaller extends BroadcastReceiver { + + private static final String LOG_TAG = "termux-package-installer"; + + + @Override + public void onReceive(Context context, Intent intent) { + try { + String packageName = intent.getData().getSchemeSpecificPart(); + String action = intent.getAction(); + PackageManager packageManager = context.getPackageManager(); + + if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + ApplicationInfo info = packageManager.getApplicationInfo(packageName, 0); + if (Process.myUid() == info.uid) { + installPackage(context, info); + } + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + if (Process.myUid() == intent.getIntExtra(Intent.EXTRA_UID, -1)) { + uninstallPackage(packageName); + } + + } + } catch (Exception e) { + Log.e("termux", "Error in package management: " + e); + } + } + + static void installPackage(Context context, ApplicationInfo info) throws Exception { + File filesMappingFile = new File(info.nativeLibraryDir, "libfiles.so"); + if (!filesMappingFile.exists()) { + Log.e("termux", "No file mapping at " + filesMappingFile.getAbsolutePath()); + return; + } + + Log.e("termux", "Installing: " + info.packageName); + BufferedReader reader = new BufferedReader(new FileReader(filesMappingFile)); + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split("←"); + if (parts.length != 2) { + Log.e(LOG_TAG, "Malformed line " + line + " in " + filesMappingFile.getAbsolutePath()); + continue; + } + + String oldPath = info.nativeLibraryDir + "/" + parts[0]; + String newPath = TermuxConstants.TERMUX_PREFIX_DIR_PATH + "/" + parts[1]; + + TermuxInstaller.ensureDirectoryExists(context, new File(newPath).getParentFile()); + + Log.e(LOG_TAG, "About to setup link: " + oldPath + " ← " + newPath); + new File(newPath).delete(); + Os.symlink(oldPath, newPath); + } + + File symlinksFile = new File(info.nativeLibraryDir, "libsymlinks.so"); + if (!symlinksFile.exists()) { + Log.e("termux", "No symlinks mapping at " + symlinksFile.getAbsolutePath()); + } + + reader = new BufferedReader(new FileReader(symlinksFile)); + while ((line = reader.readLine()) != null) { + String[] parts = line.split("←"); + if (parts.length != 2) { + Log.e(LOG_TAG, "Malformed line " + line + " in " + symlinksFile.getAbsolutePath()); + continue; + } + + String oldPath = parts[0]; + String newPath = TermuxConstants.TERMUX_PREFIX_DIR_PATH + "/" + parts[1]; + + TermuxInstaller.ensureDirectoryExists(context, new File(newPath).getParentFile()); + + Log.e(LOG_TAG, "About to setup link: " + oldPath + " ← " + newPath); + new File(newPath).delete(); + Os.symlink(oldPath, newPath); + } + } + + private static void uninstallPackage(String packageName) throws IOException { + Log.e(LOG_TAG, "Uninstalling: " + packageName); + // We're currently visiting the whole $PREFIX. + // If we store installed symlinks in installPackage() we could just visit those, + // at the cost of increased complexity and risk for errors. + File prefixDir = new File(TermuxConstants.TERMUX_PREFIX_DIR_PATH); + removeBrokenSymlinks(prefixDir); + } + + private static void removeBrokenSymlinks(File parentDir) throws IOException { + File[] children = parentDir.listFiles(); + if (children == null) { + return; + } + for (File child : children) { + if (!child.exists()) { + Log.e(LOG_TAG, "Removing broken symlink: " + child.getAbsolutePath()); + child.delete(); + } else if (child.isDirectory()) { + removeBrokenSymlinks(child); + } + } + } + + public static void setupAllInstalledPackages(Context context) { + try { + removeBrokenSymlinks(new File(TermuxConstants.TERMUX_PREFIX_DIR_PATH)); + + PackageManager packageManager = context.getPackageManager(); + for (PackageInfo info : packageManager.getInstalledPackages(0)) { + if ("com.termux".equals(info.sharedUserId)) { + installPackage(context, info.applicationInfo); + } + } + } catch (Exception e) { + Log.e(LOG_TAG, "Error setting up all packages", e); + } + + } +} diff --git a/app/src/updated/java/com/termux/app/TermuxPackageInstallerService.java b/app/src/updated/java/com/termux/app/TermuxPackageInstallerService.java new file mode 100644 index 00000000..2cb92d14 --- /dev/null +++ b/app/src/updated/java/com/termux/app/TermuxPackageInstallerService.java @@ -0,0 +1,56 @@ +package com.termux.app; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.Process; +import android.system.Os; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.termux.shared.termux.TermuxConstants; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +public class TermuxPackageInstallerService extends Service { + + private static final String LOG_TAG = "termux-package-installer"; + + class LocalBinder extends Binder { + public final TermuxPackageInstallerService service = TermuxPackageInstallerService.this; + } + + private final IBinder mBinder = new LocalBinder(); + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public void onCreate() { + super.onCreate(); + + TermuxPackageInstaller packageInstaller = new TermuxPackageInstaller(); + TermuxPackageInstaller.setupAllInstalledPackages(this); + + IntentFilter addedFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + addedFilter.addDataScheme("package"); + IntentFilter removedFilter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); + removedFilter.addDataScheme("package"); + this.registerReceiver(packageInstaller, addedFilter); + this.registerReceiver(packageInstaller, removedFilter); + } +} diff --git a/gradle.properties b/gradle.properties index 5db95191..3e68508c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,6 +20,7 @@ termuxVersionCode=111 minSdkVersion=24 targetSdkVersion=28 +updatedTargetSdkVersion=30 ndkVersion=22.1.7171670 compileSdkVersion=30