mirror of https://github.com/termux/termux-app
[WIP] Introduce an 'updated' APK packages flavor
This commit is contained in:
parent
e6dac93352
commit
60f0771888
|
@ -3,6 +3,18 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
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()
|
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||||
ndkVersion project.properties.ndkVersion
|
ndkVersion project.properties.ndkVersion
|
||||||
|
|
||||||
|
@ -25,7 +37,6 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.termux"
|
applicationId "com.termux"
|
||||||
minSdkVersion project.properties.minSdkVersion.toInteger()
|
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||||
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
|
||||||
versionCode 112
|
versionCode 112
|
||||||
versionName "0.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 digest = java.security.MessageDigest.getInstance("SHA-256")
|
||||||
|
|
||||||
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
|
def file = isPackagesInApk ? new File(project.buildDir, "./gradle/bootstrap-" + arch + "-" + version + ".zip")
|
||||||
def file = new File(projectDir, localUrl)
|
: new File(projectDir, "src/main/cpp/bootstrap-" + arch + ".zip");
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
def buffer = new byte[8192]
|
def buffer = new byte[8192]
|
||||||
def input = new FileInputStream(file)
|
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)
|
def checksum = new BigInteger(1, digest.digest()).toString(16)
|
||||||
if (checksum == expectedChecksum) {
|
if (checksum == expectedChecksum) {
|
||||||
|
if (isPackagesInApk) expandBootstrap(file, expectedChecksum, arch)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.quiet("Deleting old local file with wrong hash: " + localUrl)
|
logger.quiet("Deleting old local file with wrong hash: " + file)
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,6 +206,7 @@ def downloadBootstrap(String arch, String expectedChecksum, String version) {
|
||||||
file.delete()
|
file.delete()
|
||||||
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
|
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
|
||||||
}
|
}
|
||||||
|
if (isPackagesInApk) expandBootstrap(file, expectedChecksum, arch)
|
||||||
}
|
}
|
||||||
|
|
||||||
clean {
|
clean {
|
||||||
|
@ -154,12 +218,13 @@ clean {
|
||||||
}
|
}
|
||||||
|
|
||||||
task downloadBootstraps() {
|
task downloadBootstraps() {
|
||||||
|
boolean isPackagesInApk = getGradle().getStartParameter().getTaskRequests().toString().contains("Updated");
|
||||||
doLast {
|
doLast {
|
||||||
def version = "2021.04.13-r1"
|
def version = "2021.04.13-r1"
|
||||||
downloadBootstrap("aarch64", "ff82e5755d947cd1f3e0b30916d125c6ddd8ba3254801ca7499d73653417e158", version)
|
downloadBootstrap("aarch64", "ff82e5755d947cd1f3e0b30916d125c6ddd8ba3254801ca7499d73653417e158", version, isPackagesInApk)
|
||||||
downloadBootstrap("arm", "53a7df2d6d0a36a8c9ab5259c8b5457c93b8bae8aec2321a470236b6da54e59a", version)
|
downloadBootstrap("arm", "53a7df2d6d0a36a8c9ab5259c8b5457c93b8bae8aec2321a470236b6da54e59a", version, isPackagesInApk)
|
||||||
downloadBootstrap("i686", "f0e1399a13ebed6c5229fde161f9848d9f5eeae7b8cd82f31250a813b52e371", version)
|
downloadBootstrap("i686", "f0e1399a13ebed6c5229fde161f9848d9f5eeae7b8cd82f31250a813b52e371", version, isPackagesInApk)
|
||||||
downloadBootstrap("x86_64", "e36c4d8c933dc12b3f48937b7747c7a4dcfaa70f0dd89ad5e8b4465930075ae9", version)
|
downloadBootstrap("x86_64", "e36c4d8c933dc12b3f48937b7747c7a4dcfaa70f0dd89ad5e8b4465930075ae9", version, isPackagesInApk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,7 @@ final class TermuxInstaller {
|
||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ensureDirectoryExists(Context context, File directory) {
|
public static void ensureDirectoryExists(Context context, File directory) {
|
||||||
String errmsg;
|
String errmsg;
|
||||||
|
|
||||||
errmsg = FileUtils.createDirectoryFile(context, directory.getAbsolutePath());
|
errmsg = FileUtils.createDirectoryFile(context, directory.getAbsolutePath());
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
@ -112,6 +113,12 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
Logger.logVerbose(LOG_TAG, "onCreate");
|
Logger.logVerbose(LOG_TAG, "onCreate");
|
||||||
runStartForeground();
|
runStartForeground();
|
||||||
|
|
||||||
|
if (getApplicationContext().getApplicationInfo().targetSdkVersion >= 30) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setComponent(new ComponentName("com.termux","com.termux.app.TermuxPackageInstallerService"));
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("Wakelock")
|
@SuppressLint("Wakelock")
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.termux">
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.software.leanback"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<service
|
||||||
|
android:name=".app.TermuxPackageInstallerService"
|
||||||
|
android:exported="false" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ termuxVersionCode=111
|
||||||
|
|
||||||
minSdkVersion=24
|
minSdkVersion=24
|
||||||
targetSdkVersion=28
|
targetSdkVersion=28
|
||||||
|
updatedTargetSdkVersion=30
|
||||||
ndkVersion=22.1.7171670
|
ndkVersion=22.1.7171670
|
||||||
compileSdkVersion=30
|
compileSdkVersion=30
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue