Android Open Source - neoupdate-android-sdk neo Update






From Project

Back to project page neoupdate-android-sdk.

License

The source code is released under:

GNU General Public License

If you think the Android project neoupdate-android-sdk listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package in.neoandroid.neoupdate;
//from   ww w.  j a  va 2  s . c  o  m
/*
neoUpdate Android SDK: neoUpdate.java

Copyright (C) 2013-2014 Neophyte Technologies LLP & Respective Contributors
See contributors.txt for complete list of contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.DigestInputStream;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
import java.util.zip.GZIPInputStream;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONObject;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Environment;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import android.webkit.MimeTypeMap;
import static android.provider.BaseColumns._ID;

public class neoUpdate extends AsyncTask<Void, Float, String> {
  private final double neoUpdateVersion = 1.0f;
  private final static boolean enableDebug = true;
  private final static String TAG = "[neoUpdate]";
  private final String serverUrl = "https://neoupdate-in.appspot.com";
  private String baseUrl;
  private int nConnections; /**< if 0 -> assumes local filesystem */
  private ArrayList<NewAsset> filesToDownload;
  private String tmpDir;
  private Boolean stopped;
  private String appToken;
  private String appSecret;
  private int totalFilesToDownload;
  private String deviceID;
  private String serialNo;
  private String macAddress;
  private Context context;
  private PackageInfo packageInfo;
  private NewAsset apkUpdatePath;
  private neoUpdateDB db;
  private ReentrantLock lock;
  private Boolean fromOfflineStorage = false;
  private Boolean fromNPKStorage = false;
  
  // File list
  private final String metafile = "/neoupdate.json";
  
  /**
   * file:// implies local filesystem
   * @param baseUrl
   * @param tmpDir
   * @param nSimultaneousConnections
   */
  public neoUpdate(Context c, String baseUrl, String tmpDir, String appToken, String appSecret, int nSimultaneousConnections) {
    this.baseUrl = baseUrl;
    this.tmpDir = tmpDir;
    this.appToken = appToken;
    this.appSecret = appSecret;
    nConnections = nSimultaneousConnections;
    context = c;
    if(nConnections <= 0)
      nConnections = 1;
    // Check for local filesystem
    if(baseUrl.startsWith("file:///") || appToken == null || appSecret == null) {
      nConnections = 0;
      this.baseUrl = baseUrl.replace("file:///", "/");
      fromOfflineStorage = true;
      if(baseUrl.endsWith(".npk"))
        fromNPKStorage = true;
    } else {
      this.baseUrl = serverUrl+baseUrl;
      if(this.baseUrl.endsWith("/"))
        this.baseUrl = this.baseUrl.substring(0, this.baseUrl.length()-1);
      lock = new ReentrantLock();
    }
    filesToDownload = new ArrayList<NewAsset>();
    deviceID = Settings.Secure.getString(c.getContentResolver(), Settings.Secure.ANDROID_ID);
    serialNo = neoUpdate.getSerialNo();
    macAddress = getWifiMac(c);
    if(deviceID == null) deviceID = "";
    if(serialNo == null) serialNo = "";
    if(macAddress == null) macAddress = "";
    try {
      packageInfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
    } catch(Exception e) { }
    db = new neoUpdateDB(c);
    stopped = false;
    totalFilesToDownload = 0;
    if(enableDebug) {
      Log.d(TAG, "DeviceID: "+deviceID);
      Log.d(TAG, "serialNo: "+serialNo);
      Log.d(TAG, "MAC Address: "+macAddress);
      Log.d(TAG, "SDCard: "+Environment.getExternalStorageDirectory().getAbsolutePath());
      Log.d(TAG, "Data: "+Environment.getDataDirectory().getAbsolutePath());
    }
  }
  
  private String getMetaFromNPK() {
    try {
      GZIPInputStream npkFile = new GZIPInputStream(new FileInputStream(baseUrl));
      //FileInputStream npkFile = new FileInputStream(baseUrl);
      TarArchiveInputStream input = new TarArchiveInputStream(npkFile);
      TarArchiveEntry ae;
      while((ae = input.getNextTarEntry())!=null) {
        if(ae.isDirectory())
          Log.e("[neoUpdate]", "Dir: "+ae.getName());
        else
          Log.e("[neoUpdate]", "File: "+ae.getName());
        if(ae.getName().equalsIgnoreCase("neoupdate.json")) {
          byte buff[] = new byte[(int) ae.getSize()]; 
          input.read(buff);
          input.close();
          return new String(buff);
        }
      }
      input.close();
    } catch(Exception e) {
      e.printStackTrace();
    }
    return null;
  }
  
  private void startUpdateApk(Uri installerUri) {
    MimeTypeMap myMime = MimeTypeMap.getSingleton();
    String mimeType = myMime.getMimeTypeFromExtension("apk");
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setDataAndType(installerUri, mimeType);//"application/vnd.android.package-archive");
    context.startActivity(intent);
  }
  
  public static String getDeviceID(Context c) {
    return Settings.Secure.getString(c.getContentResolver(), Settings.Secure.ANDROID_ID);
  }
  
  public static String getWifiMac(Context c) {
    try {
      WifiManager wifiMgr = (WifiManager) c.getSystemService(Context.WIFI_SERVICE);
      return wifiMgr.getConnectionInfo().getMacAddress();
    } catch(Exception e) { }
    return null;
  }
  
  private boolean checkSignature(String jsonContent, String sign) {
    Log.d(TAG, "JSON: "+jsonContent);

    if( sign == null)
      return false;
    final String publicKeyStr = 
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq+6EG/fAE+zIdh5Wzqnf"+
              "Fo4nCf7t7eJcKyvk1lqX1MdkIi/fUs8HQ4aQ4jWLCO4M1Gkz1FQiXOnheGLV5MXY"+
              "c9GyaglsofvpA/pU5d16FybX2pCevbTzcm39eU+XlwQWOr8gh23tYD8G6uMX6sIJ"+
              "W+1k1FWdud9errMVm0YUScI+J4AV5xzN0IQ29h9IeNp6oFqZ2ByWog6OBMTUDFIW"+
              "q8oRvH0OuPv3zFR5rKwsbTYb5Da8lhUht04dLBA860Y4zeUu98huvS9jQPu2N4ns"+
              "Hf425FfDJ/wae+7eLdQo7uFb+Wvc+PO9U39e6vXQfa8ZkUoXHD0XZN4jsFcKYuJw"+
              "OwIDAQAB";
    try {
      byte keyBytes[] = Base64.decode(publicKeyStr.getBytes(), Base64.NO_WRAP);

      X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(keyBytes);
      KeyFactory kf = KeyFactory.getInstance("RSA");
      PublicKey publicKey =  kf.generatePublic(publicSpec);

      Signature signer = Signature.getInstance("SHA1withRSA");
      signer.initVerify(publicKey);
      signer.update(jsonContent.getBytes(), 0, jsonContent.length());

      return signer.verify(Base64.decode(sign, Base64.NO_WRAP));
    } catch (Exception e) { }
    return false;
  }
  
  /* -- Works only with Android 2.3+ */
  public static String getSerialNo() {
    //return android.os.SystemProperties.get("ro.serialno", "unknown");
    try {
      Class<?> c = Class.forName("android.os.SystemProperties");       
      Method get = c.getMethod("get", String.class, String.class );     
      return (String) (get.invoke(c, "ro.serialno", ""));
    } catch(Exception e) { }
    return "";
  }

    /**
     * A Simpler HTTPConnection helper.
     * Used currently.
     */
  private static HttpURLConnection getHTTPConnection(String url) throws MalformedURLException, IOException {
    HttpURLConnection c;
    c = (HttpURLConnection) new URL(url).openConnection();
    c.setUseCaches(false);
    c.connect();
    return c;
  }

  public HttpResponse HttpWithPostData(String api, long fromBytes) {
    String url = baseUrl+api;
    try {
      url = baseUrl+URLEncoder.encode(api, "UTF-8");
    } catch(Exception e) {
      if(enableDebug)
        e.printStackTrace();
      url = baseUrl+api;
    }
      // Create a new HttpClient and Post Header
      HttpClient httpclient = new DefaultHttpClient();
      HttpPost httppost = new HttpPost(url);
      Log.d(TAG, "HTTP Fetch: "+url+" with Resume: "+fromBytes);
      
      if(appToken == null || appSecret == null || appToken.length() == 0 || appSecret.length() == 0)
        return null;
      if(deviceID.length() == 0 && serialNo.length() == 0 && macAddress.length() == 0)
        return null;

      try {
        // For resuming downloads
        if(fromBytes > 0)
          httppost.addHeader(new BasicHeader("Range","bytes="+fromBytes+"-"));
          // Add post data
          ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
          nameValuePairs.add(new BasicNameValuePair("APP_TOKEN", appToken));
          nameValuePairs.add(new BasicNameValuePair("APP_SECRET", appSecret));
          if(deviceID != null)
            nameValuePairs.add(new BasicNameValuePair("DEVICE_ID", deviceID));
          if(serialNo != null)
            nameValuePairs.add(new BasicNameValuePair("DEVICE_SERIAL", serialNo));
          if(macAddress != null)
            nameValuePairs.add(new BasicNameValuePair("DEVICE_MAC", macAddress));
          httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

          // Execute HTTP Post Request
          return  httpclient.execute(httppost);
      } catch (ClientProtocolException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      }
      return null;
  }
  
  private static String inputStreamToString(InputStream is) {
      String line = "";
      StringBuilder total = new StringBuilder();      
      BufferedReader rd = new BufferedReader(new InputStreamReader(is));

      try {
        while ((line = rd.readLine()) != null) 
            total.append("\n"+line); 
      } catch(Exception e) { 
        e.printStackTrace();
      };
      
      return total.toString();
  }
  
  public int totalFilesToDownload() {
    return totalFilesToDownload;
  }
  
  public int filesToDownload() {
    return filesToDownload.size();
  }
  
  /**
   * Stops the update procedure.
   * @return Success/failure
   */
  public Boolean stopUpdate() {
    stopped = true;
    return true;
  }
  
  public static Boolean isDevicePresent(Context c, String device) {
    neoUpdateDB db = new neoUpdateDB(c);
    return db.isDevicePresent(device);
  }
  
  private InputStream getLocalFile(String path) throws FileNotFoundException {
    FileInputStream file = new FileInputStream(path);
    return file;
  }

  private JSONObject downloadMetafile() {
    // TODO: Check for resuming download (?) - Not necessary for metafiles ?
    try {
      String str = null;
      if(nConnections == 0 || fromOfflineStorage) {
        if(fromNPKStorage)
          str = getMetaFromNPK();
        else
          str = inputStreamToString(getLocalFile(baseUrl+metafile));
      }
      else {
        HttpResponse res = HttpWithPostData(metafile, 0);
        if(res != null)
          str = inputStreamToString(res.getEntity().getContent());
      }
      if(str == null)
        return null;
      String sign = str.substring(str.lastIndexOf('\n')).trim();
      str = str.substring(0, str.lastIndexOf('\n')).trim();
      if(!checkSignature(str, sign)) {
        Log.e(TAG, "Signature Verification failed!");
        return null;
      }
      return new JSONObject(str);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  private Boolean parseMetafile(JSONObject metafile) {
    double version;
    boolean forceVersion;
    boolean allowed = false;
    try {
      version = metafile.getDouble("version");
      forceVersion = metafile.getBoolean("forceVersion");
      JSONObject appDetails = metafile.getJSONObject("app");
      JSONArray assets = metafile.getJSONArray("assets");
      JSONArray devices = metafile.getJSONArray("allowedDevices");

      int nAssets = assets.length();
      String packageName = appDetails.getString("packageName");
      int versionCode = appDetails.getInt("versionCode");
      String apkPath = appDetails.getString("APK");
      boolean offlineSupport = appDetails.getBoolean("offlineSupport");
      
      if(enableDebug) {
        Log.d(TAG,"Version: "+version+":"+neoUpdateVersion);
        Log.d(TAG,"Package Name: "+packageName+":"+packageInfo.packageName);
        Log.d(TAG,"APK Path: "+apkPath);
      }

      // Check if it is being updated using offline storage
      if(!offlineSupport && fromOfflineStorage) {
        Log.e(TAG, "Updating from offline storage is disabled for this app?");
        return false;
      }

      db.clearDevicesList();
      for(int i=0;i<devices.length();i++) {
        String device = devices.getString(i);
        if(device.length() > 0 && deviceID.compareToIgnoreCase(device) == 0)
          allowed = true;
        db.insertDevice(device);
        if(enableDebug)
          Log.d(TAG,"Device Allowed: "+device);
      }
      
      
      // DeviceID or signature error
      if(!allowed)
        return false;

      apkUpdatePath = null;
      if(version > neoUpdateVersion && forceVersion) {
        Log.e(TAG,"neoUpdate seems to be of older version! Required: "+version+" Current: "+neoUpdateVersion);
        return false;
      }
      
      if(packageInfo.packageName.compareTo(packageName) != 0) {
        Log.e(TAG, "PackageNames don't seem to match - url for some other app? Provided: "+packageName);
        return false;
      }
      
      if(packageInfo.versionCode < versionCode) {
        // APK Update Required - Lets first do that
        apkUpdatePath = new NewAsset();
        apkUpdatePath.path = apkPath;
        apkUpdatePath.md5 = appDetails.getString("md5");
        return true;
      }
            
      // Parse the assets
      for(int i=0;i<nAssets;i++) {
        JSONObject obj = assets.getJSONObject(i);
        NewAsset asset = new NewAsset();
        asset.path = obj.getString("path");
        asset.md5 = obj.getString("md5");

        // Ignore already downloaded files
        if(db.updateAndGetStatus(asset.path, asset.md5) == neoUpdateDB.UPDATE_STATUS.UPDATE_COMPLETE)
          continue;
        filesToDownload.add(asset);
        if(enableDebug) {
          Log.d(TAG, "Enqueued: "+asset.path+" With MD5: "+asset.md5);
        }
      }
      totalFilesToDownload = filesToDownload.size();
    } catch(Exception e) { 
      if(enableDebug)
        e.printStackTrace();
      return false;
    }
    return true;
  }
  
  private String mapPath(String path) {
    String ret = path;
    if(path.startsWith("/sdcard/"))
      path.replaceFirst("/sdcard/",Environment.getExternalStorageDirectory().getAbsolutePath()+"/");
    if(path.startsWith("/data/"))
      path.replaceFirst("/data/",Environment.getDataDirectory().getAbsolutePath()+"/");
    return ret;
  }
  
  private void createSubDirectories(String path) {
    try {
      File newDir = new File(path.substring(0, path.lastIndexOf('/')));
      newDir.mkdirs();
    } catch(Exception e) {
      if(enableDebug)
        e.printStackTrace();
    }
  }
  
  private boolean downloadFile(NewAsset asset, String toPath) {
    return downloadFile(asset, toPath, null, null);
  }
  
  private boolean downloadFile(NewAsset asset, String toPath, TarArchiveInputStream tin, TarArchiveEntry ae ) {
    if(enableDebug)
      Log.d(TAG,"Start download: "+asset.path + ":NPK: "+(tin!=null));
    boolean resume = (db.updateAndGetStatus(asset.path, asset.md5) == neoUpdateDB.UPDATE_STATUS.UPDATE_RESUME);
    String newPath;
    if(toPath != null)
      newPath = toPath;
    else
      newPath = mapPath(asset.path);
    createSubDirectories(newPath);
    File newFile = new File(newPath);
    long fromBytes = 0;
    if(resume)
      fromBytes = newFile.length();

    try {
      FileOutputStream os = new FileOutputStream(newFile, resume);
      db.setMd5(asset.path, asset.md5);

      if(tin != null && ae != null) {
        // Via NPK
        final int BUFF_SIZE = (8*1024); // Buffer size of 8KB
        byte[] buffer = new byte[BUFF_SIZE];
        int n = 0;
        long size = ae.getSize();
        if(resume && fromBytes > 0 && fromBytes < size) {
          tin.skip(fromBytes);
          size -= fromBytes;
        }
        while(size > 0) {
          n = BUFF_SIZE;
          if( n > size)
            n = (int)size;
          n = tin.read(buffer, 0, n);
          if(n < 0)
            break;
          if(n > 0)
            os.write(buffer, 0, n);
        }
      }
      else if(nConnections <= 0) {
        // Via Local File System
        FileInputStream is = new FileInputStream(baseUrl+asset.path);
        is.getChannel().transferTo(fromBytes, is.getChannel().size()-fromBytes, os.getChannel());
        is.close();
      } else {
        // Via Internet
        HttpResponse resp = HttpWithPostData(asset.path, fromBytes);
        resp.getEntity().writeTo(os);
      }
      db.setDownloaded(asset.path, true);
      os.close();
    }
    catch(Exception e) {
      if(enableDebug)
        e.printStackTrace();
      return false;
    }
    return true;
  }
  
  private String processFromLocalStorage() {
    while(filesToDownload.size() > 0 && !stopped) {
      NewAsset asset = filesToDownload.remove(0);
      if(!downloadFile(asset, null)) {
        Log.e(TAG,"File download failed!");
        return "Unable to find the required file: "+asset.path;
      }
      publishProgress((float)(totalFilesToDownload-filesToDownload.size())/(float)totalFilesToDownload);
    }
    return "Success";
  }
  
  private NewAsset findAndGetAsset(String path) {
    for(NewAsset asset:filesToDownload) {
      if(asset.path.equalsIgnoreCase(path)) {
        filesToDownload.remove(asset);
        return asset;
      }
    }
    return null;
  }

  private String processFromNPK() {
    try {
      GZIPInputStream npkFile = new GZIPInputStream(new FileInputStream(baseUrl));
      //FileInputStream npkFile = new FileInputStream(baseUrl);
      TarArchiveInputStream input = new TarArchiveInputStream(npkFile);
      TarArchiveEntry ae;
      while((ae = input.getNextTarEntry())!=null && filesToDownload.size() > 0 && !stopped) {
        if(ae.isDirectory()) {
          Log.e("[neoUpdate]", "Dir: "+ae.getName());
        } else {
          Log.e("[neoUpdate]", "File: "+ae.getName());
          String filename = ae.getName();
          NewAsset asset = findAndGetAsset(filename);
          if(asset != null) {
            downloadFile(asset, null, input, ae);
            publishProgress((float)(totalFilesToDownload-filesToDownload.size())/(float)totalFilesToDownload);
          }
        }
      }
      input.close();
    } catch(Exception e) {
      e.printStackTrace();
      return "Unknown Error: Update Failed!";
    }
    return "Success";
  }
  
  private boolean downloadAPKFromNPK() {
    try {
      String apkName = apkUpdatePath.path.replace("/", "");
      GZIPInputStream npkFile = new GZIPInputStream(new FileInputStream(baseUrl));
      //FileInputStream npkFile = new FileInputStream(baseUrl);
      TarArchiveInputStream input = new TarArchiveInputStream(npkFile);
      TarArchiveEntry ae;
      while((ae = input.getNextTarEntry())!=null) {
        if(!ae.isDirectory() && ae.getName().equalsIgnoreCase(apkName)) {
          String apkPath = tmpDir+apkUpdatePath.path;
          boolean status = downloadFile(apkUpdatePath, apkPath, input, ae);
          input.close();
          return status;
        }
      }
      input.close();
    } catch(Exception e) {
      e.printStackTrace();
    }

    return false;
  }

  @Override
  protected String doInBackground(Void... params) {
    JSONObject metafile = downloadMetafile();
    if(metafile == null)
      return "Could not download meta data. Please check the connection";
    if(!parseMetafile(metafile))
      return "Failed to authenticate the device or Failed to parse metadata.";
    
    if(apkUpdatePath != null) {
      String apkPath = "";
      if(fromNPKStorage) {
        apkPath = tmpDir+apkUpdatePath.path.replace("/", "");
        if(!downloadAPKFromNPK())
          return "Unable to save APK!";
      } else if(nConnections <= 0) {
        apkPath = baseUrl+apkUpdatePath.path;
      } else {
        apkPath = tmpDir+apkUpdatePath.path.substring(apkUpdatePath.path.lastIndexOf('/'));
        if(!downloadFile(apkUpdatePath, apkPath)) {
          return "Unable to download APK!";
        }
      }
      startUpdateApk(Uri.parse("file:///"+apkPath));
      return "APK Updated. Please re-run update after restarting the app to continue update";
    }
    if(nConnections > 0) {
      long waitTime = 1000/nConnections;
      if(waitTime <= 0)
        waitTime = 1;
      Thread threads[] = new Thread[nConnections];
      int i;
      for(i=0;i<nConnections;i++) {
        threads[i] = new Thread(new DownloadRunnable());
        threads[i].start();
      }
      while(!stopped) {
        if(totalFilesToDownload > 0) {
          float nComplete = (totalFilesToDownload-filesToDownload.size()-nConnections);
          if(nComplete >= 0)
            publishProgress(nComplete/(float)totalFilesToDownload);
        }
        boolean inprogress = false;
        for(i=0;i<nConnections;i++) {
          try {
            threads[i].join(waitTime);
          } catch(Exception e) { }
          inprogress = inprogress || threads[i].isAlive();
        }
        if(!inprogress)
          break;
        if(stopped && filesToDownload.size() > 0) {
          lock.lock();
          filesToDownload.clear();
          lock.unlock();
        }
      }
    } else {
      if(fromNPKStorage)
        return processFromNPK();
      return processFromLocalStorage();
    }
    
    return "Success";
  }
  
  private static class neoUpdateDB extends SQLiteOpenHelper {
    private final static String DB_NAME = "neoUpdateDB";
    private final static int DB_VERSION = 1;
    private final static String TABLE_NAME = "neoUpdate";
    private final static String COL_PATH = "PATH";
    private final static String COL_MD5 = "MD5";
    private final static String COL_DOWNLOADED = "DOWNLOADED";
    private final static String DEVICES_TABLE = "neoDevices";
    private final static String COL_DEVICES = "DEVICES";
    
    private SQLiteDatabase neoDB; 
    
    public static enum UPDATE_STATUS {
      UPDATE_REQUIRED,
      UPDATE_RESUME,
      UPDATE_COMPLETE
    };

    public neoUpdateDB(Context context) {
      super(context, DB_NAME, null, DB_VERSION);
      neoDB = getWritableDatabase();
    }
    
    public void insertDevice(String device) {
      ContentValues cv = new ContentValues();
      cv.put(COL_DEVICES, device);
      neoDB.insert(DEVICES_TABLE, null, cv);
    }
    
    public Boolean isDevicePresent(String device) {
      Cursor c = neoDB.query(DEVICES_TABLE, new String[] {COL_DEVICES}, 
          COL_DEVICES+"=?", new String[]{device}, null, null, null, "1");
      if(c != null && c.getCount() > 0) {
        c.close();
        return true;
      }
      return false;
    }
    
    /**
     * Call this after completion of the download (Or to pause and resume the download later)
     * @param path Path of the file
     * @param status True to set it as complete / False to resume later
     */
    public void setDownloaded(String path, boolean status) {
      ContentValues cv = new ContentValues();
      cv.put(COL_DOWNLOADED, status);
      neoDB.update(TABLE_NAME, cv, COL_PATH+"=?", new String[] {path});
    }
    
    /**
     * Call this to set the Md5, just after creating an empty file.
     * Note: Make sure not to call this before creating an empty file, 
     * otherwise it might cause the subsequent call to return UPDATE_RESUME wrongly.
     * 
     * @param path  Path of the file
     * @param md5 MD5 sum of the file
     */
    public void setMd5(String path, String md5) {
      ContentValues cv = new ContentValues();
      cv.put(COL_MD5, md5);
      neoDB.update(TABLE_NAME, cv, COL_PATH+"=?", new String[] {path});
    }
    
    private String getMd5Sum(File file) {
      try {
        byte[] buffer = new byte[8192];
        FileInputStream iStream = new FileInputStream(file);
        MessageDigest md = MessageDigest.getInstance("MD5");
        DigestInputStream dis = new DigestInputStream(iStream, md);
        while(dis.read(buffer) != -1) { }
        byte[] data = md.digest();
        StringBuffer md5 = new StringBuffer();
        for (int i=0;i<data.length;i++) {
          String hex = Integer.toHexString(0xFF & data[i]);
          if(hex.length() == 1)
            md5.append("0");
            md5.append(hex);
        }
        dis.close();
        return new String(md5);
      } catch(Exception e) { 
        if(enableDebug)
          e.printStackTrace();
      }
      return "";
    }
    
    /**
     * 
     * @param path
     * @param md5
     * @return
     */
    public UPDATE_STATUS updateAndGetStatus(String path, String md5) {
      boolean latestFileAvailable = false;
      Cursor c = neoDB.query(TABLE_NAME, new String[] {COL_MD5, COL_DOWNLOADED}, 
          COL_PATH+"=?", new String[]{path}, null, null, null, "1");

      int iMd5 = c.getColumnIndex(COL_MD5);
      int iDownloaded = c.getColumnIndex(COL_DOWNLOADED);
      File file = new File(path);
      
      if(c != null && c.getCount() > 0) {
        c.moveToFirst();
        boolean downloaded = (c.getInt(iDownloaded) > 0);
        boolean md5Match = (md5.compareToIgnoreCase(c.getString(iMd5)) == 0);
        c.close();

        if(!md5Match || !file.exists()) {
          if(downloaded)
            setDownloaded(path, false);
          return UPDATE_STATUS.UPDATE_REQUIRED;
        } else {
          if(downloaded)
            return UPDATE_STATUS.UPDATE_COMPLETE;
          return UPDATE_STATUS.UPDATE_RESUME;
        }        
      } else if(file.exists()) {
        // File exists check its MD5
        if(getMd5Sum(file).equalsIgnoreCase(md5))
          latestFileAvailable = true;
      }
      
      // New path - insert this
      ContentValues cv = new ContentValues();
      cv.put(COL_PATH, path);
      if(latestFileAvailable) {
        // Latest file is available - but it is not present in the db
        cv.put(COL_MD5, md5);
        cv.put(COL_DOWNLOADED, true);
      } else {
        cv.put(COL_MD5, ""); // Make sure not to input correct md5 - otherwise it might cause
                   // the subsequent call to return UPDATE_RESUME wrongly
        cv.put(COL_DOWNLOADED, false);
      }
      neoDB.insert(TABLE_NAME, null, cv);

      return latestFileAvailable ? UPDATE_STATUS.UPDATE_COMPLETE : UPDATE_STATUS.UPDATE_REQUIRED;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
      db.execSQL("CREATE TABLE "+TABLE_NAME+" ("+_ID+
          " INTEGER PRIMARY KEY AUTOINCREMENT, "+COL_PATH+" TEXT NOT NULL, "+
          COL_MD5+" TEXT NOT NULL, "+COL_DOWNLOADED+" INTEGER);");
      db.execSQL("CREATE TABLE "+DEVICES_TABLE+" ("+_ID+
          " INTEGER PRIMARY KEY AUTOINCREMENT, "+COL_DEVICES+" TEXT NOT NULL);");
    }
    
    public void clearDevicesList() {
      neoDB.execSQL("DELETE FROM "+DEVICES_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      db.execSQL("DROP TABLE IF EXISTS "+TABLE_NAME);
      db.execSQL("DROP TABLE IF EXISTS "+TABLE_NAME);
      onCreate(db);
    }
    
    @Override
    public void finalize() {
      neoDB.close();
    }
  }
  private static class NewAsset {
    public String path;
    public String md5;
  }
  private class DownloadRunnable implements Runnable {
    @Override
    public void run() {
      while(true) {
        NewAsset asset = null;
        lock.lock();
        try {
          if(filesToDownload.size() > 0)
            asset = filesToDownload.remove(0);
        } catch(Exception e) { 
          if(enableDebug)
            e.printStackTrace();
        }
        lock.unlock();
        if(asset != null)
          downloadFile(asset, null);
        else
          return;
      }
    }
  }
}




Java Source Code List

in.neoandroid.neoupdate.neoUpdate.java