android网络下载

/*
 * Copyright 2013 WonderMedia Technologies, Inc. All Rights Reserved. 
 *  
 * This PROPRIETARY SOFTWARE is the property of WonderMedia Technologies, Inc. 
 * and may contain trade secrets and/or other confidential information of 
 * WonderMedia Technologies, Inc. This file shall not be disclosed to any third party, 
 * in whole or in part, without prior written consent of WonderMedia. 
 *  
 * THIS PROPRIETARY SOFTWARE AND ANY RELATED DOCUMENTATION ARE PROVIDED AS IS, 
 * WITH ALL FAULTS, AND WITHOUT WARRANTY OF ANY KIND EITHER EXPRESS OR IMPLIED, 
 * AND WonderMedia TECHNOLOGIES, INC. DISCLAIMS ALL EXPRESS OR IMPLIED WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  
 */

package com.wmt.wondertv;

import java.io.BufferedInputStream;
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.net.HttpURLConnection;
import java.util.ArrayList;

import org.json.JSONArray;
import org.json.JSONObject;

import com.wmt.wondertv.netmedia.WonderDB;
import com.wmt.wondertv.setting.UpdateConfig;

import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

public final class UpdateManager {
	private static final String TAG = "UpdateManager";
	private static boolean DEBUG = true;

	private static final String CAB_EXT = ".zip";
	

	public static final String NAME_HOT = "hot";
	public static final String NAME_VIDEO = "video";
	public static final String NAME_TV = "tv";
	public static final String NAME_APK = "apk";
	//start modify by tangzefang
	public static final String NAME_SCREENSHORTS = "screenshots";
	public static int LOCAL_VERSION = -1;
	//end modify by tangzefang
	private static final String SERV_UPDATE_FOLDER="update/"; 
	
	private static final String STORAGE_PATH = "/data/data/"
		+ UpdateManager.class.getPackage().getName() + "/download/";

	private static ArrayList<Listener> sListeners  = new ArrayList<UpdateManager.Listener>();
	private static UpdateInfo sUpdateInfo = new UpdateInfo();
	
	public static final class UpdateInfo {
		public long busywaittime;
		public String serverRedirect;
		public String updateToken;
		
	
		//other time stamp are save to UpdateItem
		public String toString() {
			//TODO: more...
			return "token=" + updateToken;
		}
		
		private static long parseLong(final JSONObject obj, String node, long defValue){
			try {
				return Long.parseLong(obj.getString(node));
			}
			catch(Exception e){
				if(DEBUG)
					Log.v(TAG, node + "parse error:", e);
				return defValue;
			}
		}
		
		private static String parseString(final JSONObject obj, String node, String defValue){
			try {
				return obj.getString(node);
			}
			catch(Exception e){
				if(DEBUG)
					Log.v(TAG, node + "parse error:", e);
				return defValue;
			}			
		}

		private void updateFromJson(final JSONObject obj) {			
			serverRedirect = parseString(obj, "serverRedirect", "");
			busywaittime =   parseLong(obj, "busywaittime", 3600*1000);
			updateToken  =   parseString(obj, "updateToken", ".*");
		}
	};
	
	public static final class UpdateItem {
		UpdateItem(String name) {
			this.name = name;
			this.localVersion = Util.getPropertyKeyLong(this.name, 0);
			this.serverVersion = 0;
		}
		
		public String name;
		public long   serverVersion;
		public File   unzipFolder;		/* unzipped folder can be used now */
		
		private int   index;
		private long   localVersion;		/* a time stamp version */
		
		public String toString() {
			return name + ",version=" + localVersion + ",serverVersion=" + 
					serverVersion;
		}
	};
	
	private static final ArrayList<UpdateItem> sUpdateItems = new ArrayList<UpdateItem>();
	
	
	private static final HandlerThread sUpdateThread = new HandlerThread("Update");
	private static UpdateHandler sUpdateHandler;
	private static File sUpdateTempDir;
		
