3

I need to be able to push out updates to a private app (doesn't exist on Play Store) by checking for a newer version (via versionCode/versionName) of an apk hosted somewhere*.

I believe I've got all the code I need- connect to Dropbox, download the file, trigger broadcast receiver to check the version and install the app if newer than current is available.

My issue is that getPackageArchiveInfo always returns null on the downloaded apk.

Note: I am able to manually install the app after it has been downloaded (the file is not corrupt), but I need this process to be automated.

*using Dropbox in this example, but this will not be what prod uses.

I've used a few different resources to build and try to debug my issues, see:
Android: install .apk programmatically
Android install apk with Intent.VIEW_ACTION not working with File provider

The compileSdkVersion is 27, minSdkVersion and targetSdkVersion are 23. This configuration is crucial and cannot be changed.

I am primarily testing on an emulator set to the specific configuration that the production device will be using, but I also am using a Galaxy S6 to test.

MainActivity:

public void onCheckClick(View view) {
        try {
            String versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
            txtCurrent.setText("Current: " + versionName);

            String url = apkLocation;
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
            request.setDescription("description");
            request.setTitle("app-debug.apk");

            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app-debug.apk");

            DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
            manager.enqueue(request);

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    public void onUpdateClick(View view) {

        Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);

        installIntent.setDataAndType(Uri.parse("file:///storage/emulated/0/Download/app-debug.apk"), "application/vnd.android.package-archive");
        startActivity(installIntent);
    }

DownloadBroadcastReceiver:

public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)){
            //do stuff
            PackageManager pm = context.getPackageManager();
            String apkName = "app-debug.apk";
            String fullPath = Environment.DIRECTORY_DOWNLOADS + "/" + apkName;
            PackageInfo info = pm.getPackageArchiveInfo(fullPath, 0);

            Toast.makeText(context, info.packageName, Toast.LENGTH_LONG).show();
        }
    }

AndroidManifest:

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".DownloadBroadcastReceiver">
            <intent-filter>
                <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
            </intent-filter>
        </receiver>

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>
    </application>

Expected: Grab the version information from the downloaded apk, then be able to install that apk if it has a newer version.
Actual: Version information is null, and therefore cannot continue the process.

Daniel F
  • 41
  • 6
  • Is there a particular reason you're creating the open/install Intent with `Intent.ACTION_VIEW` instead of `Intent.ACTION_INSTALL_PACKAGE`? Also, are you sure your download / getPackageInfo / install file locations actually line up? Pretty sure `setDestinationInExternalPublicDir` might be doing something here – Cruceo Apr 17 '19 at 18:40
  • @Cruceo both `Intent.ACTION_VIEW` and `Intent.ACTION_INSTALL_PACKAGE` have the same result, although you're probably right about using the latter. I tried hardcoding all the filepaths to be the same, but again, same result. For reference, `setDestinationInExternalPublicDir` returns a path of `file:///storage/emulated/0/Download//` – Daniel F Apr 17 '19 at 18:57
  • And `File(Environment.DIRECTORY_DOWNLOADS + "/app-debug.apk")`'s path is identical? – Cruceo Apr 17 '19 at 19:07
  • Aside from the number of `/` after `file:`, the paths are identical – Daniel F Apr 17 '19 at 19:12

1 Answers1

0

I had the same Problem. I just solved it. Here is my solution: (NB: I used FTP server here, Targated SDK 23)

AndroidManifest:

<provider
            android:name="android.support.v4.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>

Download Apk Class:

class DownloadNewVersion extends AsyncTask<String,Integer,Boolean> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressdlg = new ProgressDialog(DownloadApk.this);
            progressdlg.setCancelable(false);
            progressdlg.setMessage("Downloading...");
            progressdlg.setProgressStyle(progressdlg.STYLE_SPINNER);
            //progressdlg.setMax(100);
            progressdlg.setIndeterminate(true);
            progressdlg.setCanceledOnTouchOutside(false);
            progressdlg.show();


        }

        @Override
        protected void onPostExecute(Boolean result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressdlg.dismiss();
            if(result){
                Toast.makeText(DownloadApk.this,"Done!!", Toast.LENGTH_SHORT).show();
                
                OpenNewVersion(path);


            }else{
                Toast.makeText(DownloadApk.this,"Error: Server Connection Failed!", Toast.LENGTH_SHORT).show();
            }
        }
        @Override
        protected Boolean doInBackground(String... arg0) {
            Boolean flag = false;
            String sdcardRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
            String apkSavePath = sdcardRoot+"/app-debug.apk";
            path=apkSavePath;
            try {
                

                String server = "103.121.78.28"; //Your download Link
                int port = 21;
                String user = "ftp";
                String pass = "";


                FTPClient ftpClient = new FTPClient();

                try {

                    ftpClient.connect(server, port);
                    ftpClient.login(user, pass);
                    ftpClient.enterLocalPassiveMode();
                    ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
                  


                    String remoteFile1 = "app-debug.apk";//apk name
                    File downloadFile1 = new File(apkSavePath);

                    OutputStream outputStream1 = new BufferedOutputStream(new FileOutputStream(downloadFile1));

                    boolean success = ftpClient.retrieveFile(remoteFile1, outputStream1);
                    outputStream1.close();
                    Log.d("Update", "doInBackground: "+"before if"+success);



                    if (success) {
                        Log.d("Update", "doInBackground: "+"Success"+success);
                       


                        

                        flag = true;
                        

                     

                    }






                } catch (IOException ex) {
                    progressdlg.dismiss();
                    System.out.println("Error: " + ex.getMessage());
                    flag = false;
                    ex.printStackTrace();
                } finally {
                    try {
                        if (ftpClient.isConnected()) {
                            ftpClient.logout();
                            ftpClient.disconnect();
                            progressdlg.dismiss();
                        }
                    } catch (IOException ex) {
                        progressdlg.dismiss();
                        ex.printStackTrace();
                        flag = false;
                    }
                }
            } catch (Exception e) {
                progressdlg.dismiss();
                Log.e("TAG", "Update Error: " + e.getMessage());
                e.printStackTrace();
                flag = false;
            }

            return flag;
        }
    }
    void OpenNewVersion(String location) {
        File apkFile = new File(location);
        Intent intent = new Intent(Intent.ACTION_VIEW);
            


        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri uri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + ".provider", apkFile);
            intent.setDataAndType(uri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
        }


        startActivity(intent);;
    }


    // Function to check and request permission.
    public void checkPermission(String permission, int requestCode)
    {
        if (ContextCompat.checkSelfPermission(DownloadApk.this, permission)
                == PackageManager.PERMISSION_DENIED) {

            // Requesting the permission
            ActivityCompat.requestPermissions(DownloadApk.this,
                    new String[] { permission },
                    requestCode);
        }
        else {
            Toast.makeText(DownloadApk.this,
                    "Permission already granted",
                    Toast.LENGTH_SHORT)
                    .show();
        }
    }

    // This function is called when the user accepts or decline the permission.
    // Request Code is used to check which permission called this function.
    // This request code is provided when the user is prompt for permission.

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super
                .onRequestPermissionsResult(requestCode,
                        permissions,
                        grantResults);


            if (requestCode == STORAGE_PERMISSION_CODE) {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(DownloadApk.this,
                        "Storage Permission Granted",
                        Toast.LENGTH_SHORT)
                        .show();
            }
            else {
                Toast.makeText(DownloadApk.this,
                        "Storage Permission Denied",
                        Toast.LENGTH_SHORT)
                        .show();
            }
        }
    }

Call Download Apk class from any click listener:

new DownloadNewVersion ().execute();