Android: install .apk programmatically

AndroidInstallation

Android Problem Overview


I made this with help from https://stackoverflow.com/questions/576513/android-download-binary-file-problems and https://stackoverflow.com/questions/4604239/install-application-programmatically-on-android.

I want to make auto-update and auto-install at once. It is local so it's non-market application.

Here is my code for it:

public void Update(String apkurl){
    try {
	    URL url = new URL(apkurl);
	    HttpURLConnection c = (HttpURLConnection) url.openConnection();
	    c.setRequestMethod("GET");
	    c.setDoOutput(true);
	    c.connect();

	    String PATH = Environment.getExternalStorageDirectory() + "/download/";
	    File file = new File(PATH);
	    file.mkdirs();
	    File outputFile = new File(file, "app.apk");
	    FileOutputStream fos = new FileOutputStream(outputFile);
	    
	    InputStream is = c.getInputStream();

	    byte[] buffer = new byte[1024];
	    int len1 = 0;
	    while ((len1 = is.read(buffer)) != -1) {
	        fos.write(buffer, 0, len1);
	    }
	    fos.close();
	    is.close();//till here, it works fine - .apk is download to my sdcard in download file

        Intent promptInstall = new Intent(Intent.ACTION_VIEW)
            .setData(Uri.parse(PATH+"app.apk"))
            .setType("application/android.com.app");
	    startActivity(promptInstall);//installation is not working
	    
	} catch (IOException e) {
        Toast.makeText(getApplicationContext(), "Update error!", Toast.LENGTH_LONG).show();
    }
}  

My permissions are INTERNET, WRITE_EXTERNAL_STORAGE, INSTALL_PACKAGES, and DELETE_PACKAGES.

When Intent promptInstall is loaded, the app crashes =/

So, am I missing permissions or is my code incorrect, or is there a better way to do this?

Android Solutions


Solution 1 - Android

I solved the problem. I made mistake in setData(Uri) and setType(String).

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

That is correct now, my auto-update is working. Thanks for help. =)

Edit 20.7.2016:

After a long time, I had to use this way of updating again in another project. I encountered a number of problems with old solution. A lot of things have changed in that time, so I had to do this with a different approach. Here is the code:

    //get destination to update file and set Uri
    //TODO: First I wanted to store my update .apk file on internal storage for my app but apparently android does not allow you to open and install
    //aplication with existing package from there. So for me, alternative solution is Download directory in external storage. If there is better
    //solution, please inform us in comment
    String destination = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/";
    String fileName = "AppName.apk";
    destination += fileName;
    final Uri uri = Uri.parse("file://" + destination);

    //Delete update file if exists
    File file = new File(destination);
    if (file.exists())
    //file.delete() - test this, I think sometimes it doesnt work
        file.delete();

    //get url of app on server
    String url = Main.this.getString(R.string.update_app_url);

    //set downloadmanager
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
    request.setDescription(Main.this.getString(R.string.notification_description));
    request.setTitle(Main.this.getString(R.string.app_name));

    //set destination
    request.setDestinationUri(uri);

    // get download service and enqueue file
    final DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
    final long downloadId = manager.enqueue(request);

    //set BroadcastReceiver to install app when .apk is downloaded
    BroadcastReceiver onComplete = new BroadcastReceiver() {
        public void onReceive(Context ctxt, Intent intent) {
            Intent install = new Intent(Intent.ACTION_VIEW);
            install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            install.setDataAndType(uri,
                    manager.getMimeTypeForDownloadedFile(downloadId));
            startActivity(install);

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

Solution 2 - Android

For ICS I´ve implemented your code and made a class that extends AsyncTask. I hope you appreciate it! Thanks for your code and solution.

public class UpdateApp extends AsyncTask<String,Void,Void>{
    private Context context;
	public void setContext(Context contextf){
	    context = contextf;
	}

    @Override
	protected Void doInBackground(String... arg0) {
        try {
            URL url = new URL(arg0[0]);
            HttpURLConnection c = (HttpURLConnection) url.openConnection();
            c.setRequestMethod("GET");
            c.setDoOutput(true);
            c.connect();

            String PATH = "/mnt/sdcard/Download/";
            File file = new File(PATH);
            file.mkdirs();
            File outputFile = new File(file, "update.apk");
            if(outputFile.exists()){
            	outputFile.delete();
            }
            FileOutputStream fos = new FileOutputStream(outputFile);

            InputStream is = c.getInputStream();

            byte[] buffer = new byte[1024];
            int len1 = 0;
            while ((len1 = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len1);
            }
            fos.close();
            is.close();

            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(new File("/mnt/sdcard/Download/update.apk")), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
            context.startActivity(intent);


        } catch (Exception e) {
            Log.e("UpdateAPP", "Update error! " + e.getMessage());
        }
	    return null;
    }
} 	

To use it, in your main activity call by this way:

atualizaApp = new UpdateApp();
atualizaApp.setContext(getApplicationContext());
atualizaApp.execute("http://serverurl/appfile.apk");

Solution 3 - Android

/*	
 * 	Code Prepared by **Muhammad Mubashir**.
 *  Analyst Software Engineer.
	Email Id : [email protected]
	Skype Id : muhammad.mubashir.ansari
	Code: **August, 2011.**
	
	Description: **Get Updates(means New .Apk File) from IIS Server and Download it on Device SD Card,
				 and Uninstall Previous (means OLD .apk) and Install New One.
				 and also get Installed App Version Code & Version Name.**
				 
	All Rights Reserved.
*/
package com.SelfInstall01;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.SelfInstall01.SelfInstall01Activity;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class SelfInstall01Activity extends Activity 
{
	class PInfo {
	    private String appname = "";
	    private String pname = "";
	    private String versionName = "";
	    private int versionCode = 0;
	    //private Drawable icon;
	    /*private void prettyPrint() {
	        //Log.v(appname + "\t" + pname + "\t" + versionName + "\t" + versionCode);
	    }*/
	}
	public int VersionCode;
	public String VersionName="";
	public String ApkName ;
	public String AppName ;
	public String BuildVersionPath="";
	public String urlpath ;
	public String PackageName;
	public String InstallAppPackageName;
	public String Text="";
	
	TextView tvApkStatus;
	Button btnCheckUpdates;
	TextView tvInstallVersion;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        //Text= "Old".toString();
        Text= "New".toString();
        

        ApkName = "SelfInstall01.apk";//"Test1.apk";// //"DownLoadOnSDcard_01.apk"; //		
        AppName = "SelfInstall01";//"Test1"; //
        
		BuildVersionPath = "http://10.0.2.2:82/Version.txt".toString();
		PackageName = "package:com.SelfInstall01".toString(); //"package:com.Test1".toString();
		urlpath = "http://10.0.2.2:82/"+ Text.toString()+"_Apk/" + ApkName.toString();
        
        tvApkStatus =(TextView)findViewById(R.id.tvApkStatus);
        tvApkStatus.setText(Text+" Apk Download.".toString());
        

        tvInstallVersion = (TextView)findViewById(R.id.tvInstallVersion);
        String temp = getInstallPackageVersionInfo(AppName.toString());
        tvInstallVersion.setText("" +temp.toString());
        
        btnCheckUpdates =(Button)findViewById(R.id.btnCheckUpdates);
        btnCheckUpdates.setOnClickListener(new OnClickListener() 
        {		
			@Override
			public void onClick(View arg0) 
			{
				GetVersionFromServer(BuildVersionPath); 

				if(checkInstalledApp(AppName.toString()) == true)
				{	
					Toast.makeText(getApplicationContext(), "Application Found " + AppName.toString(), Toast.LENGTH_SHORT).show();


				}else{
					Toast.makeText(getApplicationContext(), "Application Not Found. "+ AppName.toString(), Toast.LENGTH_SHORT).show();			
				}				
			}
		});
        	
    }// On Create END.
    
    private Boolean checkInstalledApp(String appName){
    	return getPackages(appName);	
    }
    
    // Get Information about Only Specific application which is Install on Device.
    public String getInstallPackageVersionInfo(String appName) 
	{
    	String InstallVersion = "";    	
	    ArrayList<PInfo> apps = getInstalledApps(false); /* false = no system packages */
	    final int max = apps.size();
	    for (int i=0; i<max; i++) 
	    {
	        //apps.get(i).prettyPrint();	    
	        if(apps.get(i).appname.toString().equals(appName.toString()))
	        {
	        	InstallVersion = "Install Version Code: "+ apps.get(i).versionCode+
	        		" Version Name: "+ apps.get(i).versionName.toString();
	        	break;
		    }
	    }
	    
	    return InstallVersion.toString();
	}
    private Boolean getPackages(String appName) 
	{
    	Boolean isInstalled = false;
	    ArrayList<PInfo> apps = getInstalledApps(false); /* false = no system packages */
	    final int max = apps.size();
	    for (int i=0; i<max; i++) 
	    {
	        //apps.get(i).prettyPrint();
	    	
	        if(apps.get(i).appname.toString().equals(appName.toString()))
	        {
	        	/*if(apps.get(i).versionName.toString().contains(VersionName.toString()) == true &&
	        			VersionCode == apps.get(i).versionCode)
	        	{
	        		isInstalled = true;
	        		Toast.makeText(getApplicationContext(),
	        				"Code Match", Toast.LENGTH_SHORT).show(); 
	        		openMyDialog();
	        	}*/
	        	if(VersionCode <= apps.get(i).versionCode)
	        	{
	        		isInstalled = true;
	        		
	        		/*Toast.makeText(getApplicationContext(),
	        				"Install Code is Less.!", Toast.LENGTH_SHORT).show();*/
	        		
	        		DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() 
	        		{
	        		    @Override
	        		    public void onClick(DialogInterface dialog, int which) {
	        		    	switch (which)
	        		    	{
	        		    	case DialogInterface.BUTTON_POSITIVE:
	        		    		//Yes button clicked
	        		    		//SelfInstall01Activity.this.finish(); Close The App.

	        		    		DownloadOnSDcard();
	        		    		InstallApplication();
	        		    		UnInstallApplication(PackageName.toString());

	        		    		break;

	        		    	case DialogInterface.BUTTON_NEGATIVE:
	        		    		//No button clicked

	        		    		break;
	        		    	}
	        		    }
	        		};

	        		AlertDialog.Builder builder = new AlertDialog.Builder(this);
	        		builder.setMessage("New Apk Available..").setPositiveButton("Yes Proceed", dialogClickListener)
	        		    .setNegativeButton("No.", dialogClickListener).show();
	        		
	        	}    
	        	if(VersionCode > apps.get(i).versionCode)
	        	{
	        		isInstalled = true;
	        		/*Toast.makeText(getApplicationContext(),
	        				"Install Code is better.!", Toast.LENGTH_SHORT).show();*/
	        		
	        		DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() 
	        		{
	        		    @Override
	        		    public void onClick(DialogInterface dialog, int which) {
	        		    	switch (which)
	        		    	{
	        		    	case DialogInterface.BUTTON_POSITIVE:
	        		    		//Yes button clicked
	        		    		//SelfInstall01Activity.this.finish(); Close The App.

	        		    		DownloadOnSDcard();
	        		    		InstallApplication();
	        		    		UnInstallApplication(PackageName.toString());
	        		    		
	        		    		break;

	        		    	case DialogInterface.BUTTON_NEGATIVE:
	        		    		//No button clicked
	        		    		
	        		    		break;
	        		    	}
	        		    }
	        		};

	        		AlertDialog.Builder builder = new AlertDialog.Builder(this);
	        		builder.setMessage("NO need to Install.").setPositiveButton("Install Forcely", dialogClickListener)
	        		    .setNegativeButton("Cancel.", dialogClickListener).show();      		
	        	}
		    }
	    }
	    
	    return isInstalled;
	}
    private ArrayList<PInfo> getInstalledApps(boolean getSysPackages) 
    {    	
	    ArrayList<PInfo> res = new ArrayList<PInfo>();        
	    List<PackageInfo> packs = getPackageManager().getInstalledPackages(0);
	
	    for(int i=0;i<packs.size();i++) 
	    {
	        PackageInfo p = packs.get(i);
	        if ((!getSysPackages) && (p.versionName == null)) {
	            continue ;
	        }
	        PInfo newInfo = new PInfo();
	        newInfo.appname = p.applicationInfo.loadLabel(getPackageManager()).toString();
	        newInfo.pname = p.packageName;
	        newInfo.versionName = p.versionName;
	        newInfo.versionCode = p.versionCode;
	        //newInfo.icon = p.applicationInfo.loadIcon(getPackageManager());
	        res.add(newInfo);
	    }
	    return res; 
	}
    
    
    public void UnInstallApplication(String packageName)// Specific package Name Uninstall.
    {
		//Uri packageURI = Uri.parse("package:com.CheckInstallApp");
		Uri packageURI = Uri.parse(packageName.toString());
		Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
		startActivity(uninstallIntent);	
    }
    public void InstallApplication()
    {	
    	Uri packageURI = Uri.parse(PackageName.toString());
		Intent intent = new Intent(android.content.Intent.ACTION_VIEW, packageURI);
		
//		Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
		
		//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		//intent.setFlags(Intent.ACTION_PACKAGE_REPLACED);
		
		//intent.setAction(Settings. ACTION_APPLICATION_SETTINGS);

		intent.setDataAndType
		(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/"  + ApkName.toString())), 
		"application/vnd.android.package-archive");

		// Not open this Below Line Because...
		////intent.setClass(this, Project02Activity.class); // This Line Call Activity Recursively its dangerous.

		startActivity(intent);  
    }
    public void GetVersionFromServer(String BuildVersionPath)
	{
		//this is the file you want to download from the remote server        	
    	//path ="http://10.0.2.2:82/Version.txt";
    	//this is the name of the local file you will create
    	// version.txt contain Version Code = 2; \n Version name = 2.1;   	    	
    	URL u;
    	try {
    		u = new URL(BuildVersionPath.toString());

    		HttpURLConnection c = (HttpURLConnection) u.openConnection();        	
    		c.setRequestMethod("GET");
    		c.setDoOutput(true);
    		c.connect();

    		//Toast.makeText(getApplicationContext(), "HttpURLConnection Complete.!", Toast.LENGTH_SHORT).show();  

    		InputStream in = c.getInputStream();

    		ByteArrayOutputStream baos = new ByteArrayOutputStream();

    		byte[] buffer = new byte[1024]; //that stops the reading after 1024 chars..
    		//in.read(buffer); //  Read from Buffer.
    		//baos.write(buffer); // Write Into Buffer.

    		int len1 = 0;
    		while ( (len1 = in.read(buffer)) != -1 ) 
    		{        		
    			baos.write(buffer,0, len1); // Write Into ByteArrayOutputStream Buffer.
    		}

    		String temp = "";     
    		String s = baos.toString();// baos.toString(); contain Version Code = 2; \n Version name = 2.1;
    		
    		for (int i = 0; i < s.length(); i++)
    		{				
    			i = s.indexOf("=") + 1; 
    			while (s.charAt(i) == ' ') // Skip Spaces
				{
					i++; // Move to Next.
				}
    			while (s.charAt(i) != ';'&& (s.charAt(i) >= '0' && s.charAt(i) <= '9' || s.charAt(i) == '.'))
    			{
    				temp = temp.toString().concat(Character.toString(s.charAt(i))) ;
    				i++;
    			}
    			//
    			s = s.substring(i); // Move to Next to Process.!
    			temp = temp + " "; // Separate w.r.t Space Version Code and Version Name.
    		}
    		String[] fields = temp.split(" ");// Make Array for Version Code and Version Name.
    		
    		VersionCode = Integer.parseInt(fields[0].toString());// .ToString() Return String Value.
     		VersionName = fields[1].toString();
     		
    		baos.close();
    	}
    	catch (MalformedURLException e) {
    		Toast.makeText(getApplicationContext(), "Error." + e.getMessage(), Toast.LENGTH_SHORT).show();
    		e.printStackTrace();
    	} catch (IOException e) {			
			e.printStackTrace();
			Toast.makeText(getApplicationContext(), "Error." + e.getMessage(), Toast.LENGTH_SHORT).show();
		}
    		//return true;
	}// Method End.
    
    // Download On My Mobile SDCard or Emulator.
	public void DownloadOnSDcard()
    {
    	try{
    		URL url = new URL(urlpath.toString()); // Your given URL.

    		HttpURLConnection c = (HttpURLConnection) url.openConnection();
    		c.setRequestMethod("GET");
    		c.setDoOutput(true);
    		c.connect(); // Connection Complete here.!

    		//Toast.makeText(getApplicationContext(), "HttpURLConnection complete.", Toast.LENGTH_SHORT).show();

    		String PATH = Environment.getExternalStorageDirectory() + "/download/";
    		File file = new File(PATH); // PATH = /mnt/sdcard/download/
    		if (!file.exists()) {
    			file.mkdirs();
    		}
    		File outputFile = new File(file, ApkName.toString());	        
    		FileOutputStream fos = new FileOutputStream(outputFile);

    		//		Toast.makeText(getApplicationContext(), "SD Card Path: " + outputFile.toString(), Toast.LENGTH_SHORT).show();

    		InputStream is = c.getInputStream(); // Get from Server and Catch In Input Stream Object.

    		byte[] buffer = new byte[1024];
    		int len1 = 0;
    		while ((len1 = is.read(buffer)) != -1) {
    			fos.write(buffer, 0, len1); // Write In FileOutputStream.
    		}
    		fos.close();
    		is.close();//till here, it works fine - .apk is download to my sdcard in download file.
    		// So please Check in DDMS tab and Select your Emulator.

    		//Toast.makeText(getApplicationContext(), "Download Complete on SD Card.!", Toast.LENGTH_SHORT).show();
    		//download the APK to sdcard then fire the Intent.
    	} 
    	catch (IOException e) 
    	{
    		Toast.makeText(getApplicationContext(), "Error! " +
    				e.toString(), Toast.LENGTH_LONG).show();
    	}			
    }
}

Solution 4 - Android

Thank you for sharing this. I have it implemented and working. However:

  1. I install ver 1 of my app (working no problem)
  2. I place ver 2 on the server. the app retrieves ver2 and saves to SD card and prompts user to install the new package ver2
  3. ver2 installs and works as expected
  4. Problem is, every time the app starts it wants the user to re-install version 2 again.

So I was thinking the solution was simply delete the APK on the sdcard, but them the Async task wil simply retrieve ver2 again for the server.

So the only way to stop in from trying to install the v2 apk again is to remove from sdcard and from remote server.

As you can imagine that is not really going to work since I will never know when all users have received the lastest version.

Any help solving this is greatly appreciated.

I IMPLEMENTED THE "ldmuniz" method listed above.

NEW EDIT: Was just thinking all me APK's are named the same. Should I be naming the myapk_v1.0xx.apk and and in that version proactivily set the remote path to look for v.2.0 whenever it is released?

I tested the theory and it does SOLVE the issue. You need to name your APK file file some sort of versioning, remembering to always set your NEXT release version # in your currently released app. Not ideal but functional.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionUroš PodkrižnikView Question on Stackoverflow
Solution 1 - AndroidUroš PodkrižnikView Answer on Stackoverflow
Solution 2 - AndroidldmunizView Answer on Stackoverflow
Solution 3 - AndroidMuhammad MubashirView Answer on Stackoverflow
Solution 4 - AndroidmxrideView Answer on Stackoverflow