* Changed the hardcoded cache directory to the android alternative (pointing to /data/user/0 which is a symlink to /data/data).
* Implemented the transparent activity mechanism for downloading multiple apps through the Play Store (which just works as if the intent was added to the back-stack)
* Now we check deliberately check for source (external for ext. repo and play-store for the Play Store).
* Fixed a few bugs.
This commit is contained in:
Prakhar Shukla 2020-10-05 21:00:05 +05:30 committed by Leonid Pliushch
parent 0f078a7cf3
commit 3497119c71
No known key found for this signature in database
GPG Key ID: 45F2964132545795
9 changed files with 122 additions and 43 deletions

View File

@ -68,7 +68,7 @@ dependencies {
testImplementation 'org.robolectric:robolectric:4.3.1'
//kotlin
implementation "androidx.core:core-ktx:1.3.1"
implementation "androidx.core:core-ktx:1.3.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
@ -181,9 +181,9 @@ task setupBootstraps() {
doLast {
def version = 12
setupBootstrap("aarch64", "5e07239cad78050f56a28f9f88a0b485cead45864c6c00e1a654c728152b0244", version)
setupBootstrap("arm", "fc72279c480c1eea46b6f0fcf78dc57599116c16dcf3b2b970a9ef828f0ec30b", version)
setupBootstrap("i686", "895680fc967aecfa4ed77b9dc03aab95d86345be69df48402c63bfc0178337f6", version)
setupBootstrap("x86_64", "8714ab8a5ff4e1f5f3ec01e7d0294776bfcffb187c84fa95270ec67ede8f682e", version)
setupBootstrap("arm", "fc72279c480c1eea46b6f0fcf78dc57599116c16dcf3b2b970a9ef828f0ec30b", version)
setupBootstrap("i686", "895680fc967aecfa4ed77b9dc03aab95d86345be69df48402c63bfc0178337f6", version)
setupBootstrap("x86_64", "8714ab8a5ff4e1f5f3ec01e7d0294776bfcffb187c84fa95270ec67ede8f682e", version)
}
}

View File

@ -73,7 +73,10 @@
android:resizeableActivity="true"
android:theme="@android:style/Theme.Material.Light.DarkActionBar" />
<activity
android:name=".app.packagemanager.GooglePlayTransparentActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity>
<activity
android:name="com.termux.filepicker.TermuxFileReceiverActivity"
android:excludeFromRecents="true"

View File

@ -23,6 +23,9 @@ import android.util.Log;
import android.widget.ArrayAdapter;
import com.termux.R;
import com.termux.app.packagemanager.PackageInstaller;
import com.termux.app.packagemanager.PackageLister;
import com.termux.app.packagemanager.PackageUninstaller;
import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSession.SessionChangedCallback;
@ -137,11 +140,14 @@ public final class TermuxService extends Service implements SessionChangedCallba
Log.e(EmulatorDebug.LOG_TAG, ACTION_INSTALL_PACKAGES + " called without packages");
} else {
PackageInstaller packageInstaller = new PackageInstaller(this);
if (source == null || source.isEmpty()) {
packageInstaller.initDownloader(packages);
} else {
packageInstaller.downloadFromPlayStore(packages);
}
if (source != null) {
if (source.equals("external")) {
packageInstaller.initDownloader(packages);
} else if (source.equals("play-store")) {
packageInstaller.downloadFromPlayStore(packages);
}
} else
Log.e(EmulatorDebug.LOG_TAG, ACTION_INSTALL_PACKAGES + " called without the download source!");
}
} else if (ACTION_LIST_PACKAGES.equals(action)) {
PackageLister packageLister = new PackageLister(this);

View File

@ -0,0 +1,9 @@
package com.termux.app.packagemanager
class Constants {
companion object {
const val PACKAGE_INSTALLED_ACTION = "com.termux.SESSION_API_PACKAGE_INSTALLED"
const val TERMUX_CACHE_PKG_DIRECTORY_SUBFOLDER = "/pkg"
const val APK_REPO_URL = "https://termux.net/apks/"
}
}

View File

@ -0,0 +1,45 @@
package com.termux.app.packagemanager
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import com.termux.app.packagemanager.PackageInstaller.Companion.log
class GooglePlayTransparentActivity : Activity() {
private var currentPosition = 0
private var onResumeCount = 0
private lateinit var packageList: Array<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
packageList = intent.getStringArrayExtra("packages")!!
openStoreLink(packageList[currentPosition])
}
override fun onResume() {
super.onResume()
if (++onResumeCount > 1) {
var temp = currentPosition
if (++temp == packageList.size) {
Toast.makeText(this, "PlayStore redirection list has exhausted. Finishing the activity...", Toast.LENGTH_SHORT).show()
"Play Redirect Done!".log()
finish()
} else {
openStoreLink(packageList[++currentPosition])
}
}
}
private fun openStoreLink(packageName: String) {
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=net.termux.$packageName")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} catch (e: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=net.termux.$packageName")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}
}