	private static final int MSG_GETUPDATEINFO	= 1;
	private static final int MSG_UPDATE_CATALOG	= 100;
	private static final int MANUAL_UPDATE = 1;

	
	public static void startup(Context context){
		if( null != sUpdateHandler)
			return;
		
		sUpdateTempDir = context.getDir("update", Context.MODE_WORLD_READABLE);
		if(DEBUG)
			Log.d(TAG, "sUpdateTempDir is " + sUpdateTempDir.getAbsolutePath());
		
		sUpdateItems.add( new UpdateItem(NAME_HOT));
		sUpdateItems.add( new UpdateItem(NAME_VIDEO));
		sUpdateItems.add( new UpdateItem(NAME_TV));
		//modify by tangzefang
		sUpdateItems.add(new UpdateItem(NAME_SCREENSHORTS));
		UpdateItem apk = new UpdateItem(NAME_APK);
		sUpdateItems.add( apk);
		
		for(int i = 0; i < sUpdateItems.size(); i++){
			sUpdateItems.get(i).index = i;
		}
		
		//if not download apk, use the current apk version.
		// apk is special, we get it from current apk version.
		// note: we won't re-download as we checked later.
		apk.localVersion = UpdateConfig.getVerCode(context);
		//modify by tangzefang
		LOCAL_VERSION = UpdateConfig.getVerCode(context);
				
		sUpdateThread.start();
		sUpdateHandler = new UpdateHandler(sUpdateThread.getLooper());

		Message msg = sUpdateHandler.obtainMessage(MSG_GETUPDATEINFO);
		//delay few seconds to start talk with server
		sUpdateHandler.sendMessageDelayed(msg, 3000);
	}
	
	public static interface Listener {
		/**
		 * report server's update info
		 * @param info null if server error
		 */
		public void onServerUpdateInfo(final UpdateInfo info);
		
		/**
		 * report this item is need to download or not 
		 * @param item
		 * @param needUpdate
		 */
		public void onUpdateNeed(final UpdateItem item, final boolean needUpdate);
		
		/**
		 * 
		 * @param item the downloading item
		 * @param newPrecent 0-100, -1 if error.
		 * When precent reach to 100, soon you will get onUpdateAvaliable callback.
		 * So don't do real update in this callback function.
		 */
		public void onDownloadProgress(final UpdateItem item, final int precent);

		
		/**
		 * item.unzipFolder now contains unzip content can be upgrade now.
		 * @param item
		 */
		public void onUpdateAvaliable(final UpdateItem item, final UpdateInfo updateInfo);
	};
	
	/**
	 * register listener
	 * @param l Listener object
	 * 	can be null if interest all items.
	 */
	public static void registerListener(Listener l){
		synchronized (sListeners) {
			sListeners.add(l);
		}
	}
	
	//Note: don't call this function in Listener's callback otherwise dead-lock happens.
	public static void unregisterListener(Listener l){
		synchronized (sListeners) {
			sListeners.remove(l);
		}
	}
	
	/**
	 * return listener
	 */
	public static ArrayList<Listener> getListener(){
		synchronized (sListeners) {
			return sListeners;
		}
	}
	
	/**
	 * clean listener
	 */
	public static void cleanListener(){
		synchronized (sListeners) {
			sListeners.clear();
		}
	} 
	
	public static void manualRefresh(){
		cleanPendingMessages();
		
		//then start the new one.
		Message msg = sUpdateHandler.obtainMessage(MSG_GETUPDATEINFO);
		msg.arg2 = MANUAL_UPDATE;	/* 1 means manual update */
		//msg.sendToTarget();
		//delay few seconds to start talk with server
		sUpdateHandler.sendMessageDelayed(msg, 2000);
	}

	public static File getItemFolder(String name){
		long version = Util.getPropertyKeyLong(name, 0);
		return getUnZipFolder(name, version);
	}
	
