Commit 06d4c5f4 authored by Arnau Vàzquez's avatar Arnau Vàzquez Committed by Romain Hunault
Browse files

Add XAPK support

Currently, there are two types of Xapk supported
	1. OBB + APK
	2. Multi arch APKs

Current implementation install XAPK from internal storage
parent f64ab2b4
......@@ -5,14 +5,16 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
compileSdkVersion 28
buildToolsVersion '28.0.3'
defaultConfig {
applicationId "foundation.e.apps"
minSdkVersion 21
targetSdkVersion 27
versionCode 9
versionCode 10
versionName "1.1.6"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions.cruncherEnabled = false
}
buildTypes {
release {
......@@ -26,27 +28,57 @@ android {
lintOptions {
lintConfig file("lint.xml")
}
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
androidExtensions {
experimental = true
}
}
dependencies {
def lifecycle_version = "1.1.1"
implementation "android.arch.work:work-runtime:1.0.1"
implementation 'androidx.work:work-runtime:2.3.1'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:preference-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
implementation "com.android.support:support-compat:27.1.1"
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// implementation 'com.android.support:design:27.1.1'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation files('libs/jackson-databind-2.9.7.jar')
implementation files('libs/jackson-annotations-2.9.7.jar')
implementation files('libs/jackson-core-2.9.7.jar')
implementation group: 'commons-codec', name: 'commons-codec', version: '1.11'
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
implementation 'org.bouncycastle:bcpg-jdk15on:1.60'
implementation "androidx.vectordrawable:vectordrawable:1.0.0"
implementation "androidx.vectordrawable:vectordrawable-animated:1.0.0"
implementation 'com.google.android.material:material:1.1.0-alpha05'
def nav_version = "1.0.0-alpha02"
implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0-rc02'
// use -ktx for Kotlin
implementation 'androidx.navigation:navigation-ui-ktx:2.0.0-rc02'
implementation "androidx.preference:preference-ktx:1.1.0"
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.0.0'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.mikepenz:iconics-core:3.1.0@aar'
implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar'
implementation 'org.greenrobot:eventbus:3.1.1'
implementation 'com.trello.rxlifecycle3:rxlifecycle-android:3.1.0'
implementation 'com.trello.rxlifecycle3:rxlifecycle-components:3.1.0'
implementation 'com.trello.rxlifecycle3:rxlifecycle-components-preference:3.1.0'
implementation 'com.makeramen:roundedimageview:2.3.0'
}
......@@ -9,6 +9,9 @@
<uses-permission
android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.google.android.launcher.permission.READ_SETTINGS"/>
<uses-permission android:name="com.google.android.launcher.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
......@@ -19,8 +22,11 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:largeHeap="true"
android:launchMode="singleTask"
android:theme="@style/AppTheme">
<activity android:name=".settings.AppRequestActivity"></activity>
<!-- <activity android:name=".application.PwaInstaller"/>-->
<activity android:name=".settings.AppRequestActivity" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
......@@ -31,13 +37,14 @@
<activity android:name=".application.ApplicationActivity" />
<activity android:name=".categories.category.CategoryActivity" />
<service
android:name=".applicationmanager.ApplicationManagerService"
android:description="@string/service_description"
android:exported="false" />
<provider
android:name="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="foundation.e.apps.provider"
android:exported="false"
android:grantUriPermissions="true">
......@@ -50,6 +57,15 @@
<activity
android:name=".application.ScreenshotsActivity"
android:theme="@style/FullScreenTheme" />
<activity
android:name=".PWA.PwaInstaller"
android:theme="@style/FullScreenTheme" />
<activity
android:name=".XAPK.InstallSplitApksActivity"
android:configChanges="screenSize|orientation|keyboardHidden"
android:label="XAPK Installer"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize" />
</application>
</manifest>
\ No newline at end of file
......@@ -17,16 +17,22 @@
package foundation.e.apps
//import androidx.fragment.app.ListFragment
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Bundle
import android.support.design.internal.BottomNavigationItemView
import android.support.design.internal.BottomNavigationMenuView
import android.support.design.widget.BottomNavigationView
import android.support.design.widget.Snackbar
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.os.Handler
import android.preference.PreferenceManager
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationItemView
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomnavigation.LabelVisibilityMode
import com.google.android.material.snackbar.Snackbar
import foundation.e.apps.applicationmanager.ApplicationManager
import foundation.e.apps.applicationmanager.ApplicationManagerServiceConnection
import foundation.e.apps.applicationmanager.ApplicationManagerServiceConnectionCallback
......@@ -40,24 +46,48 @@ import foundation.e.apps.utils.Constants
import foundation.e.apps.utils.Constants.CURRENTLY_SELECTED_FRAGMENT_KEY
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener,
ApplicationManagerServiceConnectionCallback {
private var currentFragmentId = 0
private val homeFragment = HomeFragment()
private val searchFragment = SearchFragment()
private val updatesFragment = UpdatesFragment()
private val applicationManagerServiceConnection =
ApplicationManagerServiceConnection(this)
private val codeRequestPermissions = 9527
var doubleBackToExitPressedOnce = false;
companion object {
lateinit var mActivity: MainActivity
var sharedPreferences : SharedPreferences?=null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mActivity = this
disableCategoryIfOpenSource()
bottom_navigation_view.setOnNavigationItemSelectedListener{
if (selectFragment(it.itemId,it)) {
disableCategoryIfOpenSource()
currentFragmentId = it.itemId
return@setOnNavigationItemSelectedListener true
}
return@setOnNavigationItemSelectedListener false
}
bottom_navigation_view.setOnNavigationItemSelectedListener(this)
disableShiftingOfNabBarItems()
initialiseUpdatesWorker()
// Show the home fragment by default
currentFragmentId = if (savedInstanceState != null &&
savedInstanceState.containsKey(CURRENTLY_SELECTED_FRAGMENT_KEY)) {
......@@ -73,11 +103,13 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
private fun initialiseUpdatesWorker() {
UpdatesManager(applicationContext).startWorker()
}
override fun onServiceBind(applicationManager: ApplicationManager) {
initialiseFragments(applicationManager)
selectFragment(currentFragmentId)
selectFragment(currentFragmentId, null)
}
private fun initialiseFragments(applicationManager: ApplicationManager) {
......@@ -87,32 +119,53 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
if (selectFragment(item.itemId)) {
if (selectFragment(item.itemId,item)) {
currentFragmentId = item.itemId
return true
}
return false
}
private fun selectFragment(fragmentId: Int): Boolean {
fun showApplicationTypePreference(): String {
val preferences = PreferenceManager.getDefaultSharedPreferences(mActivity)
var showAllApps = preferences.getBoolean(mActivity.getString(R.string.Show_all_apps), true)
var showAllOpenSourceApps = preferences.getBoolean(mActivity.getString(R.string.show_only_open_source_apps_key), false)
var showAllPwaApps = preferences.getBoolean(mActivity.getString(R.string.show_only_pwa_apps_key), false)
if (showAllOpenSourceApps) {
return "open"
} else if (showAllApps) {
return "any"
} else if (showAllPwaApps) {
return "pwa"
}
return "any"
}
private fun selectFragment(fragmentId: Int, item: MenuItem?): Boolean {
when (fragmentId) {
R.id.menu_home -> {
item?.setIcon(R.drawable.ic_menu_home)
showFragment(homeFragment)
return true
}
R.id.menu_categories -> {
item?.setIcon(R.drawable.ic_menu_categories)
showFragment(CategoriesFragment())
return true
}
R.id.menu_search -> {
item?.setIcon(R.drawable.ic_menu_search)
showFragment(searchFragment)
return true
}
R.id.menu_updates -> {
item?.setIcon(R.drawable.ic_menu_updates)
showFragment(updatesFragment)
return true
}
R.id.menu_settings -> {
item?.setIcon(R.drawable.ic_menu_settings)
showFragment(SettingsFragment())
return true
}
......@@ -144,8 +197,13 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
for (i in 0 until menuView.childCount) {
val itemView = menuView.getChildAt(i) as BottomNavigationItemView
itemView.setShiftingMode(false)
itemView.setChecked(itemView.itemData.isChecked)
itemView.setLabelVisibilityMode(LabelVisibilityMode.LABEL_VISIBILITY_LABELED); itemView.setChecked(itemView.itemData.isChecked)
}
}
private fun disableCategoryIfOpenSource(){
if(showApplicationTypePreference()=="open") {
bottom_navigation_view.menu.removeItem(R.id.menu_categories)
}
}
......@@ -158,7 +216,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
}
}
override fun onSaveInstanceState(outState: Bundle?) {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState?.putInt(CURRENTLY_SELECTED_FRAGMENT_KEY, currentFragmentId)
}
......@@ -170,4 +228,20 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
updatesFragment.decrementApplicationUses()
applicationManagerServiceConnection.unbindService(this)
}
}
override fun onBackPressed() {
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
}
this.doubleBackToExitPressedOnce = true;
Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();
Handler().postDelayed(Runnable() {
run {
doubleBackToExitPressedOnce = false;
}
}, 2000)
}
}
\ No newline at end of file
package foundation.e.apps.PWA
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Looper
import android.provider.Browser
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import foundation.e.apps.MainActivity.Companion.mActivity
import foundation.e.apps.MainActivity.Companion.sharedPreferences
import foundation.e.apps.R
import foundation.e.apps.application.model.data.PwasBasicData
import foundation.e.apps.utils.Constants
import java.io.FileNotFoundException
import java.net.URL
import java.util.*
import javax.net.ssl.HttpsURLConnection
class PwaInstaller : AppCompatActivity() {
var icon : Bitmap?=null
private val sharedPrefFile = "kotlinsharedpreference"
var scaledBitmap :Bitmap?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_application)
sharedPreferences= this.getSharedPreferences(sharedPrefFile,Context.MODE_PRIVATE)
val extras = intent.extras
val name = extras.getString("NAME")
val Url = Uri.parse(extras.getString("URL"))
installer(name,Url)
}
override fun onResume() {
super.onResume()
finish()
}
fun setBooleanConfig(key:String?,flag:Boolean){
val editor:SharedPreferences.Editor = sharedPreferences!!.edit()
editor.putBoolean(key,flag)
editor.apply()
}
private fun installer(name: String?, myUrl: Uri) {
setBooleanConfig(name,true)
Thread{
run {
Looper.prepare();//Call looper.prepare()
try {
var uri = PwasBasicData.thisActivity!!.uri
val url = URL(Constants.BASE_URL + "media/" + uri)
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.requestMethod = Constants.REQUEST_METHOD_GET
urlConnection.connectTimeout = Constants.CONNECT_TIMEOUT
urlConnection.readTimeout = Constants.READ_TIMEOUT
icon = BitmapFactory.decodeStream(urlConnection.inputStream)
scaledBitmap = Bitmap.createScaledBitmap(icon, 128, 128, true)
}catch (e: FileNotFoundException) {
val x = R.drawable.pwa_default_icon
val icon = BitmapFactory.decodeResource(mActivity.getResources(),
x)
scaledBitmap = Bitmap.createScaledBitmap(icon, 128, 128, true)
}
val intent = Intent()
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.setAction(Intent.ACTION_VIEW)
intent.putExtra(Browser.EXTRA_APPLICATION_ID, Long.toString())
intent.setData(myUrl)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
val shortcutManager = mActivity.getSystemService(ShortcutManager::class.java)
if (isExistShortcutInfo(name)) {
Toast.makeText(this, "Shortcut already exist", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Shortcut created", Toast.LENGTH_SHORT).show()
val shortcut = ShortcutInfo.Builder(mActivity, java.lang.Long.toString(Random().nextLong()))
.setShortLabel(name.toString())
.setIcon(Icon.createWithAdaptiveBitmap(scaledBitmap))
.setIntent(intent)
.build()
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
shortcutManager.requestPinShortcut(shortcut, null)
}
} else {
val installer = Intent()
installer.putExtra("android.intent.extra.shortcut.INTENT", intent)
installer.putExtra("android.intent.extra.shortcut.NAME", name)
installer.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaledBitmap)
installer.putExtra("duplicate", false);
installer.setAction("com.android.launcher.action.INSTALL_SHORTCUT")
mActivity.sendBroadcast(installer)
}
Looper.loop();
}
}.start()
}
companion object {
@SuppressLint("NewApi")
fun isExistShortcutInfo(shortcutId: String?): Boolean {
val shortcutManager = mActivity.getSystemService(ShortcutManager::class.java)
val shortcutInfoList = shortcutManager!!.getPinnedShortcuts()
for (info in shortcutInfoList) {
if (info.getId() == shortcutId) {
}
if (info.getShortLabel() == shortcutId) {
return true
}
}
return false
}
}
}
package foundation.e.apps.XAPK
data class ApkAssetBean(
var xApkInfo: XApkInfo?,
var sortPosition: Long,
var apkAssetType: ApkAssetType) {
constructor() : this( null, 0L, ApkAssetType.XAPK)
}
package foundation.e.apps.XAPK
enum class ApkAssetType(val suffix: String) {
Apk(".apk"),
XAPK(".xapk"),
Apks(".apks")
}
package foundation.e.apps.XAPK
import android.annotation.SuppressLint
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@SuppressLint("ParcelCreator")
@Parcelize
data class ApksBean(
var packageName: String,
var label: String,
var iconPath: String,
var apkAssetType: ApkAssetType?,
var outputFileDir: String,
var splitApkPaths: ArrayList<String>?
) : Parcelable {
constructor() : this(String(), String(), String(), null, String(), null)
}
package foundation.e.apps.XAPK
import android.os.Environment
import foundation.e.apps.BuildConfig
import java.io.File
object AppFolder {
private val APP_FOLDER_NAME: String
get() {
return if (false) {
"XAPK Installer"
} else {
"XAPK Installer-${BuildConfig.BUILD_TYPE}"
}
}
private const val TEMP_FOLDER_NAME = "temp"
val tempFolder: File?
get() = createAppFolderDirectory(TEMP_FOLDER_NAME)
fun getXApkInstallTempFolder(packageName: String): File {
val tempFile = File(tempFolder, packageName)
FsUtils.createOnNotFound(tempFile)
return tempFile
}
private fun createAppFolderDirectory(directoryName: String): File? {
return FsUtils.createOnNotFound(File(appFolder, directoryName))
}
private val appFolder: File?
get() {
return if (FsUtils.isSdUsable) {
val appFolder = File(Environment.getExternalStorageDirectory(), APP_FOLDER_NAME)
FsUtils.createOnNotFound(appFolder)
} else {
null
}
}
}
package foundation.e.apps.XAPK
import android.os.Bundle
import com.trello.rxlifecycle3.components.support.RxAppCompatActivity
abstract class BaseActivity : RxAppCompatActivity() {
protected val logTag: String by lazy { javaClass.simpleName }
protected val mContext by lazy { this }
protected val mActivity: BaseActivity by lazy { this }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
init()
nextStep()
}
protected open fun init() {}
protected open fun nextStep() {}
}
package foundation.e.apps.XAPK
import org.greenrobot.eventbus.EventBus
object EventManager {
fun register(subscriber: Any) {
EventBus.getDefault().register(subscriber)
}
fun unregister(subscriber: Any) {
EventBus.getDefault().unregister(subscriber)
}
}
\ No newline at end of file