Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Android 10+ support #1427

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/debug_build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build
name: APK

on:
push:
Expand All @@ -14,6 +14,10 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Setup java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build
run: |
./gradlew assembleDebug
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Setup java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Execute tests
run: |
./gradlew test
129 changes: 76 additions & 53 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,6 @@ android {
targetSdkVersion project.properties.targetSdkVersion.toInteger()
versionCode 98
versionName "0.98"

externalNativeBuild {
ndkBuild {
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
}
}

ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}

}

signingConfigs {
Expand Down Expand Up @@ -58,12 +47,6 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}

externalNativeBuild {
ndkBuild {
path "src/main/cpp/Android.mk"
}
}

testOptions {
unitTests {
includeAndroidResources = true
Expand All @@ -82,67 +65,107 @@ task versionName {
}
}

def downloadBootstrap(String arch, String expectedChecksum, int version) {
def setupBootstrap(String arch, String expectedChecksum, int version) {
def digest = java.security.MessageDigest.getInstance("SHA-256")

def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
def file = new File(projectDir, localUrl)
if (file.exists()) {
def zipDownloadFile = new File(project.buildDir, "./gradle/bootstrap-" + arch + "-" + version + ".zip")

if (zipDownloadFile.exists()) {
def buffer = new byte[8192]
def input = new FileInputStream(file)
def input = new FileInputStream(zipDownloadFile)
while (true) {
def readBytes = input.read(buffer)
if (readBytes < 0) break
digest.update(buffer, 0, readBytes)
}
def checksum = new BigInteger(1, digest.digest()).toString(16)
if (checksum == expectedChecksum) {
return
} else {
logger.quiet("Deleting old local file with wrong hash: " + localUrl)
file.delete()
if (checksum != expectedChecksum) {
logger.quiet("Deleting old local file with wrong hash: " + zipDownloadFile.getAbsolutePath())
zipDownloadFile.delete()
}
}

def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip"
logger.quiet("Downloading " + remoteUrl + " ...")
if (!zipDownloadFile.exists()) {
def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=android10-v" + version + "-bootstrap-" + arch + ".zip"
logger.quiet("Downloading " + remoteUrl + " ...")

file.parentFile.mkdirs()
def out = new BufferedOutputStream(new FileOutputStream(file))
zipDownloadFile.parentFile.mkdirs()
def out = new BufferedOutputStream(new FileOutputStream(zipDownloadFile))

def connection = new URL(remoteUrl).openConnection()
connection.setInstanceFollowRedirects(true)
def digestStream = new java.security.DigestInputStream(connection.inputStream, digest)
out << digestStream
out.close()
def connection = new URL(remoteUrl).openConnection()
connection.setInstanceFollowRedirects(true)
def digestStream = new java.security.DigestInputStream(connection.inputStream, digest)
out << digestStream
out.close()

def checksum = new BigInteger(1, digest.digest()).toString(16)
if (checksum != expectedChecksum) {
file.delete()
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
def checksum = new BigInteger(1, digest.digest()).toString(16)
if (checksum != expectedChecksum) {
zipDownloadFile.delete()
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
}
}
}

clean {
doLast {
def tree = fileTree(new File(projectDir, 'src/main/cpp'))
tree.include 'bootstrap-*.zip'
tree.each { it.delete() }
def doneMarkerFile = new File(zipDownloadFile.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, "symlinks.so").getAbsoluteFile()
if (symlinksFile.exists()) symlinksFile.delete();

def mappingsFile = new File(outputDir, "files.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(zipDownloadFile)).withCloseable { zipInput ->
java.util.zip.ZipEntry zipEntry
while ((zipEntry = zipInput.getNextEntry()) != null) {
if (zipEntry.getName() == "SYMLINKS.txt") {
zipInput.transferTo(new FileOutputStream(symlinksFile))
} else if (!zipEntry.isDirectory()) {
def soName = counter + ".so"
def targetFile = new File(outputDir, soName).getAbsoluteFile()

zipInput.transferTo(new FileOutputStream(targetFile))

if (zipEntry.getName().endsWith("/pkg")) {
def pkgScript = new FileInputStream(project.getRootDir().getAbsolutePath() + "/pkg.sh")
pkgScript.transferTo(new FileOutputStream(targetFile))
}

mappingsFileWriter.writeLine(soName + "←" + zipEntry.getName())
counter++
}
}
}

mappingsFileWriter.close()
doneMarkerFile.createNewFile()
}

task downloadBootstraps(){
task setupBootstraps(){
doLast {
def version = 27
downloadBootstrap("aarch64", "517fb3aa215f7b96961f9377822d7f1b5e86c831efb4ab096ed65d0b1cdf02e9", version)
downloadBootstrap("arm", "94d17183afdd017cf8ab885b9103a370b16bec1d3cb641884511d545ee009b90", version)
downloadBootstrap("i686", "7f27723d2f0afbe7e90f203b3ca2e80871a8dfa08b136229476aa5e7ba3e988f", version)
downloadBootstrap("x86_64", "b19b2721bae5fb3a3fb0754c49611ce4721221e1e7997e7fd98940776ad88c3d", version)
def version = 12
setupBootstrap("aarch64", "5e07239cad78050f56a28f9f88a0b485cead45864c6c00e1a654c728152b0244", version)
setupBootstrap("arm", "fc72279c480c1eea46b6f0fcf78dc57599116c16dcf3b2b970a9ef828f0ec30b", version)
setupBootstrap("i686", "895680fc967aecfa4ed77b9dc03aab95d86345be69df48402c63bfc0178337f6", version)
setupBootstrap("x86_64", "8714ab8a5ff4e1f5f3ec01e7d0294776bfcffb187c84fa95270ec67ede8f682e", version)
}
}

afterEvaluate {
android.applicationVariants.all { variant ->
variant.javaCompileProvider.get().dependsOn(downloadBootstraps)
variant.javaCompileProvider.get().dependsOn(setupBootstraps)
}
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

<application
android:extractNativeLibs="true"
Expand Down
5 changes: 0 additions & 5 deletions app/src/main/cpp/Android.mk

This file was deleted.

18 changes: 0 additions & 18 deletions app/src/main/cpp/termux-bootstrap-zip.S

This file was deleted.

11 changes: 0 additions & 11 deletions app/src/main/cpp/termux-bootstrap.c

This file was deleted.

47 changes: 47 additions & 0 deletions app/src/main/java/com/termux/app/ApkInstaller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.termux.app;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;

import com.termux.terminal.EmulatorDebug;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;

public class ApkInstaller {
static void installPackageApk(String packageName, Context context) {
// FIXME: Different file names in case of multiple packages being installed at same time.
new Thread() {
@Override
public void run() {
try {
String urlString = "https://termux.net/apks/" + packageName + ".apk";
Log.e(EmulatorDebug.LOG_TAG, "Installing " + packageName + ", url is " + urlString);
File downloadFile = new File(TermuxService.FILES_PATH, "tmp.apk");
URL url = new URL(urlString);
try (FileOutputStream out = new FileOutputStream(downloadFile)) {
try (InputStream in = url.openStream()) {
byte[] buffer = new byte[8196];
int read;
while ((read = in.read(buffer)) >= 0) {
out.write(buffer, 0, read);
}
}
}

Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
installIntent.setData(Uri.parse("content://com.termux.files" + downloadFile.getAbsolutePath()));
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(installIntent);
} catch (Exception e) {
Log.e("termux", "Error installing " + packageName, e);
}
}
}.start();

}
}
1 change: 1 addition & 0 deletions app/src/main/java/com/termux/app/BackgroundJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ static String[] buildEnvironment(boolean failSafe, String cwd) {

List<String> environment = new ArrayList<>();

environment.add("TERMUX_ANDROID10=1");
environment.add("TERM=xterm-256color");
environment.add("COLORTERM=truecolor");
environment.add("HOME=" + TermuxService.HOME_PATH);
Expand Down
22 changes: 10 additions & 12 deletions app/src/main/java/com/termux/app/TermuxActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -487,19 +487,17 @@ public View getView(int position, View convertView, @NonNull ViewGroup parent) {

if (mTermService.getSessions().isEmpty()) {
if (mIsVisible) {
TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> {
if (mTermService == null) return; // Activity might have been destroyed.
try {
Bundle bundle = getIntent().getExtras();
boolean launchFailsafe = false;
if (bundle != null) {
launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false);
}
addNewSession(launchFailsafe, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
if (mTermService == null) return; // Activity might have been destroyed.
try {
Bundle bundle = getIntent().getExtras();
boolean launchFailsafe = false;
if (bundle != null) {
launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false);
}
});
addNewSession(launchFailsafe, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
}
} else {
// The service connected while not in foreground - just bail out.
finish();
Expand Down
Loading