I am making my app to be updated via my own file server instead of Google Play-store. However, it does not work well. After confirming to "update", APK file is downloaded, it is not opened correctly. Please check the demonstration https://youtu.be/qDSGZ9fQ1Oo
class MainActivity : Activity() {
private fun checkUpdate(){
val jsonObjectRequest = JsonObjectRequest(
Request.Method.GET,
"https://myserver/release.json",
null,
Response.Listener { response ->
if(response.getInt("version") > versionCode){
val builder = AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog_Alert)
builder.setTitle("Update to v" + response.getString("version") + " ?")
builder.setMessage(response.getString("note"))
builder.setPositiveButton("Yes") { _, _ ->
downloadUpdate(response.getString("version"))
}
builder.setNegativeButton("No") { _, _ ->
showUserInteraction()
}
builder.setCancelable(false)
builder.show()
}else{
showUserInteraction()
}
},
Response.ErrorListener{ _ ->
showUserInteraction()
}
)
requestQueue.add(jsonObjectRequest)
}
private fun downloadUpdate(versionCode: String) {
registerReceiver(onDownloadComplete(), IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
val request = DownloadManager
.Request(Uri.parse("https://myserver/app-release.apk"))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "myapp_v" + versionCode + ".apk")
downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadId = downloadManager.enqueue(request)
}
private class onDownloadComplete: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val c = downloadManager.query(DownloadManager.Query().setFilterById(downloadId))
if(c != null){
c.moveToFirst()
val fileUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val mFile = File(Uri.parse(fileUri).path!!)
val fileName = mFile.absolutePath
context.unregisterReceiver(this)
val intent = Intent(Intent.ACTION_VIEW)
var contentUri: Uri
if (SDK_VER >= Build.VERSION_CODES.N) {
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", File(fileName))
}else{
contentUri = Uri.fromFile(File(fileName))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
startActivity(context, intent, null)
}
}
}
}
May anyone please point out my mistake? Thanks.
You need to add below permission in your manifest.xml file.
If an app uses a targetSdkLevel of 26 or above and prompts the user to install other apps, the manifest file needs to include the REQUEST_INSTALL_PACKAGES permission:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
You can see below link why it's needed
Link1
Link2
Related
I have an android app that not on Google play and I want it to get updates using APK on the server.
This code is working and checks for updates by searching for APK on Firebase storage with greater version than the current build and ending it to download
private fun checkUpdates(){
val storage = FirebaseStorage.getInstance()
val storageRef = storage.reference
val rootRef = storageRef.root
rootRef.listAll().addOnSuccessListener { listResult ->
for (item in listResult.items) {
// item is a StorageReference for a file
val fileName = item.name
val version = fileName.substring(fileName.indexOf("app_v") + 5, fileName.indexOf(".apk"))
if (version.replace(".", "") > BuildConfig.VERSION_CODE.toString().replace(".", "")) {
val builder = AlertDialog.Builder(this)
builder.setMessage(R.string.new_up_av)
builder.setPositiveButton(R.string.install) { _, _ ->
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_STORAGE
)
download(version)
} else {
download(version)
}
}
val dialog = builder.create()
dialog.show()
}
else{
Toast.makeText(this, R.string.no_updates, Toast.LENGTH_LONG).show()
}
}
} .addOnFailureListener{
Toast.makeText(this, "ERROR", Toast.LENGTH_SHORT).show()
}
}
The download function is this (notice the comment on line 4):
private fun download(version: String) {
Toast.makeText(this, R.string.download_updates, Toast.LENGTH_SHORT).show()
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(//url for apk here.)
val request = DownloadManager.Request(downloadUri)
.setTitle(getString(R.string.download_updates))
.setDescription(getString(R.string.app_name))
val downloadId = downloadManager.enqueue(request)
// Check if the download was successful
if (downloadId == -1L) {
// Download failed, show an error message
Toast.makeText(this, "download failed", Toast.LENGTH_SHORT).show()
return
}
// Listen for the download to complete and install the update
val downloadReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
if (downloadId == id) {
installApk(context, "app_v$version.apk")
}
}
}
registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
on line 4 i verified it works manually and the problems is showing also with sure-working APKs like WhatsApp.
the installApk function:
fun installApk(context: Context, fileName: String) {
val file = File(Environment.getExternalStorageDirectory().toString() + "/Download/" + filename)
val fileUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
provider in Manifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android: resource="#xml/provider_paths" />
</provider>
provider_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="downloads" path="Download/" />
</paths>
The problem: seems like its downloading, starting to install, and than shows an error occurred while parsing the package message. no logs.
(It works manually)
I have tried many other ways buts I couldn't figure what cussing the problem.
If someone fammiliar with intalling apk programmatically I looking for help.
I'm trying to build an auto updater via github. The app so far detects new available versions and downloads the apk file. I however cannot get it to install the apk file.
There are no crashes or Log messages indicating an error. The install dialog just does not show up.
This is my code:
fun downloadAndInstall(link: Uri, fileName: String){
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = link
val request = DownloadManager.Request(downloadUri)
request.setMimeType(MIME_TYPE)
val destination = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/" + fileName
val destinationUri = Uri.parse("file://$destination")
request.setDestinationUri(destinationUri)
fun showInstallOption(destination: String) {
val onComplete = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
val contentUri = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + ".provider",
File(destination)
)
val installer = context.packageManager.packageInstaller
val resolver = context.contentResolver
resolver.openInputStream(contentUri)?.use { apkStream ->
val length =
DocumentFile.fromSingleUri(context, contentUri)?.length() ?: -1
val params =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = installer.createSession(params)
val session = installer.openSession(sessionId)
session.openWrite("INSTALL", 0, length).use { sessionStream ->
apkStream.copyTo(sessionStream)
session.fsync(sessionStream)
}
val intent = Intent(context, InstallReceiver::class.java)
intent.action = "com.blazecode.tsviewer.util.updater.SESSION_API_PACKAGE_INSTALLED"
val pi = PendingIntent.getBroadcast(
context,
3,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
session.commit(pi.intentSender)
session.close()
}
Toast.makeText(context, "done", Toast.LENGTH_LONG).show()
}
}
context.registerReceiver(onComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
showInstallOption(destination)
downloadManager.enqueue(request)
Toast.makeText(context, context.getString(R.string.downloading), Toast.LENGTH_LONG).show()
}
I tested this on Android 13 and 8, both with the same result. Android 13 is a physical device. Android 8 is an emulator.
This Code Works Fine With Media Files I want a solution For Document Files
I Don't Know how to put contentValues For Document Files
fun getFile(fileName: String): File? {
with(sharePrefHelper.app){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues()
// Here is My Question That what should i Do Here Because this is for document not for image
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
// for MIME_TYPE "image/jpg" this is working
values.put(MediaStore.Images.Media.MIME_TYPE, "text/csv")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Donny")
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.let {
it.path?.let { finalPath ->
return File(finalPath)
}
}
} else {
val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Donny")
if (!directory.exists()){
directory.mkdirs()
}
return File(directory, fileName)
}
return null
}
}
This Code Works Fine with media Files
My Question Here is How to save documents like CSV File in outer folder of android device
EDIT :
Well well well, I'm still trying to add anytype of file in the "download" directory.
Personnaly, I'm trying to copy a file from my assetFolder and paste it to the "Download" folder. I haven't succeeded yet.
However, I can currently CREATE anytype of file in that folder, it's working with the method below. I hope this can help you.
Here is my code :
public void saveFileToPhone(InputStream inputStream, String filename) {
OutputStream outputStream;
Context myContext = requireContext();
try {
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.Q){
ContentResolver contentResolver = requireContext().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Downloads.DISPLAY_NAME,filename);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
Uri collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri fileUri = contentResolver.insert(collection, contentValues);
outputStream = contentResolver.openOutputStream(Objects.requireNonNull(fileUri));
Objects.requireNonNull(outputStream);
}
}catch (FileNotFoundException e) {
e.printStackTrace();
}
}
Here it is what i have done with clean way
This is file provider activity
class FileProviderActivity : AppCompatActivity() {
var commonIntentLauncher: ActivityResultLauncher<Intent?> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.let {
intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
"FileUri" to result?.data?.data.toString()
))
finish()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
val activityIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = intent.getStringExtra("fileType")
putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("fileName"))
putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
}
commonIntentLauncher.launch(activityIntent)
}else{
val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/Dunny/CSVFiles")
if (!directory.exists()){
directory.mkdirs()
}
intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
"FileUri" to File(directory, intent.getStringExtra("fileName")!!).toURI().toString()
))
finish()
}
}
}
This FileProviderHelper
class MyFileProvider {
companion object {
fun with(context: Context) = FileRequest(context)
}
class FileRequest(private val context: Context) {
fun request(fileName: String, fileType: String = "application/*", file: (Uri?) -> Unit ) {
val intent = Intent(context, FileProviderActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra("fileName",fileName)
.putExtra("fileType", fileType)
.putExtras(bundleOf("FileReceiver" to FileReceiver(file)))
context.startActivity(intent)
}
}
internal class FileReceiver(private val file: (Uri?) -> Unit) : ResultReceiver(Handler(Looper.getMainLooper())) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
super.onReceiveResult(resultCode, resultData)
resultData?.let {
file(it.getString("FileUri")?.toUri())
}
}
}
}
Here Is Use Of this Function
MyFileProvider.with(this).request("TestFile.csv","application/*") { fileUri ->
toast(fileUri.toString())
}
I'm trying to find a solution to simply download a base64 encoded pdf file of a webservice and open it with an preinstalled pdf viewer. My application targets Android R. I tried something like this but I dont't want a picker to open.
This is my code so far. It is just downloading the file and converts it to a bytearray. The next step should by saving the file and opening it.
lifecycleScope.launch {
withContext(Dispatchers.IO) {
try {
Snackbar.make(binding.root, getString(R.string.load_document_started), Snackbar.LENGTH_LONG).show()
val documentData = DocumentDao().get(document.id, montageOrder)
val docAsByte = Base64.decode(documentData.data, Base64.DEFAULT)
val currentDateString = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = document.documentType.
lowercase()
.replace("ä", "ae")
.replace("ü", "ue")
.replace("ö", "oe") +
"_" + currentDateString
val file = File(requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName)
val fileStream = FileOutputStream(file)
fileStream.write(docAsByte)
fileStream.close()
val target = Intent(Intent.ACTION_VIEW)
target.setDataAndType(Uri.fromFile(file), "application/pdf")
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
val intent = Intent.createChooser(target, "Yolo");
startActivity(intent)
} catch (e: Exception) {
Log.e(TAG, "Dokument konnte nicht geladen werden: " + e.message, e)
Snackbar.make(binding.root, getString(R.string.exception_could_not_load_document), Snackbar.LENGTH_LONG).show()
}
}
}
This results in a FileUriExposedException
Another aproach was using the SAF
lateinit var docAsByte : ByteArray
private val createFileLauncher = registerForActivityResult(CreatePdfDocument()) { uri ->
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val stream = requireContext().contentResolver.openOutputStream(uri)
stream?.write(docAsByte)
stream?.close()
val target = Intent(Intent.ACTION_VIEW)
target.setDataAndType(uri, "application/pdf")
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
startActivity(target)
}
}
}
private fun setGui() {
_binding?.lvDocuments?.adapter = DocumentAdapter(requireContext(), montageOrder.documents)
_binding?.lvDocuments?.setOnItemClickListener { parent, _, position, _ ->
val document : Document = parent.getItemAtPosition(position) as Document
lifecycleScope.launch {
withContext(Dispatchers.IO) {
try {
val currentDateString = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = document.documentType.
lowercase()
.replace("ä", "ae")
.replace("ü", "ue")
.replace("ö", "oe") +
"_" +
montageOrder.orderNumber +
"_" +
currentDateString +
".pdf"
Snackbar.make(binding.root, getString(R.string.load_document_started), Snackbar.LENGTH_LONG).show()
val documentData = DocumentDao().get(document.id, montageOrder)
docAsByte = Base64.decode(documentData.data, Base64.DEFAULT)
createFileLauncher.launch(fileName)
} catch (e: Exception) {
Log.e(TAG, "Dokument konnte nicht geladen werden: " + e.message, e)
Snackbar.make(binding.root, getString(R.string.exception_could_not_load_document), Snackbar.LENGTH_LONG).show()
}
}
}
}
}
Everything works fine except for opening. But if I open the pdf via file explorer it works.
Found a thread online and solved it this way: https://www.py4u.net/discuss/614761
Add provider_paths.xml to xml resource folder
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
In your manifest add a FileProvider:
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
</application>
Prepare to download files to any directory your app owns, such as getFilesDir(), getExternalFilesDir(), getCacheDir() or getExternalCacheDir().
val privateDir = context.getFilesDir()
Download file taking its progress into account (DIY):
val downloadedFile = myFancyMethodToDownloadToAnyDir(url, privateDir, fileName)
Copy it to Downloads folder:
private val DOWNLOAD_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val finalUri : Uri? = copyFileToDownloads(context, downloadedFile)
fun copyFileToDownloads(context: Context, downloadedFile: File): Uri? {
val resolver = context.contentResolver
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, getName(downloadedFile))
put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(downloadedFile))
put(MediaStore.MediaColumns.SIZE, getFileSize(downloadedFile))
}
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
} else {
val authority = "${context.packageName}.provider"
val destinyFile = File(DOWNLOAD_DIR, getName(downloadedFile))
FileProvider.getUriForFile(context, authority, destinyFile)
}?.also { downloadedUri ->
resolver.openOutputStream(downloadedUri).use { outputStream ->
val brr = ByteArray(1024)
var len: Int
val bufferedInputStream = BufferedInputStream(FileInputStream(downloadedFile.absoluteFile))
while ((bufferedInputStream.read(brr, 0, brr.size).also { len = it }) != -1) {
outputStream?.write(brr, 0, len)
}
outputStream?.flush()
bufferedInputStream.close()
}
}
Once in download folder you can open file from app like this:
val authority = "${context.packageName}.provider"
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(finalUri, getMimeTypeForUri(finalUri))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
try {
context.startActivity(Intent.createChooser(intent, chooseAppToOpenWith))
} catch (e: Exception) {
Toast.makeText(context, "Error opening file", Toast.LENGTH_LONG).show()
}
//Kitkat or above
fun getMimeTypeForUri(context: Context, finalUri: Uri) : String =
DocumentFile.fromSingleUri(context, finalUri)?.type ?: "application/octet-stream"
//Just in case this is for Android 4.3 or below
fun getMimeTypeForFile(finalFile: File) : String =
DocumentFile.fromFile(it)?.type ?: "application/octet-stream"
Im doing an app and i want to share photo just i saved to phone to another apps like instagram twitter.Im not able to do it and i cant see where the mistake is.Here is my code
`private fun getScreenshot(currentPage: Int){
QuickShot.of(requireActivity().findViewById<ConstraintLayout(currentPage))
.setResultListener(this)
.enableLogging()
.setFilename("screen")
.setPath("Spotibud")
.toJPG()
.save()
}
override fun onQuickShotSuccess(path: String?) {
Log.d(TAG, "onQuickShotSuccess: $path")
shareOnInstagram(path!!)
}
override fun onQuickShotFailed(path: String?, errorMsg: String?) {
Log.d(TAG, "onQuickShotFailed: $errorMsg")
}
private fun shareOnInstagram(path: String){
val stickerAssetUri: Uri = Uri.parse(path)
val intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM,stickerAssetUri)
type = "image/*"
}
startActivity(intent)
}`
and my log when app saves image
2021-02-18 17:28:08.750 16355-16355/com.example.contestifyfirsttry D/Home Fragment: onQuickShotSuccess: /Pictures/Spotibud/screen.jpg
also is there any code how i can see error.try catch not worked
now i found the solution this isnt best practice but im sure it makes you to see what you should do
private fun shareOnInstagram(path: String){
var file : File = File("/storage/emulated/0/Pictures/Spotibud/screen.jpg")
if (file.exists()){
Log.d(TAG, "shareOnInstagram: file exists")
val stickerAssetUri: Uri = Uri.fromFile(file)
val sourceApplication = "com.example.contestifyfirsttry"
val intent = Intent("com.instagram.share.ADD_TO_STORY")
intent.putExtra("source_application", sourceApplication)
intent.type = "image/*"
intent.putExtra("interactive_asset_uri", stickerAssetUri)
intent.putExtra("top_background_color", "#33FF33")
intent.putExtra("bottom_background_color", "#FF00FF")
val activity: Activity? = activity
activity!!.grantUriPermission(
"com.instagram.android", stickerAssetUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
)
if (activity!!.packageManager.resolveActivity(intent, 0) != null) {
activity!!.startActivityForResult(intent, 0)
}
}else{
Log.d(TAG, "shareOnInstagram: file dont exists")
}
}