How am I supposed to invoke clearApplicationUserData() in Kotlin? - android

From the app context? Do I have to include a permission?
And does it close/reboot the app?

Try this
private fun clearAppData() {
try {
// clearing app data
if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) {
(getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).clearApplicationUserData() // note: it has a return value!
} else {
val packageName = applicationContext.packageName
val runtime = Runtime.getRuntime()
runtime.exec("pm clear $packageName")
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
Note: This might kill/close your application after clearing user data

Related

How do I check the value changed in unit test (Android)

`I am having a hard time getting the middle? value of a stateFlow and checking using assertTrue
Here is my code for testing
fun initData() = viewModelScope.launch {
modifiableUiState.emit(UiState.Loading)
try {
testSomeThing()
modifiableUiState.emit(UiState.Loaded)
} catch (e: Exception) {
modifiableUiState.emit(UiState.Error(e))
} finally {
modifiableUiState.emit(UiState.Idle)
}
}
and my test code
#Test
fun load_store_data_succeed() = runTest {
// given
var uiState: PaySettingBalanceNotificationViewModel.UiState? = null
val collectJob = launch {
viewModel.uiState.collect { uiState = it }
}
// when
viewModel.initMandatoryData()
runCurrent()
// then
assertTrue(uiState is PaySettingBalanceNotificationViewModel.UiState.InitialDataLoaded)
collectJob.cancel()
}
since in finally block it will emit Idle, I can't check if the value has changed to InitialDataLoaded. Is there a way I can check the history? of a value for testing?`

Android 12 breaks installation of APK via the Package Manager?

I was working on an update for my app, a section of which deals with downloading and installing an APK file.
As long as the previous version were targeting SDK 30 everything worked pretty smoothly. But as soon as I incremented the target and compile SDK to 32 it just started behaving queerly.
​
Here is the code that deals which the package manager and the installation:
private fun installOnClickListener() {
binding.termuxInstallCard.showProgress()
var session: PackageInstaller.Session? = null
try {
val packageInstaller: PackageInstaller =
requireContext().packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL
)
val sessionId = packageInstaller.createSession(params)
session = packageInstaller.openSession(sessionId)
viewModel.addApkToSession(session)
var installBroadcast: PendingIntent? = null
val intent =
Intent(PACKAGE_INSTALLED_ACTION).putExtra(
"packageName",
"com.termux"
)
installBroadcast = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getBroadcast(
context,
0,
intent,
FLAG_MUTABLE
)
} else {
PendingIntent.getBroadcast(context, 0, intent, FLAG_UPDATE_CURRENT)
}
session.commit(installBroadcast.intentSender)
session.close()
} catch (e: IOException) {
throw RuntimeException("Couldn't install package", e)
} catch (e: RuntimeException) {
session?.abandon()
throw e
} finally {
session?.close()
}
}
Here is what is happening:
​
As I am targeting SDK 32, I am required to specify the Mutability of PendingIntent
Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent
When I use:
FLAG_MUTABLE- The installation just fails stating the error code- STATUS_FAILURE_INVALID with no extra message for debugging in EXTRA_STATUS_MESSAGE. The thing is that when I try to install the same downloaded APK via the adb shell, it just installs normally without any issues.
FLAG_IMMUTABLE- The installation succeeds without prompting user with the installation dialog but nothing is actually installed.
​
More code in case you need it-
fun addApkToInstallSession(
path: String,
session: PackageInstaller.Session
) {
val file = File("${context.filesDir.path}/$path")
val packageInSession: OutputStream = session.openWrite("com.termux", 0, -1)
val inputStream = FileInputStream(file)
val byteStream = inputStream.read()
try {
var c: Int
val buffer = ByteArray(16384)
while (inputStream.read(buffer).also { c = it } >= 0) {
packageInSession.write(buffer, 0, c)
}
} catch (e: IOException) {
println("IOEX")
} finally {
try {
packageInSession.close()
inputStream.close()
} catch (e: IOException) {
println("IOEX in closing the stream")
}
}
}
​
private val broadcastReceiverForInstallEvents = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
lifecycleScope.launch(Dispatchers.IO) {
val extras = intent.extras
val status = extras!!.getInt(PackageInstaller.EXTRA_STATUS)
val packageName = extras.getString("packageName")!!
if (PACKAGE_INSTALLED_ACTION == intent.action) {
println("STATUS $status")
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
try {
val confirmIntent = extras[Intent.EXTRA_INTENT] as Intent
confirmIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(confirmIntent)
} catch (e: Exception) {
lifecycleScope.launch(Dispatchers.Main) {
Toast.makeText(
requireContext(),
"We could not find an application to handle the installation of apps. Please download a package installer.",
Toast.LENGTH_SHORT
).show()
}
}
}
PackageInstaller.STATUS_SUCCESS -> {
lifecycleScope.launch(Dispatchers.Main) {
println("$packageName Install succeeded!")
// todo all done animation
binding.termuxInstallCard.markAsComplete()
Toast.makeText(requireContext(), "All Done!", Toast.LENGTH_SHORT)
.show()
lifecycleScope.launch {
// viewModel.setTermuxSetupDone()
}
/* redirecting... */
Handler(Looper.getMainLooper()).postDelayed({
redirect()
}, 2000)
}
}
PackageInstaller.STATUS_FAILURE, PackageInstaller.STATUS_FAILURE_ABORTED, PackageInstaller.STATUS_FAILURE_BLOCKED, PackageInstaller.STATUS_FAILURE_CONFLICT, PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, PackageInstaller.STATUS_FAILURE_INVALID, PackageInstaller.STATUS_FAILURE_STORAGE -> {
lifecycleScope.launch(Dispatchers.Main) {
println("Extra Status Message${extras.getString("EXTRA_STATUS_MESSAGE")}")
"There was an error installing Termux. Please retry.".showSnackbar(
binding.root,
true
)
binding.termuxInstallCard.hideProgress()
}
}
else -> {
lifecycleScope.launch(Dispatchers.Main) {
println("$packageName Install failed else!")
// exitActivity("Package failed to install -> Unknown Error!")
binding.termuxInstallCard.hideProgress()
}
}
}
}
}
}
}
I would really appreciate some help!
​
In the past when I tried to install APKs I used either Knox on Samsung devices or I'd refer the user to install the package via package manager, this is the code I've used:
public static void cleanInstall(String datum, Context context) {
File file = new File(datum);
if (file.exists()) {
Intent intent = new Intent(Intent.ACTION_VIEW);
String type = "application/vnd.android.package-archive";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri downloadedApk = FileProvider.getUriForFile(context, ".MyPackage.myFileProvider", file);
intent.setDataAndType(downloadedApk, type);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(Uri.fromFile(file), type);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
} else {
Toast.makeText(context, "ّFile not found!", Toast.LENGTH_SHORT).show();
}
}
I've not used this code in a while so I don't know if it works anymore but it's something you might want to check, to see if it does. Make sure the path you are referring to does not reside on SD as you'll probably face permission denied issues.
On calculating the hash of the file I was committing to the session of the Package Manager, I found out that it didn't write the APK file correctly.
val inputStream: InputStream = session.openRead("com.termux")
val file = File("${requireContext().filesDir.path}/test2.apk")
if (!file.exists()) {
file.createNewFile()
}
try {
inputStream.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
} catch (e: Exception) {
println("Exception occured $e")
}
if (file.length() > 0) {
val hash = file.getMD5Hash()
println("HASH of session - $hash")
}
Fixed that and with the combination of the Mutable pending intent. The package is now installing perfectly.

