mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-23 00:42:38 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9a712f692 | ||
|
|
6a0b3fc8bd | ||
|
|
684fd99b83 | ||
|
|
fd83daa776 | ||
|
|
9a87716635 | ||
|
|
31566ce64b | ||
|
|
d078879e7b | ||
|
|
5fa42b6ca5 | ||
|
|
e164b87436 | ||
|
|
433d6a0f46 | ||
|
|
cf8b1edbe2 | ||
|
|
500e00a9cc | ||
|
|
c3f80e9c74 | ||
|
|
7eb06e175f | ||
|
|
141681be05 | ||
|
|
dcfab5e001 |
No files matched your search
@@ -7,6 +7,15 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
config:
|
||||||
|
- name: Release
|
||||||
|
lower: release
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
lower: debug
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -27,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
run: |
|
run: |
|
||||||
./gradlew app:assembleDebug \
|
./gradlew app:assemble${{ matrix.config.name }} \
|
||||||
-Pandroid.injected.signing.store.file="$HOME/release.keystore" \
|
-Pandroid.injected.signing.store.file="$HOME/release.keystore" \
|
||||||
-Pandroid.injected.signing.store.password="${{secrets.KEYSTORE_PASSWORD}}" \
|
-Pandroid.injected.signing.store.password="${{secrets.KEYSTORE_PASSWORD}}" \
|
||||||
-Pandroid.injected.signing.key.alias="${{secrets.KEY_ALIAS}}" \
|
-Pandroid.injected.signing.key.alias="${{secrets.KEY_ALIAS}}" \
|
||||||
@@ -37,8 +46,8 @@ jobs:
|
|||||||
id: rename_apk_universal
|
id: rename_apk_universal
|
||||||
run: |
|
run: |
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
SHORT_SHA=${GITHUB_SHA::7}
|
||||||
APK_PATH=${{github.workspace}}/app/build/outputs/apk/debug/app-universal-debug.apk
|
APK_PATH=${{github.workspace}}/app/build/outputs/apk/${{ matrix.config.lower }}/app-universal-${{ matrix.config.lower }}.apk
|
||||||
OUT_PATH=${{github.workspace}}/geode-launcher-debug-$SHORT_SHA.apk
|
OUT_PATH=${{github.workspace}}/geode-launcher-${{ matrix.config.lower }}-$SHORT_SHA.apk
|
||||||
mv $APK_PATH $OUT_PATH
|
mv $APK_PATH $OUT_PATH
|
||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@@ -46,15 +55,15 @@ jobs:
|
|||||||
id: rename_apk
|
id: rename_apk
|
||||||
run: |
|
run: |
|
||||||
SHORT_SHA=${GITHUB_SHA::7}
|
SHORT_SHA=${GITHUB_SHA::7}
|
||||||
APK_PATH=${{github.workspace}}/app/build/outputs/apk/debug/app-armeabi-v7a-debug.apk
|
APK_PATH=${{github.workspace}}/app/build/outputs/apk/${{ matrix.config.lower }}/app-armeabi-v7a-${{ matrix.config.lower }}.apk
|
||||||
OUT_PATH=${{github.workspace}}/geode-launcher-debug-android32-$SHORT_SHA.apk
|
OUT_PATH=${{github.workspace}}/geode-launcher-${{ matrix.config.lower }}-android32-$SHORT_SHA.apk
|
||||||
mv $APK_PATH $OUT_PATH
|
mv $APK_PATH $OUT_PATH
|
||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload development build
|
- name: Upload development build
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: geode-launcher-debug
|
name: geode-launcher-${{ matrix.config.lower }}
|
||||||
path: |
|
path: |
|
||||||
${{ steps.rename_apk_universal.outputs.path }}
|
${{ steps.rename_apk_universal.outputs.path }}
|
||||||
${{ steps.rename_apk.outputs.path }}
|
${{ steps.rename_apk.outputs.path }}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person or organization
|
||||||
|
obtaining a copy of the software and accompanying documentation covered by
|
||||||
|
this license (the "Software") to use, reproduce, display, distribute,
|
||||||
|
execute, and transmit the Software, and to prepare derivative works of the
|
||||||
|
Software, and to permit third-parties to whom the Software is furnished to
|
||||||
|
do so, all subject to the following:
|
||||||
|
|
||||||
|
The copyright notices in the Software and this entire statement, including
|
||||||
|
the above license grant, this restriction and the following disclaimer,
|
||||||
|
must be included in all copies of the Software, in whole or in part, and
|
||||||
|
all derivative works of the Software, unless such copies or derivative
|
||||||
|
works are solely in the form of machine-executable object code generated by
|
||||||
|
a source language processor.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||||
|
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
+2
-2
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "com.geode.launcher"
|
applicationId "com.geode.launcher"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 7
|
versionCode 9
|
||||||
versionName "1.0.0"
|
versionName "1.1.1"
|
||||||
|
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary true
|
useSupportLibrary true
|
||||||
|
|||||||
@@ -9,8 +9,15 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="32" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.wifi"
|
android:name="android.hardware.wifi"
|
||||||
|
|||||||
@@ -65,8 +65,12 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
||||||
val abiMismatch = LaunchUtils.detectAbiMismatch(this, gdPackageInfo, e)
|
val abiMismatch = LaunchUtils.detectAbiMismatch(this, gdPackageInfo, e)
|
||||||
|
|
||||||
val errorMessage = if (abiMismatch) getString(R.string.load_failed_abi_error_description) else
|
val is64bit = LaunchUtils.is64bit
|
||||||
getString(R.string.load_failed_link_error_description)
|
val errorMessage = when {
|
||||||
|
abiMismatch && is64bit -> getString(R.string.load_failed_abi_error_need_32bit_description)
|
||||||
|
abiMismatch -> getString(R.string.load_failed_abi_error_need_64bit_description)
|
||||||
|
else -> getString(R.string.load_failed_link_error_description)
|
||||||
|
}
|
||||||
|
|
||||||
returnToMain(
|
returnToMain(
|
||||||
getString(R.string.load_failed_link_error),
|
getString(R.string.load_failed_link_error),
|
||||||
@@ -336,14 +340,19 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRestart() {
|
||||||
|
super.onRestart()
|
||||||
|
mIsOnPause = false
|
||||||
|
BaseRobTopActivity.isPaused = false
|
||||||
|
if (!this.mIsRunning) {
|
||||||
|
resumeGame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
mIsOnPause = true
|
mIsOnPause = true
|
||||||
BaseRobTopActivity.isPaused = true
|
BaseRobTopActivity.isPaused = true
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
if (mIsRunning) {
|
if (mIsRunning) {
|
||||||
pauseGame()
|
pauseGame()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,9 +252,14 @@ fun UpdateWarning() {
|
|||||||
Text(stringResource(R.string.launcher_unsupported_version_title))
|
Text(stringResource(R.string.launcher_unsupported_version_title))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
|
val message = if (gdVersionCode >= Constants.SUPPORTED_VERSION_CODE) {
|
||||||
|
R.string.launcher_unsupported_version_description
|
||||||
|
} else {
|
||||||
|
R.string.launcher_outdated_version_description
|
||||||
|
}
|
||||||
|
|
||||||
Text(stringResource(
|
Text(stringResource(
|
||||||
R.string.launcher_unsupported_version_description,
|
message, gdVersionString, Constants.SUPPORTED_VERSION_STRING
|
||||||
gdVersionString, Constants.SUPPORTED_VERSION_STRING
|
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.geode.launcher.activityresult
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
|
||||||
|
open class GeodeOpenFileActivityResult : ActivityResultContract<GeodeOpenFileActivityResult.OpenFileParams, Uri?>() {
|
||||||
|
class OpenFileParams(val extraMimes: Array<String>, val defaultPath: Uri?) {}
|
||||||
|
override fun createIntent(context: Context, input: OpenFileParams): Intent {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.putExtra(Intent.EXTRA_MIME_TYPES, input.extraMimes)
|
||||||
|
.setType("*/*")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
if (input.defaultPath != null) {
|
||||||
|
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input.defaultPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun getSynchronousResult(
|
||||||
|
context: Context,
|
||||||
|
input: OpenFileParams
|
||||||
|
): SynchronousResult<Uri?>? = null
|
||||||
|
|
||||||
|
final override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||||
|
return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.geode.launcher.activityresult
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
|
||||||
|
open class GeodeOpenFilesActivityResult : ActivityResultContract<GeodeOpenFilesActivityResult.OpenFileParams, List<@JvmSuppressWildcards Uri>>() {
|
||||||
|
class OpenFileParams(val extraMimes: Array<String>, val defaultPath: Uri?) {}
|
||||||
|
override fun createIntent(context: Context, input: OpenFileParams): Intent {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
|
.putExtra(Intent.EXTRA_MIME_TYPES, input.extraMimes)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||||
|
.setType("*/*")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
if (input.defaultPath != null) {
|
||||||
|
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input.defaultPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun getSynchronousResult(
|
||||||
|
context: Context,
|
||||||
|
input: OpenFileParams
|
||||||
|
): SynchronousResult<List<@JvmSuppressWildcards Uri>>? = null
|
||||||
|
|
||||||
|
final override fun parseResult(resultCode: Int, intent: Intent?): List<Uri> {
|
||||||
|
// Gracefully stolen from ActivityResultContracts.Intent.getclipDataUris
|
||||||
|
val clipData = intent.takeIf {
|
||||||
|
resultCode == Activity.RESULT_OK
|
||||||
|
}?.clipData
|
||||||
|
val resultSet = LinkedHashSet<Uri>()
|
||||||
|
intent?.data?.let { data ->
|
||||||
|
resultSet.add(data)
|
||||||
|
}
|
||||||
|
if (clipData == null && resultSet.isEmpty()) {
|
||||||
|
return emptyList()
|
||||||
|
} else if (clipData != null) {
|
||||||
|
for (i in 0 until clipData.itemCount) {
|
||||||
|
val uri = clipData.getItemAt(i).uri
|
||||||
|
if (uri != null) {
|
||||||
|
resultSet.add(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ArrayList(resultSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.geode.launcher.activityresult
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
|
||||||
|
open class GeodeSaveFileActivityResult : ActivityResultContract<GeodeSaveFileActivityResult.SaveFileParams, Uri?>() {
|
||||||
|
class SaveFileParams(val mimeType: String?, val defaultPath: Uri?) {}
|
||||||
|
override fun createIntent(context: Context, input: SaveFileParams): Intent {
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
|
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
.setType(input.mimeType ?: "*/*")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
if (input.defaultPath != null) {
|
||||||
|
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input.defaultPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun getSynchronousResult(
|
||||||
|
context: Context,
|
||||||
|
input: SaveFileParams
|
||||||
|
): SynchronousResult<Uri?>? = null
|
||||||
|
|
||||||
|
final override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||||
|
return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import kotlinx.coroutines.yield
|
|||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.source
|
import okio.source
|
||||||
import java.io.EOFException
|
import java.io.EOFException
|
||||||
import java.util.LinkedList
|
import java.io.IOException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel to manage log output from logcat.
|
* ViewModel to manage log output from logcat.
|
||||||
@@ -30,6 +30,8 @@ class LogViewModel: ViewModel() {
|
|||||||
private val _isLoading = MutableStateFlow(false)
|
private val _isLoading = MutableStateFlow(false)
|
||||||
val isLoading = _isLoading.asStateFlow()
|
val isLoading = _isLoading.asStateFlow()
|
||||||
|
|
||||||
|
private var failedToLoad = false
|
||||||
|
|
||||||
private var logJob: Job? = null
|
private var logJob: Job? = null
|
||||||
|
|
||||||
var filterCrashes = false
|
var filterCrashes = false
|
||||||
@@ -43,14 +45,24 @@ class LogViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun logOutput(): List<LogLine> {
|
private suspend fun logOutput(): List<LogLine> {
|
||||||
val logLines = LinkedList<LogLine>()
|
val logLines = ArrayList<LogLine>()
|
||||||
|
|
||||||
|
// -B = binary format, -d = dump logs
|
||||||
val logCommand = if (filterCrashes) "logcat -b crash -B -d"
|
val logCommand = if (filterCrashes) "logcat -b crash -B -d"
|
||||||
else "logcat -B -d"
|
else "logcat -B -d"
|
||||||
|
|
||||||
// -B = binary format, -d = dump logs
|
val logProcess = try {
|
||||||
val logSource = Runtime.getRuntime().exec(logCommand)
|
Runtime.getRuntime().exec(logCommand)
|
||||||
.inputStream.source().buffer()
|
} catch (ioe: IOException) {
|
||||||
|
ioe.printStackTrace()
|
||||||
|
logLines += LogLine.showException(ioe)
|
||||||
|
|
||||||
|
failedToLoad = true
|
||||||
|
|
||||||
|
return logLines
|
||||||
|
}
|
||||||
|
|
||||||
|
val logSource = logProcess.inputStream.source().buffer()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
@@ -71,6 +83,9 @@ class LogViewModel: ViewModel() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
logLines += LogLine.showException(e)
|
logLines += LogLine.showException(e)
|
||||||
|
|
||||||
|
// technically it maybe didn't completely fail...
|
||||||
|
failedToLoad = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return logLines
|
return logLines
|
||||||
@@ -88,8 +103,24 @@ class LogViewModel: ViewModel() {
|
|||||||
_lineState.clear()
|
_lineState.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLogData(): String {
|
private fun dumpLogcatText(): String {
|
||||||
return _lineState.joinToString("\n") { it.asSimpleString }
|
val logCommand = if (filterCrashes) "logcat -b crash -d"
|
||||||
|
else "logcat -d"
|
||||||
|
|
||||||
|
val logProcess = try {
|
||||||
|
Runtime.getRuntime().exec(logCommand)
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
ioe.printStackTrace()
|
||||||
|
return ioe.stackTraceToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return logProcess.inputStream.bufferedReader().readText()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLogData(): String = if (failedToLoad) {
|
||||||
|
dumpLogcatText()
|
||||||
|
} else {
|
||||||
|
_lineState.joinToString("\n") { it.asSimpleString }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadLogs() {
|
private fun loadLogs() {
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package com.geode.launcher.utils
|
||||||
|
|
||||||
|
import android.content.ContentUris
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class FileUtils {
|
||||||
|
// copied from https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android
|
||||||
|
// i am actually very lazy to move this to a separate class
|
||||||
|
// hello zmx i moved it
|
||||||
|
companion object {
|
||||||
|
fun getRealPathFromURI(context: Context, uri: Uri): String? {
|
||||||
|
when {
|
||||||
|
// DocumentProvider
|
||||||
|
DocumentsContract.isDocumentUri(context, uri) -> {
|
||||||
|
when {
|
||||||
|
// ExternalStorageProvider
|
||||||
|
isExternalStorageDocument(uri) -> {
|
||||||
|
return handleExternalStorageDocument(context, uri)
|
||||||
|
}
|
||||||
|
isDownloadsDocument(uri) -> {
|
||||||
|
return handleDownloadsDocument(context, uri)
|
||||||
|
}
|
||||||
|
isMediaDocument(uri) -> {
|
||||||
|
return handleMediaDocument(context, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add the geode provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"content".equals(uri.scheme, ignoreCase = true) -> {
|
||||||
|
// Return the remote address
|
||||||
|
if (isGooglePhotosUri(uri)) return uri.lastPathSegment
|
||||||
|
// This happened with a directory
|
||||||
|
if (isExternalStorageDocument(uri)) {
|
||||||
|
return handleExternalStorageDocument(context, uri)
|
||||||
|
}
|
||||||
|
return getDataColumn(context, uri, null, null)
|
||||||
|
}
|
||||||
|
"file".equals(uri.scheme, ignoreCase = true) -> {
|
||||||
|
return uri.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleMediaDocument(context: Context, uri: Uri): String? {
|
||||||
|
val docId = DocumentsContract.getDocumentId(uri)
|
||||||
|
val split = docId.split(":").toTypedArray()
|
||||||
|
val type = split[0]
|
||||||
|
var contentUri: Uri? = null
|
||||||
|
when (type) {
|
||||||
|
"image" -> {
|
||||||
|
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
|
}
|
||||||
|
"video" -> {
|
||||||
|
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||||
|
}
|
||||||
|
"audio" -> {
|
||||||
|
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val selection = "_id=?"
|
||||||
|
val selectionArgs = arrayOf(split[1])
|
||||||
|
return getDataColumn(
|
||||||
|
context,
|
||||||
|
contentUri,
|
||||||
|
selection,
|
||||||
|
selectionArgs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDownloadsDocument(context: Context, uri: Uri): String? {
|
||||||
|
val fileName = getFilePath(context, uri)
|
||||||
|
if (fileName != null) {
|
||||||
|
return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
|
||||||
|
}
|
||||||
|
var id = DocumentsContract.getDocumentId(uri)
|
||||||
|
if (id.startsWith("raw:")) {
|
||||||
|
id = id.replaceFirst("raw:".toRegex(), "")
|
||||||
|
val file = File(id)
|
||||||
|
if (file.exists()) return id
|
||||||
|
}
|
||||||
|
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
|
||||||
|
return getDataColumn(context, contentUri, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleExternalStorageDocument(context: Context, uri: Uri): String {
|
||||||
|
val docId: String = if (DocumentsContract.isDocumentUri(context, uri)) {
|
||||||
|
DocumentsContract.getDocumentId(uri)
|
||||||
|
} else {
|
||||||
|
// please please please PLEASE let this be the only other case
|
||||||
|
DocumentsContract.getTreeDocumentId(uri)
|
||||||
|
}
|
||||||
|
val split = docId.split(":").toTypedArray()
|
||||||
|
val type = split[0]
|
||||||
|
// This is for checking Main Memory
|
||||||
|
return if ("primary".equals(type, ignoreCase = true)) {
|
||||||
|
if (split.size > 1) {
|
||||||
|
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
|
||||||
|
} else {
|
||||||
|
Environment.getExternalStorageDirectory().toString() + "/"
|
||||||
|
}
|
||||||
|
// This is for checking SD Card
|
||||||
|
} else {
|
||||||
|
"storage" + "/" + docId.replace(":", "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun getFilePath(context: Context, uri: Uri?): String? {
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
val projection = arrayOf(
|
||||||
|
MediaStore.MediaColumns.DISPLAY_NAME
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (uri == null) return null
|
||||||
|
cursor = context.contentResolver.query(
|
||||||
|
uri, projection, null, null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||||
|
val data = cursor.getString(index)
|
||||||
|
cursor.close()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
cursor?.close()
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDataColumn(context: Context, uri: Uri?, selection: String?,
|
||||||
|
selectionArgs: Array<String>?): String? {
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
val column = "_data"
|
||||||
|
val projection = arrayOf(
|
||||||
|
column
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (uri == null) return null
|
||||||
|
cursor = context.contentResolver.query(
|
||||||
|
uri, projection, selection, selectionArgs,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
val index = cursor.getColumnIndexOrThrow(column)
|
||||||
|
val data = cursor.getString(index)
|
||||||
|
cursor.close()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri The Uri to check.
|
||||||
|
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||||
|
*/
|
||||||
|
private fun isExternalStorageDocument(uri: Uri): Boolean {
|
||||||
|
return "com.android.externalstorage.documents" == uri.authority
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri The Uri to check.
|
||||||
|
* @return Whether the Uri authority is DownloadsProvider.
|
||||||
|
*/
|
||||||
|
private fun isDownloadsDocument(uri: Uri): Boolean {
|
||||||
|
return "com.android.providers.downloads.documents" == uri.authority
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri The Uri to check.
|
||||||
|
* @return Whether the Uri authority is MediaProvider.
|
||||||
|
*/
|
||||||
|
private fun isMediaDocument(uri: Uri): Boolean {
|
||||||
|
return "com.android.providers.media.documents" == uri.authority
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri The Uri to check.
|
||||||
|
* @return Whether the Uri authority is Google Photos.
|
||||||
|
*/
|
||||||
|
private fun isGooglePhotosUri(uri: Uri): Boolean {
|
||||||
|
return "com.google.android.apps.photos.content" == uri.authority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,62 +1,64 @@
|
|||||||
package com.geode.launcher.utils
|
package com.geode.launcher.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.ContentUris
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.database.Cursor
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
|
||||||
import android.provider.OpenableColumns
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.geode.launcher.activityresult.GeodeOpenFileActivityResult
|
||||||
|
import com.geode.launcher.activityresult.GeodeOpenFilesActivityResult
|
||||||
|
import com.geode.launcher.activityresult.GeodeSaveFileActivityResult
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.net.URI
|
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
object GeodeUtils {
|
object GeodeUtils {
|
||||||
private lateinit var activity: WeakReference<AppCompatActivity>
|
private lateinit var activity: WeakReference<AppCompatActivity>
|
||||||
private lateinit var fileResultLauncher: ActivityResultLauncher<Intent>
|
private lateinit var openFileResultLauncher: ActivityResultLauncher<GeodeOpenFileActivityResult.OpenFileParams>
|
||||||
private lateinit var filesResultLauncher: ActivityResultLauncher<Intent>
|
private lateinit var openDirectoryResultLauncher: ActivityResultLauncher<Uri?>
|
||||||
|
private lateinit var openFilesResultLauncher: ActivityResultLauncher<GeodeOpenFilesActivityResult.OpenFileParams>
|
||||||
|
private lateinit var saveFileResultLauncher: ActivityResultLauncher<GeodeSaveFileActivityResult.SaveFileParams>
|
||||||
|
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
|
||||||
|
|
||||||
fun setContext(activity: AppCompatActivity) {
|
fun setContext(activity: AppCompatActivity) {
|
||||||
this.activity = WeakReference(activity)
|
this.activity = WeakReference(activity)
|
||||||
fileResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
openFileResultLauncher = activity.registerForActivityResult(GeodeOpenFileActivityResult()) { uri ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
|
||||||
val data = result.data
|
|
||||||
if (data != null) {
|
|
||||||
val uri = data.data
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
val path = getRealPathFromURI(activity, uri)
|
val path = FileUtils.getRealPathFromURI(activity, uri)
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
selectFileCallback(path)
|
selectFileCallback(path)
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
failedCallback()
|
||||||
}
|
}
|
||||||
|
openDirectoryResultLauncher = activity.registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
|
||||||
|
if (it != null) {
|
||||||
|
val path = FileUtils.getRealPathFromURI(activity, it)
|
||||||
|
if (path != null)
|
||||||
|
selectFileCallback(path)
|
||||||
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
failedCallback()
|
failedCallback()
|
||||||
}
|
}
|
||||||
filesResultLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
openFilesResultLauncher = activity.registerForActivityResult(GeodeOpenFilesActivityResult()) { result ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.isEmpty()) {
|
||||||
val data = result.data
|
failedCallback()
|
||||||
if (data != null) {
|
return@registerForActivityResult
|
||||||
val clipData = data.clipData
|
}
|
||||||
if (clipData != null) {
|
val paths: Array<String> = Array(result.size) {"n = $it"}
|
||||||
val paths = Array(clipData.itemCount) { "" }
|
for (i in result.indices) {
|
||||||
for (i in 0 until clipData.itemCount) {
|
val path = FileUtils.getRealPathFromURI(activity, result[i])
|
||||||
val path = getRealPathFromURI(activity, clipData.getItemAt(i).uri)
|
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
paths[i] = path
|
paths[i] = path
|
||||||
}
|
}
|
||||||
@@ -64,24 +66,30 @@ object GeodeUtils {
|
|||||||
selectFilesCallback(paths)
|
selectFilesCallback(paths)
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
saveFileResultLauncher = activity.registerForActivityResult(GeodeSaveFileActivityResult()) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
val path = FileUtils.getRealPathFromURI(activity, uri)
|
||||||
|
if (path != null) {
|
||||||
|
selectFileCallback(path)
|
||||||
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
failedCallback()
|
failedCallback()
|
||||||
}
|
}
|
||||||
|
requestPermissionLauncher = activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
|
permissionCallback(isGranted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getLogcatCrashBuffer(): String {
|
fun getLogcatCrashBuffer(): String {
|
||||||
try {
|
return try {
|
||||||
val logcatProcess = Runtime.getRuntime().exec("logcat -v brief -b crash -d")
|
val logcatProcess = Runtime.getRuntime().exec("logcat -v brief -b crash -d")
|
||||||
|
|
||||||
val logcatOutput = logcatProcess.inputStream.bufferedReader().readText()
|
logcatProcess.inputStream.bufferedReader().readText()
|
||||||
|
} catch (e: Exception) {
|
||||||
return logcatOutput
|
|
||||||
}
|
|
||||||
catch (e: Exception) {
|
|
||||||
Log.e("Geode", "Failed to get logcat crash buffer", e)
|
Log.e("Geode", "Failed to get logcat crash buffer", e)
|
||||||
return ""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,18 +119,19 @@ object GeodeUtils {
|
|||||||
activity.get()?.run {
|
activity.get()?.run {
|
||||||
packageManager.getLaunchIntentForPackage(packageName)?.also {
|
packageManager.getLaunchIntentForPackage(packageName)?.also {
|
||||||
val mainIntent = Intent.makeRestartActivityTask(it.component)
|
val mainIntent = Intent.makeRestartActivityTask(it.component)
|
||||||
mainIntent.putExtra("restarted", true);
|
mainIntent.putExtra("restarted", true)
|
||||||
startActivity(mainIntent)
|
startActivity(mainIntent)
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO As of now this is unused
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun openFolder(path: String): Boolean {
|
fun openFolder(path: String): Boolean {
|
||||||
activity.get()?.run {
|
activity.get()?.run {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
DocumentFile.fromFile(File(path)).also {
|
DocumentFile.fromFile(File(path)).also {
|
||||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
|
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
|
||||||
@@ -137,28 +146,27 @@ object GeodeUtils {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("KotlinJniMissingFunction")
|
||||||
external fun selectFileCallback(path: String)
|
external fun selectFileCallback(path: String)
|
||||||
|
|
||||||
|
@Suppress("KotlinJniMissingFunction")
|
||||||
external fun selectFilesCallback(paths: Array<String>)
|
external fun selectFilesCallback(paths: Array<String>)
|
||||||
|
|
||||||
|
@Suppress("KotlinJniMissingFunction")
|
||||||
external fun failedCallback()
|
external fun failedCallback()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun selectFile(path: String): Boolean {
|
fun selectFile(path: String): Boolean {
|
||||||
activity.get()?.run {
|
activity.get()?.run {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
var uri: Uri?
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
DocumentFile.fromFile(File(path)).also {
|
DocumentFile.fromFile(File(path)).also {
|
||||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
|
uri = it.uri
|
||||||
}
|
}
|
||||||
intent.setType("*/*")
|
try {
|
||||||
|
openFileResultLauncher.launch(GeodeOpenFileActivityResult.OpenFileParams(arrayOf("*/*"), uri))
|
||||||
if (intent.resolveActivity(packageManager) != null) {
|
|
||||||
print("Geode Selectfile")
|
|
||||||
fileResultLauncher.launch(intent)
|
|
||||||
print("Geode launched ")
|
|
||||||
return true
|
return true
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -167,18 +175,15 @@ object GeodeUtils {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun selectFiles(path: String): Boolean {
|
fun selectFiles(path: String): Boolean {
|
||||||
activity.get()?.run {
|
activity.get()?.run {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
var uri: Uri?
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
DocumentFile.fromFile(File(path)).also {
|
DocumentFile.fromFile(File(path)).also {
|
||||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
|
uri = it.uri
|
||||||
}
|
}
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
try {
|
||||||
intent.setType("*/*")
|
openFilesResultLauncher.launch(GeodeOpenFilesActivityResult.OpenFileParams(arrayOf("*/*"), uri))
|
||||||
|
|
||||||
if (intent.resolveActivity(packageManager) != null) {
|
|
||||||
filesResultLauncher.launch(intent)
|
|
||||||
return true
|
return true
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -187,17 +192,15 @@ object GeodeUtils {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun selectFolder(path: String): Boolean {
|
fun selectFolder(path: String): Boolean {
|
||||||
activity.get()?.run {
|
activity.get()?.run {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
var uri: Uri?
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
DocumentFile.fromFile(File(path)).also {
|
DocumentFile.fromFile(File(path)).also {
|
||||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
|
uri = it.uri
|
||||||
}
|
}
|
||||||
intent.setType("*/*")
|
try {
|
||||||
|
openDirectoryResultLauncher.launch(uri)
|
||||||
if (intent.resolveActivity(packageManager) != null) {
|
|
||||||
fileResultLauncher.launch(intent)
|
|
||||||
return true
|
return true
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -206,17 +209,16 @@ object GeodeUtils {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun createFile(path: String): Boolean {
|
fun createFile(path: String): Boolean {
|
||||||
activity.get()?.run {
|
activity.get()?.run {
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
var uri: Uri?
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
DocumentFile.fromFile(File(path)).also {
|
DocumentFile.fromFile(File(path)).also {
|
||||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
|
uri = it.uri
|
||||||
}
|
}
|
||||||
intent.setType("*/*")
|
|
||||||
|
|
||||||
if (intent.resolveActivity(packageManager) != null) {
|
try {
|
||||||
fileResultLauncher.launch(intent)
|
saveFileResultLauncher.launch(GeodeSaveFileActivityResult.SaveFileParams(null, uri))
|
||||||
return true
|
return true
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -250,155 +252,38 @@ object GeodeUtils {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// copied from https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android
|
|
||||||
// i am actually very lazy to move this to a separate class
|
|
||||||
|
|
||||||
fun getRealPathFromURI(context: Context, uri: Uri): String? {
|
|
||||||
when {
|
|
||||||
// DocumentProvider
|
|
||||||
DocumentsContract.isDocumentUri(context, uri) -> {
|
|
||||||
when {
|
|
||||||
// ExternalStorageProvider
|
|
||||||
isExternalStorageDocument(uri) -> {
|
|
||||||
val docId = DocumentsContract.getDocumentId(uri)
|
|
||||||
val split = docId.split(":").toTypedArray()
|
|
||||||
val type = split[0]
|
|
||||||
// This is for checking Main Memory
|
|
||||||
return if ("primary".equals(type, ignoreCase = true)) {
|
|
||||||
if (split.size > 1) {
|
|
||||||
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
|
|
||||||
} else {
|
|
||||||
Environment.getExternalStorageDirectory().toString() + "/"
|
|
||||||
}
|
|
||||||
// This is for checking SD Card
|
|
||||||
} else {
|
|
||||||
"storage" + "/" + docId.replace(":", "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isDownloadsDocument(uri) -> {
|
|
||||||
val fileName = getFilePath(context, uri)
|
|
||||||
if (fileName != null) {
|
|
||||||
return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
|
|
||||||
}
|
|
||||||
var id = DocumentsContract.getDocumentId(uri)
|
|
||||||
if (id.startsWith("raw:")) {
|
|
||||||
id = id.replaceFirst("raw:".toRegex(), "")
|
|
||||||
val file = File(id)
|
|
||||||
if (file.exists()) return id
|
|
||||||
}
|
|
||||||
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
|
|
||||||
return getDataColumn(context, contentUri, null, null)
|
|
||||||
}
|
|
||||||
isMediaDocument(uri) -> {
|
|
||||||
val docId = DocumentsContract.getDocumentId(uri)
|
|
||||||
val split = docId.split(":").toTypedArray()
|
|
||||||
val type = split[0]
|
|
||||||
var contentUri: Uri? = null
|
|
||||||
when (type) {
|
|
||||||
"image" -> {
|
|
||||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
"video" -> {
|
|
||||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
"audio" -> {
|
|
||||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val selection = "_id=?"
|
|
||||||
val selectionArgs = arrayOf(split[1])
|
|
||||||
return getDataColumn(context, contentUri, selection, selectionArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add the geode provider
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"content".equals(uri.scheme, ignoreCase = true) -> {
|
|
||||||
// Return the remote address
|
|
||||||
if (isGooglePhotosUri(uri)) return uri.lastPathSegment
|
|
||||||
return getDataColumn(context, uri, null, null)
|
|
||||||
}
|
|
||||||
"file".equals(uri.scheme, ignoreCase = true) -> {
|
|
||||||
return uri.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDataColumn(context: Context, uri: Uri?, selection: String?,
|
|
||||||
selectionArgs: Array<String>?): String? {
|
|
||||||
var cursor: Cursor? = null
|
|
||||||
val column = "_data"
|
|
||||||
val projection = arrayOf(
|
|
||||||
column
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
if (uri == null) return null
|
|
||||||
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs,
|
|
||||||
null)
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
val index = cursor.getColumnIndexOrThrow(column)
|
|
||||||
return cursor.getString(index)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
cursor?.close()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getFilePath(context: Context, uri: Uri?): String? {
|
|
||||||
var cursor: Cursor? = null
|
|
||||||
val projection = arrayOf(
|
|
||||||
MediaStore.MediaColumns.DISPLAY_NAME
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
if (uri == null) return null
|
|
||||||
cursor = context.contentResolver.query(uri, projection, null, null,
|
|
||||||
null)
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
|
|
||||||
return cursor.getString(index)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
cursor?.close()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is ExternalStorageProvider.
|
|
||||||
*/
|
|
||||||
fun isExternalStorageDocument(uri: Uri): Boolean {
|
|
||||||
return "com.android.externalstorage.documents" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is DownloadsProvider.
|
|
||||||
*/
|
|
||||||
fun isDownloadsDocument(uri: Uri): Boolean {
|
|
||||||
return "com.android.providers.downloads.documents" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is MediaProvider.
|
|
||||||
*/
|
|
||||||
fun isMediaDocument(uri: Uri): Boolean {
|
|
||||||
return "com.android.providers.media.documents" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param uri The Uri to check.
|
|
||||||
* @return Whether the Uri authority is Google Photos.
|
|
||||||
*/
|
|
||||||
fun isGooglePhotosUri(uri: Uri): Boolean {
|
|
||||||
return "com.google.android.apps.photos.content" == uri.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isGeodeUri(uri: Uri): Boolean {
|
fun isGeodeUri(uri: Uri): Boolean {
|
||||||
return "com.geode.launcher.user" == uri.authority
|
return "com.geode.launcher.user" == uri.authority
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getPermissionStatus(permission: String): Boolean {
|
||||||
|
return ContextCompat.checkSelfPermission(
|
||||||
|
activity.get()?.applicationContext ?: return false,
|
||||||
|
permission
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun requestPermission(permission: String) {
|
||||||
|
if (getPermissionStatus(permission)) {
|
||||||
|
permissionCallback(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.get()?.run {
|
||||||
|
try {
|
||||||
|
requestPermissionLauncher.launch(permission)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
permissionCallback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionCallback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("KotlinJniMissingFunction")
|
||||||
|
external fun permissionCallback(granted: Boolean)
|
||||||
}
|
}
|
||||||
@@ -89,8 +89,9 @@ object LaunchUtils {
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
val applicationArchitecture: String = Build.CPU_ABI
|
val applicationArchitecture: String = Build.CPU_ABI
|
||||||
|
|
||||||
val platformName: String = if (applicationArchitecture == "arm64-v8a")
|
val is64bit = applicationArchitecture == "arm64-v8a"
|
||||||
"android64" else "android32"
|
|
||||||
|
val platformName: String = if (is64bit) "android64" else "android32"
|
||||||
|
|
||||||
val geodeFilename: String = "Geode.$platformName.so"
|
val geodeFilename: String = "Geode.$platformName.so"
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,11 @@
|
|||||||
<string name="launcher_warning_icon_alt">warning</string>
|
<string name="launcher_warning_icon_alt">warning</string>
|
||||||
<string name="launcher_error_icon_alt">error</string>
|
<string name="launcher_error_icon_alt">error</string>
|
||||||
<string name="launcher_unsupported_version_title">Unsupported version</string>
|
<string name="launcher_unsupported_version_title">Unsupported version</string>
|
||||||
|
<string name="launcher_outdated_version_description">
|
||||||
|
The current version of Geometry Dash is not compatible with the version supported by the launcher (%2$s). You must update Geometry Dash to fix this.\nLaunch will continue, but crashes may occur!
|
||||||
|
</string>
|
||||||
<string name="launcher_unsupported_version_description">
|
<string name="launcher_unsupported_version_description">
|
||||||
The current version of Geometry Dash is not compatible with the version supported by the launcher (%2$s). A launcher update is required to fix this.\nLaunch will continue, but crashes may occur!
|
The current version of Geometry Dash is not compatible with the version supported by the launcher. A launcher update is required to fix this.\nLaunch will continue, but crashes may occur!
|
||||||
</string>
|
</string>
|
||||||
<string name="launcher_update_available">A new launcher update is available!</string>
|
<string name="launcher_update_available">A new launcher update is available!</string>
|
||||||
<string name="launcher_update_dismiss">Dismiss</string>
|
<string name="launcher_update_dismiss">Dismiss</string>
|
||||||
@@ -44,7 +47,8 @@
|
|||||||
<!-- GeometryDashActivity strings -->
|
<!-- GeometryDashActivity strings -->
|
||||||
<string name="load_failed_link_error">Failed to link libraries</string>
|
<string name="load_failed_link_error">Failed to link libraries</string>
|
||||||
<string name="load_failed_link_error_description">Game libraries could not be found. This may occur if the architecture of the launcher and Geometry Dash do not match, or from a system bug.\nCheck application logs for more details.</string>
|
<string name="load_failed_link_error_description">Game libraries could not be found. This may occur if the architecture of the launcher and Geometry Dash do not match, or from a system bug.\nCheck application logs for more details.</string>
|
||||||
<string name="load_failed_abi_error_description">The architecture of the launcher and Geometry Dash do not match. Please reinstall Geometry Dash from the Play Store or use the \"android32\" version of the launcher to resolve this issue.\nCheck application logs for more details.</string>
|
<string name="load_failed_abi_error_need_64bit_description">The architecture of the launcher and Geometry Dash do not match. Please reinstall Geometry Dash from the Play Store or use the 64-bit version of the launcher to resolve this issue.\nCheck application logs for more details.</string>
|
||||||
|
<string name="load_failed_abi_error_need_32bit_description">The architecture of the launcher and Geometry Dash do not match. Please reinstall Geometry Dash from the Play Store or use the 32-bit version of the launcher to resolve this issue.\nCheck application logs for more details.</string>
|
||||||
<string name="load_failed_generic_error">Load failed</string>
|
<string name="load_failed_generic_error">Load failed</string>
|
||||||
<string name="load_failed_generic_error_description">Uncaught error during game load: `%1$s`.\nCheck application logs for more details. Please report this issue!</string>
|
<string name="load_failed_generic_error_description">Uncaught error during game load: `%1$s`.\nCheck application logs for more details. Please report this issue!</string>
|
||||||
<string name="message_box_cancel">Cancel</string>
|
<string name="message_box_cancel">Cancel</string>
|
||||||
|
|||||||
Reference in new issue
Block a user