View File

@ -1,4 +1,4 @@
package com.termux.app
package com.termux.app.packagemanager
import android.app.NotificationManager
import android.content.Context
@ -6,7 +6,8 @@ import android.os.Handler
import android.os.StatFs
import androidx.core.app.NotificationCompat
import com.termux.R
import com.termux.app.PackageInstaller.Companion.log
import com.termux.app.packagemanager.Constants.Companion.APK_REPO_URL
import com.termux.app.packagemanager.PackageInstaller.Companion.log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
@ -29,7 +30,10 @@ const val NOTIFICATION_CHANNEL_ID = "termux_notification_channel"
class PackageDownloader(val context: Context) {
private lateinit var notificationManager: NotificationManager
private val TERMUX_CACHE_DIRECTORY = "${context.cacheDir}${Constants.TERMUX_CACHE_PKG_DIRECTORY_SUBFOLDER}"
private lateinit
var notificationManager: NotificationManager
private lateinit var builder: NotificationCompat.Builder
interface ProgressListener {
@ -66,9 +70,9 @@ class PackageDownloader(val context: Context) {
notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
builder = NotificationCompat.Builder(context, "termux_notification_channel").setChannelId(NOTIFICATION_CHANNEL_ID)
File(TERMUX_CACHE_PKG_DIRECTORY).let {
if (!it.exists()) {
it.mkdir()
File(TERMUX_CACHE_DIRECTORY).let { file ->
if (!file.exists()) {
file.mkdir()
}
}
@ -79,14 +83,14 @@ class PackageDownloader(val context: Context) {
var percent60 = false
var percent80 = false
val fileUrl = "https://termux.net/apks/$packageName.apk"
val fileUrl = "$APK_REPO_URL$packageName.apk"
"URL -> $fileUrl".log()
try {
downloadingJob = GlobalScope.launch(Dispatchers.IO) {
val downloadData = DownloadData(packageName, 0, 0, 0, ENTERED)
try {
showNotification(downloadData)
val downloadFile = File("${TERMUX_CACHE_PKG_DIRECTORY}/${packageName}.apk")
val downloadFile = File("$TERMUX_CACHE_DIRECTORY/${packageName}.apk")
deleteFileIfExists(downloadFile)
"Fetching the file size...".log()
val url = URL(fileUrl)
@ -220,7 +224,7 @@ class PackageDownloader(val context: Context) {
downloadingJob.cancel()
}
}
val downloadFile = File("${TERMUX_CACHE_PKG_DIRECTORY}/${this}.apk")
val downloadFile = File("$TERMUX_CACHE_DIRECTORY/${this}.apk")
deleteFileIfExists(downloadFile)
if (errorData.notificationID != 0) {
notificationManager.cancel(errorData.notificationID)

View File

@ -1,13 +1,19 @@
package com.termux.app
package com.termux.app.packagemanager
import android.app.PendingIntent
import android.content.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import com.termux.app.packagemanager.Constants.Companion.PACKAGE_INSTALLED_ACTION
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -16,11 +22,10 @@ import java.io.FileInputStream
import java.io.IOException
import java.io.OutputStream
const val PACKAGE_INSTALLED_ACTION = "com.termux.SESSION_API_PACKAGE_INSTALLED"
const val TERMUX_CACHE_PKG_DIRECTORY = "/data/data/com.termux/cache/pkg"
class PackageInstaller(val context: Context) : PackageDownloader.ErrorListener, PackageDownloader.ProgressListener, PackageDownloader.StartListener, PackageDownloader.CompleteListener {
private val TERMUX_CACHE_DIRECTORY = "${context.cacheDir}${Constants.TERMUX_CACHE_PKG_DIRECTORY_SUBFOLDER}"
private val downloadHashMap: HashMap<String, LocalDownloadData> = hashMapOf()
private val installationResponseHashMap: HashMap<String, String> = hashMapOf()
private var packagesToInstall: ArrayList<String> = arrayListOf()
@ -30,6 +35,8 @@ class PackageInstaller(val context: Context) : PackageDownloader.ErrorListener,
fun initDownloader(packageList: Array<String>) {
TERMUX_CACHE_DIRECTORY.log()
if (isInstallationOfApkAllowed()) {
context.registerReceiver(broadcastReceiver, IntentFilter(PACKAGE_INSTALLED_ACTION))
packageDownloader.initListeners(this, this, this, this)
@ -91,6 +98,7 @@ class PackageInstaller(val context: Context) : PackageDownloader.ErrorListener,
}
if (counter == 0) {
endDownloadSession()
getApkListInFileSystem()
proceedToInstallation()
}
}
@ -138,7 +146,7 @@ class PackageInstaller(val context: Context) : PackageDownloader.ErrorListener,
private fun addApkToInstallSession(session: PackageInstaller.Session,
packageName: String) {
val file = File("${TERMUX_CACHE_PKG_DIRECTORY}/$packageName.apk")
val file = File("$TERMUX_CACHE_DIRECTORY/$packageName.apk")
val packageInSession: OutputStream = session.openWrite(packageName, 0, -1)
val inputStream = FileInputStream(file)
try {
@ -202,7 +210,7 @@ class PackageInstaller(val context: Context) : PackageDownloader.ErrorListener,
}
private fun proceedToInstallation(next: Boolean = false) {
getApkListInFileSystem()
if (!next) {
if (packagesToInstall.isEmpty()) {
endInstallationSession()
@ -223,7 +231,7 @@ class PackageInstaller(val context: Context) : PackageDownloader.ErrorListener,
downloadHashMap.forEach { (packageName) ->
//Setting up a default response
installationResponseHashMap[packageName] = "the request package was either not downloaded or just doesn't exist!"
val apkFileToBeInstalled = File("${TERMUX_CACHE_PKG_DIRECTORY}/$packageName.apk")
val apkFileToBeInstalled = File("$TERMUX_CACHE_DIRECTORY/$packageName.apk")
if (apkFileToBeInstalled.exists()) {
packagesToInstall.add(packageName)
}
@ -270,21 +278,25 @@ class PackageInstaller(val context: Context) : PackageDownloader.ErrorListener,
/*--------------------------------------- Play Store Download -----------------------------------------*/
fun downloadFromPlayStore(packageList: Array<String>) {
/*Opening multiple package links at once in Google Play will be anything but user-friendly. There's no way to prevent this but to just start a transparent
* activity and monitor the lifecycle, but that's not a great idea in itself. */
fun openStoreLink(packageName: String) {
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=net.termux.$packageName")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} catch (e: ActivityNotFoundException) {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=net.termux.$packageName")).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
if (isGooglePlayPresent()) {
"Google Play Store Present".log()
context.startActivity(Intent(context, GooglePlayTransparentActivity::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("packages", packageList))
} else {
//Falling back to the external repository for downloads
initDownloader(packageList)
}
packageList.forEachIndexed { _, packageName ->
openStoreLink(packageName)
}
private fun isGooglePlayPresent(): Boolean {
return try {
val info = context.packageManager.getPackageInfo("com.android.vending", PackageManager.GET_ACTIVITIES)
val label = info.applicationInfo.loadLabel(context.packageManager) as String
TextUtils.isEmpty(label) && label.startsWith("Google Play")
} catch (e: PackageManager.NameNotFoundException) {
false
}
}
}
data class LocalDownloadData(var packageName: String, var isDownloaded: Boolean?, var extraLogs: String = "")

View File

@ -1,8 +1,8 @@
package com.termux.app
package com.termux.app.packagemanager
import android.content.Context
import android.content.pm.PackageManager
import com.termux.app.PackageInstaller.Companion.log
import com.termux.app.packagemanager.PackageInstaller.Companion.log
class PackageLister(val context: Context) {

View File

@ -1,11 +1,11 @@
package com.termux.app
package com.termux.app.packagemanager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import com.termux.app.PackageInstaller.Companion.log
import com.termux.app.packagemanager.PackageInstaller.Companion.log
const val TERMUX_APK_SUFFIX = "net.termux."
@ -19,7 +19,7 @@ class PackageUninstaller(var context: Context) {
private fun uninstallAPK(packageName: String) {
val intent = Intent(Intent.ACTION_DELETE)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse("package:${TERMUX_APK_SUFFIX}${packageName}")
intent.data = Uri.parse("package:$TERMUX_APK_SUFFIX${packageName}")
context.startActivity(intent)
}