How to cancel api request in view model in Mvvm?

I am try to cancel to api request if user calls api to fast then only the latest api should return the result all previous requests should be discarded but this isn't working anyone knows the solution please help thanks
class CartViewModel(val store: Account) : BaseViewModel() {
private var requestCalculation: Job? = null
fun recalculate() {
requestCalculation.let {
if (it != null) {
if (it.isActive) {
requestCalculation!!.cancel()
}
}
}
requestCalculation = viewModelScope.launch(Dispatchers.IO) {
isLoading.postValue(true)
try {
val order = CCOrderManager.shared.calculateTaxesAndApplyRewards(store.id)
refreshOrder()
} catch (e: Exception) {
exception.postValue(e.localizedMessage ?: e.toString())
}
}
}
}
The order of cancellation and execution is wrong. When the function starts, requestCalculation is null, so it cannot be canceled. Make sure you start first the coroutine and cancel it later. For example:
private var requestCalculation: Job? = null
fun recalculate() {
requestCalculation = viewModelScope.launch(Dispatchers.IO) {
delay(10_000)
// do your work...
}
// now the job can be canceled
requestCalculation?.cancel()
}
Adding a check after api call this.isActive {return#launch} finally worked for me...
fun recalculate() {
calculationRequest?.cancel()
isLoading.postValue(true)
calculationRequest = viewModelScope.launch(Dispatchers.IO) {
try {
val order =
CCOrderManager.shared.calculateTaxesAndApplyRewards(store.id)
// this check is the solution *******
if (!this.isActive) {return#launch}
val catalog = CatalogManager.shared().catalog
} catch (e: Exception) {
}
}
}

How to get directory's uuid using StorageManager on Android API below 26?

I created a helper function to check the remaining space of any given directory.
#RequiresApi(Build.VERSION_CODES.O)
fun Context.hasFreeSpace(directory: File, requiredStorageSpace: Long): Boolean{
return try {
val storageManager = getSystemService<StorageManager>()
val directoryUUID = storageManager!!.getUuidForPath(directory)
val availableBytes = storageManager.getAllocatableBytes(directoryUUID)
availableBytes > requiredStorageSpace
}catch (e: Exception){
e.printStackTrace()
false
}
}
Follow this link actually.
https://developer.android.com/training/data-storage/app-specific#query-free-space
The problem is I get storageManager!!.getUuidForPath and storageManager.getAllocatableBytes both require for API >= 26.
I did google around but not thing came back on how to get the directory's UUID on API < 26.
Does anyone have any idea how to achieve that?
Thanks
Well, I guess I need a different approach. As I googled, UUID required was added when Android O was released. So basically, no such thing gets directory UUID exits before O. This is my helper function now.
#SuppressLint("NewApi")
fun Context.hasFreeSpace(directory: File, requiredStorageSpace: Long): Boolean {
return try {
val api = Build.VERSION.SDK_INT
val availableBytes = when {
api >= Build.VERSION_CODES.O -> {
val storageManager = getSystemService<StorageManager>()
val directoryUUID = storageManager!!.getUuidForPath(directory)
storageManager.getAllocatableBytes(directoryUUID)
}
else -> {
val stat = StatFs(directory.path)
stat.availableBlocksLong * stat.blockSizeLong
}
}
availableBytes > requiredStorageSpace
} catch (e: Exception) {
e.printStackTrace()
false
}
}

Get EMUI version of a device programmatically

During my application execution, how can I get EMUI version?
Is there any system method to get EMUI version?
It is possible through accessing system properties like:
#SuppressLint("PrivateApi")
private fun Any?.readEMUIVersion() : String {
try {
val propertyClass = Class.forName("android.os.SystemProperties")
val method: Method = propertyClass.getMethod("get", String::class.java)
var versionEmui = method.invoke(propertyClass, "ro.build.version.emui") as String
if (versionEmui.startsWith("EmotionUI_")) {
versionEmui = versionEmui.substring(10, versionEmui.length)
}
return versionEmui
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}
return ""
}
However, this is a private Api and if it is not suitable in your case, you can possibly use this workaround (would work for EMUI 9 and 10, however definitely wouldn't for EMUI 5 or below (~android 7)):
#TargetApi(3)
fun Any?.extractEmuiVersion() : String {
return try {
val line: String = Build.DISPLAY
val spaceIndex = line.indexOf(" ")
val lastIndex = line.indexOf("(")
if (lastIndex != -1) {
line.substring(spaceIndex, lastIndex)
} else line.substring(spaceIndex)
} catch (e: Exception) { "" }
}
Any suggestions how to improve the answer are highly appreciated!

Categories

Resources