javaandroidbroadcastreceiverandroid-permissionsandroid-download-manager

Android - Download Unsuccessful and Parsing Error while trying to download and install .apk from server


The goal is to update my app without using the google play store. I’m trying to download a .apk file from a server and then install it programmatically. I’m currently getting an error that the download is unsuccessful and that there was an error while parsing the package. I have the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions requested at runtime. I had to go into the tablet's settings to give permission to “Install unknown apps”. I cannot get REQUEST_INSTALL_PACKAGES to be requested at runtime.

If I change the fileName and update URL to get a .txt that I store in the same folder on the server as the .apk AND comment out “.setMimeType()” I can download and view a .txt file.

Here are some code snippets to help

gradle:app

android {
    compileSdk 31
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "farrpro.project"
        minSdk 28
        targetSdk 30
    }

AndroidManifest.xml

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
    <uses-permission android:name="android.permission.ACTION_INSTALL_PACKAGE"/>

MainActivity.java


private void hasInstallPermission() { // runs in onCreate() 
        if (getApplicationContext().checkSelfPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, 1);
        }
    }


void downloadAndInstallUpdate(){ // runs when users accepts request to download update
        String fileName = "update.apk";
    // example of update’s string
        update = “https://myserver.net/update.apk”;

        //set download manager
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(update));
        request.setTitle(fileName)
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
                .setDescription("Downloading")
                //.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE)
                .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
                .setMimeType("application/vnd.android.package-archive");


        // get download service and enqueue file
        final DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
        final long downloadId = manager.enqueue(request);
        System.out.println("Max Bytes: "+DownloadManager.getMaxBytesOverMobile(this)); //returns null


        //set BroadcastReceiver to install app when .apk is downloaded
        BroadcastReceiver onComplete = new BroadcastReceiver() {
            public void onReceive(Context ctxt, Intent intent) {
                System.out.println("Is download successful: "+ manager.getUriForDownloadedFile(downloadId)); //returns null
                System.out.println("Mime: "+manager.getMimeTypeForDownloadedFile(downloadId)); //returns application/vnd.android.package-archive

                Intent install = new Intent(Intent.ACTION_VIEW);
                File file = new File(ctxt.getExternalFilesDir(null) + fileName);
                Uri downloadedApk = FileProvider.getUriForFile(ctxt, "farrpro.project.provider", file);

                install.setDataAndType(downloadedApk,
                        manager.getMimeTypeForDownloadedFile(downloadId));
                install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                try {
                    startActivity(install); 
                } catch (ActivityNotFoundException e) {
                    e.printStackTrace();
                    Log.e("TAG", "Error in opening the file!");// this never prints 
                }

                unregisterReceiver(this);
                finish();
            }
        };

        //register receiver for when .apk download is complete
        registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }


Solution

  • The problem was the server had restricted download types. My code worked more or less after that setting was changed. I ended up needing to change the intents in my broadcast receiver so that after the download was completed the new .apk would immediately install. Here are the changes I made to make the code posted work.

     final long downloadId = manager.enqueue(request);
    
    BroadcastReceiver onComplete = new BroadcastReceiver() {
                public void onReceive(Context ctxt, Intent intent1) {
    
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setDataAndType(manager.getUriForDownloadedFile(downloadId), "application/vnd.android.package-archive");
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    try {
                        getApplicationContext().startActivity(intent);
                    } catch (ActivityNotFoundException e) {
                        e.printStackTrace();
                        Log.e("TAG", "Error in opening the file!");
                    }
    
                    unregisterReceiver(this);
                    finish();
                }
            };