	public static InputStream getDownloaded(final File file) {
		FileInputStream inputStream = null;
		if (file.exists() && file.isFile()) {
			Log.v(TAG, "getDownloaded:" + file.getPath());
			try {
				inputStream = new FileInputStream(file);
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			Log.v(TAG, "getDownloaded file is not existing:" + file.getPath());
		}
		return inputStream;
	}
	
	public static InputStream openUpdateXMLFile(String name) {
		File file = getItemFolder(name);
		file = new File(file, name + ".xml");
		
		FileInputStream inputStream = null;
		if (file.exists() && file.isFile()) {
			Log.v(TAG, "openUpdateXMLFile:" + file.getPath());
			try {
				inputStream = new FileInputStream(file);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		} else {
			Log.v(TAG, "openUpdateXMLFile not existing:" + file.getPath());
		}
		return inputStream;
	}

	//Used for apk mostly.
	//apk.zip contains a changes.txt + apk.
	public static String getChangeLog(String name) {
		File file = getItemFolder(name);
		file = new File(file, "changes.txt");
		return Util.getFileString(file);
	}
	
	public static String getDefaultDictory() {
		return STORAGE_PATH;
	}
	
	public static void updateDone(String name)	{
		if(DEBUG)
			Log.v(TAG, name + " updated done");
		
		long version = Util.getPropertyKeyLong(name, 0);
		//now we clean the old folders start with name_xxx
		File latest = getUnZipFolder(name, version);
		cleanUnusedFolder(name, latest);
		
		//update item's local version too.
		for(UpdateItem item: sUpdateItems) {
			if( item.name.equals(name)){
				item.localVersion = version;
				break;
			}
		}
	}
	
	///////////////////////////////////////////////////////////////////////////////////////
	////  Blow are implementation 
	///////////////////////////////////////////////////////////////////////////////////////
	public static void cleanUnusedFolder(String prefix, File exclude)	{
		for(File f : sUpdateTempDir.listFiles()){
			if( f.isDirectory() && 
				f.getName().startsWith(prefix + "_") &&
				!f.getAbsolutePath().equals(exclude.getAbsolutePath()))
			{
				if(DEBUG)
					Log.v(TAG, "Delete unused folder:" + f.getAbsolutePath());
				Util.deleteRecursive(f);
			}
		}
	}
	
	//remove all old messages
	private static void cleanPendingMessages()
	{
		sUpdateHandler.removeMessages(MSG_GETUPDATEINFO);
		for(int i = 0; i < sUpdateItems.size(); i++) {
			sUpdateHandler.removeMessages(MSG_UPDATE_CATALOG + i);
		}
	}
			

	static void onServerUpdateInfo(UpdateInfo info) {
		synchronized (sListeners) {
			for (Listener l : sListeners)
				l.onServerUpdateInfo(info);
		}
	}
	

	static void onUpdateNeed(UpdateItem item, boolean needUpdate){
		synchronized (sListeners) {
			for (Listener l : sListeners)
				l.onUpdateNeed(item, needUpdate);
		}
	}
	
	static void onDownloadProgress(UpdateItem item, int newPrecent){
		synchronized (sListeners) {
			for (Listener l : sListeners)
				l.onDownloadProgress(item, newPrecent);
		}
	}

	
	static void onUpdateAvaliable(UpdateItem item, UpdateInfo updateInfo){
		synchronized (sListeners) {
			for (Listener l : sListeners)
				l.onUpdateAvaliable(item, updateInfo);
		}
	}

	
	private static File getUnZipFolder(String name, long version){
		if(version == -1) {
			//for temp purpose to download
			return new File(sUpdateTempDir, name + "_" + "temp");
		}
		else {
			return new File(sUpdateTempDir, name + "_" + version);
		}
	}

	private static final class UpdateHandler extends Handler {
		private long mAutoUpdateTimer;
		private long FAIL_RETRY_TIMER = 5 * 60 * 1000;		/* default 5 minutes if failed */
		
		public UpdateHandler(Looper looper) {
			super(looper);
		}

		private boolean getUpdateInfo(Message msg) throws Exception {
			String jsonText = "";
			mAutoUpdateTimer = FAIL_RETRY_TIMER;
			
			cleanPendingMessages();
			
			try {
				if(DEBUG)
					Log.v(TAG, "start to get server update info...");
				
				String updateURL = WonderDB.DB_UPDATE2_PHP;
				
				if( Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
					updateURL += "os=41";
				}
				else {
					updateURL += "os=42";
				}

				jsonText = Network.getWonderTVText(updateURL);
				if (jsonText == null || jsonText.length() == 0){
					onServerUpdateInfo(null);
					for(UpdateItem item : sUpdateItems) {
						if(item.name.equals(NAME_APK))
							continue;
						if( !getItemFolder(item.name).exists()){
							mAutoUpdateTimer = 10 * 1000;	//check later after 10s if any item not exist
							break;
						}
					}
					Log.w(TAG, "server update info return empty, check after " + (mAutoUpdateTimer/1000)); 
					return false;
				}

				jsonText = String.format("[%1$s]", jsonText);
				final JSONArray array = new JSONArray(jsonText);
				if (array.length() == 0) {
					Log.w(TAG, "json decode from server php wrong:" + jsonText);
					onServerUpdateInfo(null);
					return false;
				}

				final JSONObject obj = array.getJSONObject(0);
				sUpdateInfo.updateFromJson(obj);
				
				if(DEBUG)
					Log.v(TAG, "server update info:" + sUpdateInfo);

				onServerUpdateInfo(sUpdateInfo);
				
				//check the updateToken
				if( !Network.getWonderTVId().matches(sUpdateInfo.updateToken)){
					Log.v(TAG, "No token to update:" + Network.getWonderTVId() + "," + sUpdateInfo.updateToken);
					mAutoUpdateTimer = sUpdateInfo.busywaittime;
					return true;
				}
				
				for(int i = 0; i < sUpdateItems.size(); i++) {
					UpdateItem item = sUpdateItems.get(i);
					//start modify by tangzefang patch for upgrade
					if(NAME_APK.equals(item.name))
						item.localVersion = LOCAL_VERSION;
					else
						item.localVersion = Util.getPropertyKeyLong(item.name, 0);
					//end modify by tangzefang
					item.serverVersion = Long.parseLong(obj.getString(item.name));
					
					Log.d(TAG,  item.name + " local version " + item.localVersion + 
							",server version " + item.serverVersion); 
					
					if ( item.serverVersion > item.localVersion ||
							/* localVersion uses old time stamp, force a update then */
							item.localVersion > 1000000 ) {
						//check if we really need to download it or not
						Log.d(TAG, item.name + " need to update");
						
						File f = getUnZipFolder(item.name, item.serverVersion);
						if( f.exists()) {
							//item has been downloaded last time, just update is OK.
							Log.d(TAG, item.name + " ver " + item.serverVersion + " exists, update now");
							Util.setPropertyKeyValue(item.name, String.valueOf(item.serverVersion));
							
							item.unzipFolder = f;
							onUpdateAvaliable(item, sUpdateInfo);
						}
						else {
							File cur = getUnZipFolder(item.name, item.localVersion);
							cleanUnusedFolder(item.name, cur);
							
							//start to download this item now.
							Message itemMsg = sUpdateHandler.obtainMessage(MSG_UPDATE_CATALOG + i, item);
							onUpdateNeed(item, true);
							sUpdateHandler.sendMessage(itemMsg);
							
							Log.d(TAG, item.name + " start to download ver " + item.serverVersion);
						}
					} else {
						Log.d(TAG, item.name + " no need to update.");
						onUpdateNeed(item, false);
					}
				}
				
				//good. talk to server later.
				mAutoUpdateTimer = sUpdateInfo.busywaittime;
				return true;
			}
			catch(Exception e){
				Log.d(TAG, "Exception when getupdateinfo", e);
				onServerUpdateInfo(null);
				for(UpdateItem item : sUpdateItems) {
					if(item.name.equals(NAME_APK))
						continue;
					if( !getItemFolder(item.name).exists()){
						mAutoUpdateTimer = 10 * 1000;	//check later after 10s if any item not exist
						Log.w(TAG, item.name + " not exist, check after " + (mAutoUpdateTimer/1000) + "s");
						break;
					}
				} 
			}
			return false;
		}

		boolean executeZipUpdate(UpdateItem item) throws Exception {
			HttpURLConnection c = null;
			FileOutputStream fos = null;
			
			try {
				String urlPath;
				if( item.name.equals(NAME_APK)){
					if(Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
						urlPath = SERV_UPDATE_FOLDER + item.name + "41_" + item.serverVersion + CAB_EXT;
					else 
						urlPath = SERV_UPDATE_FOLDER + item.name + "42_" + item.serverVersion + CAB_EXT;
				}
				else {
					urlPath = SERV_UPDATE_FOLDER + item.name + "_" + item.serverVersion + CAB_EXT;
				}
				
				onDownloadProgress(item, 0);
				if( sUpdateInfo.serverRedirect.length() > 1) {
					Log.v(TAG, item.name + " download update from redirect server:" + sUpdateInfo.serverRedirect); 
					c = Network.createConnection(sUpdateInfo.serverRedirect, urlPath);
				}
				else {
					Log.v(TAG, item.name + " download update from direct server"); 
					c = Network.getWonderTVConnection(urlPath);
				}
				if (c == null) {
					Log.w(TAG, item.name + " can't create connection " + sUpdateInfo.serverRedirect);
					onDownloadProgress(item, -1);
					return false;
				}
				
				final File zipFile   = new File(sUpdateTempDir, item.name + CAB_EXT);
				//modify by tangzefang
				File tempUnZipFolder = getUnZipFolder(item.name, -1);
				
				Log.d(TAG, item.name + " reading update file from " + urlPath + "...");
				
				InputStream inputStream = new BufferedInputStream(c.getInputStream());

				int readLen = 0;
				final byte[] buf = new byte[8192];
				fos = new FileOutputStream(zipFile);
				

				int total = c.getContentLength();
				int read = 0;
				int lastPrecent = 0;
				while ((readLen = inputStream.read(buf)) != -1) {
					fos.write(buf, 0, readLen);
					read += readLen;
					if (total != -1) {
						int newPrecent = read * 100 / total;
						if (newPrecent != lastPrecent) {
							if( newPrecent != 100) {
								onDownloadProgress(item, newPrecent);
								lastPrecent = newPrecent;
							}
						}
					}
				}
				fos.flush();
				//start modify by tangzefang
				if(item.name.equals(NAME_SCREENSHORTS)){
					tempUnZipFolder = new File("/mnt/local/.screenshots/");
					tempUnZipFolder.setExecutable(true,false);
					tempUnZipFolder.setReadable(true,false);
					tempUnZipFolder.setWritable(true,false);
				}
				//end modify by tangzefang
				Util.deleteRecursive(tempUnZipFolder);
				
				if (Util.unZipFile(zipFile.getAbsolutePath(), tempUnZipFolder.getAbsolutePath())) {
					zipFile.delete();
					if(item.name.equals(NAME_APK)) { 
						//special process for the apk permission
						tempUnZipFolder.setReadable(true,  false);
						tempUnZipFolder.setExecutable(true, false);
						
						File newPath = new File(tempUnZipFolder, UpdateConfig.UPDATE_SAVENAME);
						File[] files = tempUnZipFolder.listFiles();
						for(File xf : files) {
							if( xf.getAbsolutePath().endsWith(".apk")) {
								xf.renameTo(newPath);
								break;
							}
						}
						newPath.setReadable(true,  false);
					}
					
					//Until now the download and unzip are done.
					//rename ~hot to hot123, this is the final folder to indicate item has been downloaded.
					File unZipFolder = getUnZipFolder(item.name, item.serverVersion);
					Util.deleteRecursive(unZipFolder);
					
					//start modify by tangzefang
					if(!item.name.equals(NAME_SCREENSHORTS))
						tempUnZipFolder.renameTo(unZipFolder);
					else 
						unZipFolder = tempUnZipFolder;
					//end modify by tangzefang
					Util.setPropertyKeyValue(item.name, String.valueOf(item.serverVersion));
					//start modify by tangzefang
					if(item.name.equals(NAME_SCREENSHORTS))
						item.localVersion =  Util.getPropertyKeyLong(item.name, 0);
					//end modify by tangzefang
					//right now even the program crash we will still use the new version now.

					Log.v(TAG, item.name + " unzip file to " + 
							unZipFolder.getAbsolutePath() + " successfully");
					
					item.unzipFolder = unZipFolder;
					onDownloadProgress(item, 100);
					
					onUpdateAvaliable(item, sUpdateInfo);

					return true;
				} else {
					Log.w(TAG, item.name + " unZipFile error at:" + zipFile);
					zipFile.delete();
					onDownloadProgress(item, -1);
				}
			} catch (Exception e) {
				onDownloadProgress(item, -1);
				throw e;
			} finally {
				if (fos != null)
					try {
						fos.close();
					} catch (IOException e) {
					}
				if (c != null)
					c.disconnect();
			}
			return false;
		}

		
		@Override
		public void handleMessage(Message msg) {
			try {
				switch(msg.what) {
				case MSG_GETUPDATEINFO:
					getUpdateInfo(msg);
					//restart the new update check always.
					Message getUpdateInfo = sUpdateHandler.obtainMessage(MSG_GETUPDATEINFO);
					sUpdateHandler.sendMessageDelayed(getUpdateInfo, mAutoUpdateTimer);
					if(DEBUG)
						Log.v(TAG, "scheduler next getupdateinfo after " + (mAutoUpdateTimer/1000) + "s");	
					break;
				default:
					UpdateItem item = (UpdateItem)msg.obj;
					if(!executeZipUpdate(item)){
						//download xxx.zip failed, try again later.
						Message itemUpdate = sUpdateHandler.obtainMessage(MSG_UPDATE_CATALOG + item.index);
						sUpdateHandler.sendMessageDelayed(itemUpdate, FAIL_RETRY_TIMER);
						if(DEBUG)
							Log.v(TAG, item.name + " scheduler next update as error");
					}
					break;
				}
			}
			catch(Exception e) {
				Log.w(TAG, "UpdateHandler exception", e);
			}
		}
	}
}





你可能感兴趣的:(android网络下载)