I'm writing unit tests for a use case that basically creates a file with the contents of an InputStream.
However, with the lines:
val path = context.filesDir.path + "/issues.csv"
val fos = FileOutputStream(path)
I'm having trouble, as the when trying to create the FileOutputStream from the path it always results in a FileNotFoundException.
This is the complete code:
class SaveFileUseCase #Inject constructor(
private val context: Context,
#DefaultCoroutineDispatcher private val defaultCoroutineDispatcher: CoroutineDispatcher
) {
suspend operator fun invoke(body: ResponseBody) =
withContext(defaultCoroutineDispatcher) {
saveFile(body)
}
private fun saveFile(body: ResponseBody?) {
if (body == null) {
return
}
var input: InputStream? = null
try {
input = body.byteStream()
val path = context.filesDir.path + "/issues.csv"
val fos = FileOutputStream(path)
fos.use { output ->
val buffer = ByteArray(4 * 1024) // or other buffer size
var read: Int
while (input.read(buffer).also { read = it } != -1) {
output.write(buffer, 0, read)
}
output.flush()
}
} catch (e: Exception) {
Log.e("Error saving file", e.toString())
} finally {
input?.close()
}
}
}
How can I test this?
Thanks a lot in advance!
I'm attempting to install an APK programmatically on Android 12 but seem to be running into unknown issues at this point. All advice I've found regarding installing an APK programmatically seem to be deprecated.
Currently, I'm able to save my file but whenever I attempt to install it using PackageManager.PackageInstaller, it fails silently and I'm unable to find anything in the logs suggesting what the failure might've been.
Here's my package installer object.
object PackageInstaller {
#SuppressLint("WrongConstant")
#Throws(IOException::class)
fun installPackage(
context: Context,
installSessionId: String?,
packageName: String?,
apkStream: InputStream?
) {
val packageManger = context.packageManager
val packageInstaller = packageManger.packageInstaller
val params = android.content.pm.PackageInstaller.SessionParams(
android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
)
params.setAppPackageName(packageName)
var session: android.content.pm.PackageInstaller.Session? = null
try {
val sessionId = packageInstaller.createSession(params)
session = packageInstaller.openSession(sessionId)
val out = session.openWrite(installSessionId!!, 0, -1)
val buffer = ByteArray(1024)
var length: Int
var count = 0
if (apkStream != null) {
while (apkStream.read(buffer).also { length = it } != -1) {
out.write(buffer, 0, length)
count += length
}
}
session.fsync(out)
out.close()
val intent = Intent
intent.addFlags(Intent.ACTION_PACKAGE_ADDED)
Log.v("installer", "Installing..?")
session.commit(
PendingIntent.getBroadcast(
context, sessionId,
intent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
).intentSender
)
}finally {
session?.close()
}
}
}
At this point I'm pretty lost as to where to look next. Does anyone even know if this is still possible? Or a solution to this issue?
You can try with this it is working with android 12
class DownloadApk(private var context: WeakReference<Context>) {
#JvmOverloads
fun startDownloadingApk(url: String, fileName: String = "App Update") {
if (URLUtil.isValidUrl(url)) {
DownloadNewVersion(context, url, fileName).execute()
}
}
#Suppress("DEPRECATION")
private class DownloadNewVersion(
private val context: WeakReference<Context>,
val downloadUrl: String,
val fileName: String
) : AsyncTask<String, Int, Boolean>() {
private lateinit var bar: ProgressDialog
override fun onPreExecute() {
super.onPreExecute()
bar = ProgressDialog(context.get()).apply {
setCancelable(false)
setMessage("Downloading...")
isIndeterminate = true
setCanceledOnTouchOutside(false)
show()
}
}
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
var msg = ""
val progress = values[0]
if (progress != null) {
bar.progress = progress
msg = if (progress > 99) "Finishing... " else "Downloading... $progress%"
}
bar.apply {
isIndeterminate = false
max = 100
setMessage(msg)
}
}
override fun onPostExecute(result: Boolean?) {
super.onPostExecute(result)
bar.dismiss()
if (result != null && result) {
context.get()?.let {
Toast.makeText(it, "Update Done", Toast.LENGTH_SHORT).show()
}
} else {
context.get()?.let {
Toast.makeText(it, "Error: Try Again", Toast.LENGTH_SHORT).show()
}
}
}
override fun doInBackground(vararg p0: String?): Boolean {
var flag = false
try {
val path =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.toString() + "/"
var outputFile = File("$path$fileName.apk")
var repetition = 1
while (outputFile.exists()) {
outputFile = File("$path$fileName ($repetition).apk")
repetition++
}
val directory = File(path)
if (!directory.exists()) {
directory.mkdirs()
}
val url = URL(downloadUrl)
val c = url.openConnection() as HttpURLConnection
c.requestMethod = "GET"
c.connect()
val fos = FileOutputStream(outputFile)
val inputStream = c.inputStream
val totalSize = c.contentLength.toFloat() //size of apk
val buffer = ByteArray(1024)
var len1: Int
var per: Float
var downloaded = 0f
while (inputStream.read(buffer).also { len1 = it } != -1) {
fos.write(buffer, 0, len1)
downloaded += len1
per = (downloaded * 100 / totalSize)
publishProgress(per.toInt())
}
fos.close()
inputStream.close()
openNewVersion(outputFile.path)
flag = true
} catch (e: MalformedURLException) {
Log.e("DownloadApk", "Update Error: " + e.message)
flag = false
} catch (e: IOException) {
e.printStackTrace()
}
return flag
}
private fun openNewVersion(location: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(
getUriFromFile(location),
"application/vnd.android.package-archive"
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.get()?.startActivity(intent)
}
private fun getUriFromFile(filePath: String): Uri? {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Uri.fromFile(File(filePath))
} else {
context.get()?.let {
FileProvider.getUriForFile(
it,
it.packageName + ".provider",
File(filePath)
)
}
}
}
}
}
I want to load rtf files with webview using raw folder. Below is my code;
val urlWebView = findViewById<WebView>(R.id.webview)
readTextFromResource(R.raw.lettemp_369)?.let { urlWebView.loadData(it, "text/rtf", "utf-8") };
Below is function;
private fun readTextFromResource(resourceID: Int): String? {
val raw: InputStream = resources.openRawResource(resourceID)
val stream = ByteArrayOutputStream()
var i: Int
try {
i = raw.read()
while (i != -1) {
stream.write(i)
i = raw.read()
}
raw.close()
} catch (e: IOException) {
e.printStackTrace()
}
return stream.toString()
}
I want to load rtf files with webview using raw folder. Below is my code;
val urlWebView = findViewById<WebView>(R.id.webview)
readTextFromResource(R.raw.lettemp_369)?.let { urlWebView.loadData(it, "text/rtf", "utf-8") };
Below is function;
private fun readTextFromResource(resourceID: Int): String? {
val raw: InputStream = resources.openRawResource(resourceID)
val stream = ByteArrayOutputStream()
var i: Int
try {
i = raw.read()
while (i != -1) {
stream.write(i)
i = raw.read()
}
raw.close()
} catch (e: IOException) {
e.printStackTrace()
}
return stream.toString()
}
I m new in Kotlin and I'm trying to do a string request (with volley library and PHP)
I did this (like a Java)
val addUrl = "myurl"
val queue = Volley.newRequestQueue(this)
val postRequest = object : StringRequest(Request.Method.POST, addUrl,
Response.Listener<String> { response ->
//Toast.makeText(this, response, Toast.LENGTH_SHORT).show()
}, Response.ErrorListener {
//Toast.makeText(this, "Something wrong", Toast.LENGTH_SHORT).show()
}) {
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
params.put("uid", uid.toString())
return params
}
}
queue.add(postRequest)
and I have error at this point
val queue = Volley.newRequestQueue(this)
Because say that I can't use "this"
Type mismatch: inferred type is DataContainer but Context! was
expected
My data container don't extend AppCompatActivity(): so I can't use a "this#Activity" as another suggestion find online
This is the companion object
class DataContainer {
val noH = 32
val TAG = "DataContainer"
private var mDb : GlucoseDataBase
private val lock = java.lang.Object()
private var lastTimeStamp : Int = 0
private val user = FirebaseAuth.getInstance().currentUser
var uid:String? = null.toString()
//private var raw_data : ByteArray = byteArrayOf()
private var readings : MutableList<ByteArray> = mutableListOf()
constructor(context : Context) {
mDb = GlucoseDataBase.getInstance(context)
}
fun append(raw_data: ByteArray, readingTime: Long, sensorId : Long) {
user?.let {
// Name, email address, and profile photo Url
//val name = user.displayName
//val email = user.email
//val photoUrl = user.photoUrl
// Check if user's email is verified
//val emailVerified = user.isEmailVerified
// The user's ID, unique to the Firebase project. Do NOT use this value to
// authenticate with your backend server, if you have one. Use
// FirebaseUser.getToken() instead.
uid = user.uid
}
synchronized(lock) {
readings.add(raw_data.copyOf())
val timestamp = RawParser.timestamp(raw_data)
if (timestamp == 0) {
mDb.sensorContactDao().insert(SensorContact(0, readingTime, sensorId, 0, 0))
return
}
// Timestamp is 2 mod 15 every time a new reading to history is done.
val minutesSinceLast = (timestamp + 12) % 15
val start = readingTime - Time.MINUTE * (15 * (noH - 1) + minutesSinceLast)
val now_history = RawParser.history(raw_data)
val now_recent = RawParser.recent(raw_data)
Log.i("UID", uid.toString());
val history_prepared = prepare(now_history, sensorId, 15 * Time.MINUTE, start, minutesSinceLast != 14 && timestamp < Time.DURATION_MINUTES, true)
val data = history_prepared.toList();
// val data2= data[0] //primo elemento più vecchio (di timestamp) //proviamo con history[0]??
//val data2 = data[0].value // elemento valore dell più vecchio
Log.i("Data History full p", data.toString());
//Log.i("Data history prepared",data2.toString());
val start_recent =
if (history_prepared.isEmpty()) {
val last = mDb.glucoseEntryDao().getLast(sensorId, true)
if (last != null) {
min(last.utcTimeStamp, readingTime - 16 * Time.MINUTE)
} else {
readingTime - 16 * Time.MINUTE
}
} else {
min(readingTime - 16 * Time.MINUTE, history_prepared.last().utcTimeStamp)
}
val recent_prepared = prepare(now_recent, sensorId, 1 * Time.MINUTE, start_recent, true, false)
val added = extend(recent_prepared) + extend(history_prepared)
mDb.sensorContactDao().insert(SensorContact(0, sensorId, readingTime, timestamp, added))
Log.i("Data recent prepared",recent_prepared.toString());
//creating volley string request
lastTimeStamp = timestamp
}
val addUrl = "myrurl"
val queue = Volley.newRequestQueue()
val postRequest = object : StringRequest(Request.Method.POST, addUrl,
Response.Listener<String> { response ->
//Toast.makeText(this, response, Toast.LENGTH_SHORT).show()
}, Response.ErrorListener {
//Toast.makeText(this, "Something wrong", Toast.LENGTH_SHORT).show()
}) {
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
params.put("uid", uid.toString())
return params
}
}
queue.add(postRequest)
}
fun get_sz_raw_data() : Int {
return readings.size
}
fun get_raw_data(i: Int) : ByteArray{
synchronized(lock){
if(0<= i && i < readings.size)
return readings[i]
else
return byteArrayOf()
}
}
fun insert(v: List<GlucoseEntry>) {
synchronized(lock){
if(size() != 0) return
for(g in v) {
mDb.glucoseEntryDao().insert(g)
}
}
Log.d(TAG, String.format("inserted %d vales into database", v.size))
}
private fun extend(v: List<GlucoseReading>) : Int {
synchronized(lock) {
val toExtend = v.filter { g: GlucoseReading -> g.status != 0 && g.value > 10 }
.map { g: GlucoseReading -> GlucoseEntry(g, 0) }
for (r: GlucoseEntry in toExtend) {
mDb.glucoseEntryDao().insert(r)
}
Log.d(TAG, "Inserted into db!")
return toExtend.size
}
}
// Inspects last entry from the same sensor and filters out all that are already logged.
private fun prepare(chunks : List<SensorChunk>,
sensorId : Long,
dt : Long,
start : Long,
certain : Boolean,
isHistory: Boolean) : List<GlucoseReading> {
val lastRecent = mDb.glucoseEntryDao().getLast(sensorId, isHistory)
if(lastRecent != null) {
var match = -1
for (i in 0 until chunks.size) {
if (lastRecent.eq(chunks[i])) {
match = i
}
}
if(match > -1) {
val range = IntRange(match + 1, chunks.size - 1)
if(!certain)
return chunks.slice(range)
.mapIndexed { i: Int, chunk: SensorChunk ->
GlucoseReading(chunk,
lastRecent.utcTimeStamp + (i + 1) * dt, sensorId)
}
else {
return chunks.mapIndexed { i: Int, chunk: SensorChunk ->
GlucoseReading(chunk,
start + i * dt, sensorId)
}.slice(range)
}
}
}
return chunks.mapIndexed { i: Int, chunk: SensorChunk ->
GlucoseReading(chunk, start + i * dt, sensorId)
}
}
fun nice(g : GlucoseEntry) : Boolean = g.status == 200 && (g.value < 5000 && g.value > 10)
fun get(after: Long, before : Long) : List<GlucoseEntry> {
return get(after, before, true)
}
fun get(after: Long, before : Long, nice : Boolean) : List<GlucoseEntry> {
synchronized(lock) {
val v = mDb.glucoseEntryDao().getBetween(after, before).orEmpty().sortedBy{ g -> g.utcTimeStamp }
if(!nice) return v
else return v.filter{g -> nice(g)}
}
}
private fun last() : GlucoseEntry? {
return mDb.glucoseEntryDao().getLast(false)
}
fun guess() : Pair<GlucoseReading, GlucoseReading>? {
synchronized(lock){
val last = last()
if(last == null || !nice(last)) return null
val last_as_reading = GlucoseReading(last.value, last.utcTimeStamp, last.sensorId, last.status, false, 0)
val candidates = get(last.utcTimeStamp - Time.MINUTE*5, last.utcTimeStamp)
val real = candidates.filter{g -> g.history == false && g.sensorId == last.sensorId}
if(real.isEmpty()) return null
val entry = real.first()
val guess = last.value * 2 - entry.value
val time = last.utcTimeStamp * 2 - entry.utcTimeStamp
return Pair(last_as_reading, GlucoseReading(guess,
time,
last.sensorId, last.status, false, 0))
}
}
fun lastTimeStamp() : Int {
synchronized(lock){return lastTimeStamp}
}
fun size() : Int {
synchronized(lock) {
val sz = mDb.glucoseEntryDao().getSize()
return sz
}
}
fun insertManual(manual : ManualGlucoseEntry) {
synchronized(lock) {
mDb.manualEntryDao().insert(manual)
}
}
companion object {
private var INSTANCE : DataContainer? = null
fun getInstance(context : Context) : DataContainer {
if (INSTANCE == null) {
synchronized(DataContainer::class) {
if(INSTANCE == null)
INSTANCE = DataContainer(context)
}
}
return this.INSTANCE!!
}
/* fun applicationContext() : Context {
return INSTANCE!!.applicationContext
}*/
fun destroyInstance(){
synchronized(DataContainer::class){
GlucoseDataBase.destroyInstance()
if(INSTANCE != null){
INSTANCE = null
}
}
}
}
}
Any suggestion?
Try to change your DataContainer implementation like below and use context
class DataContainer(val context : Context) {
....
init {
mDb = GlucoseDataBase.getInstance(context)
}
....
val queue = Volley.newRequestQueue(context)
}