package com.taobao.atlas.update;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.taobao.atlas.framework.Framework;
import android.taobao.atlas.runtime.RuntimeVariables;
import android.taobao.atlas.versionInfo.BaselineInfoManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.taobao.atlas.update.exception.MergeException;
import com.taobao.atlas.update.model.UpdateInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.BundleException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Created by guanjie on 2017/1/23.
*/
public class AwoPatchReceiver extends BroadcastReceiver{
public static final String UPDATE_JSON = "{\"baseVersion\":\"%s\",\"updateBundles\":[{\"dependency\":[],\"isMainDex\":%s,\"name\":\"%s\",\"version\":\"%s@%s\"}],\"updateVersion\":\"%s\"}";
public static String ATLAS_DEBUG_DIRECTORY;
public static final String PATCH_ACTION = "com.taobao.atlas.intent.PATCH_APP";
public static final String DEX_PATCH_ACTION = "com.taobao.atlas.intent.DEX_PATCH_APP";
public static final String ROLLBACK_ACTION = "com.taobao.atlas.intent.ROLLBACK_PATCH";
public static final String DEXROLLBACK_ACTION = "com.taobao.atlas.intent.ROLLBACK_DEX_PATCH";
static{
try {
ATLAS_DEBUG_DIRECTORY = RuntimeVariables.androidApplication.getExternalFilesDir("atlas-debug").getAbsolutePath();
} catch (Exception e) {
ATLAS_DEBUG_DIRECTORY = "/sdcard/Android/data/" + RuntimeVariables.androidApplication.getPackageName() + "/files/atlas-debug";
}
if(!new File(ATLAS_DEBUG_DIRECTORY).exists()){
new File(ATLAS_DEBUG_DIRECTORY).mkdirs();
}
}
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(DEX_PATCH_ACTION) && intent.getBooleanExtra("dexpatch",false)){
// dexpatch online
/**
* Intent i = new Intent();
i.putExtra("dexpatch",true);
i.putExtra("patch_location","file path");
i.putExtra("patch_info","json str");
*/
final String tpatchLocation = intent.getStringExtra("patch_location");
final String tpatchInfo = intent.getStringExtra("patch_info");
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if(!TextUtils.isEmpty(tpatchLocation) && !TextUtils.isEmpty(tpatchInfo)){
UpdateInfo info = parseObject(tpatchInfo);
try {
info.dexPatchVersion = Integer.parseInt(info.updateVersion);
}catch(Throwable e){
}
if(info.dexPatchVersion==0){
return null;
}
try {
AtlasUpdater.update(info,new File(tpatchLocation));
} catch (MergeException e) {
e.printStackTrace();
} catch (BundleException e) {
e.printStackTrace();
}
}
return null;
}
}.execute();
}else {
if (!Framework.isDeubgMode()) {
return;
}
if (context.getApplicationContext().getPackageName().equals(intent.getStringExtra("pkg"))) {
if (intent.getAction().equals(PATCH_ACTION)) {
Toast.makeText(context.getApplicationContext(), "动态部署安装中,请稍后...", Toast.LENGTH_LONG).show();
new PatchTask(intent.getAction()).execute();
} else if (intent.getAction().equals(DEX_PATCH_ACTION)) {
Toast.makeText(context.getApplicationContext(), "DexPath安装中,请稍后...", Toast.LENGTH_LONG).show();
new PatchTask(intent.getAction()).execute();
} else if (intent.getAction().equals(ROLLBACK_ACTION)) {
Toast.makeText(context.getApplicationContext(), "动态部署回滚,请稍后...", Toast.LENGTH_LONG).show();
new PatchTask(intent.getAction()).execute();
} else if (intent.getAction().equals(DEXROLLBACK_ACTION)) {
Toast.makeText(context.getApplicationContext(), "DexPatch回滚,请稍后...", Toast.LENGTH_LONG).show();
new PatchTask(intent.getAction()).execute();
}
}
}
}
public static class PatchTask extends AsyncTask<Void,Void,Void>{
private String mPatchAction;
public PatchTask(String action){
mPatchAction = action;
}
@Override
protected Void doInBackground(Void[] params) {
try {
wrapperPatchAndInstall(mPatchAction);
Log.d("PatchReceiver", RuntimeVariables.androidApplication.toString());
Intent intent = RuntimeVariables.androidApplication.getPackageManager().getLaunchIntentForPackage(RuntimeVariables.androidApplication.getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_NEW_TASK);
ResolveInfo info = RuntimeVariables.androidApplication.getPackageManager().resolveActivity(intent, 0);
if (info != null) {
Log.d("PatchReceiver", info.activityInfo.name);
} else {
Log.d("PatchReceiver", "no activity");
}
RuntimeVariables.androidApplication.startActivity(intent);
kill();
System.exit(0);
}catch(Throwable e){
e.printStackTrace();
}
return null;
}
private void wrapperPatchAndInstall(String action){
if(action.equals(ROLLBACK_ACTION)){
BaselineInfoManager.instance().rollback(false);
return;
}else if(action.equals(DEXROLLBACK_ACTION)){
BaselineInfoManager.instance().rollback(true);
return;
}
File debugDirectory = new File(ATLAS_DEBUG_DIRECTORY);
if (debugDirectory.exists()) {
File[] bundles = debugDirectory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if (filename.endsWith(".so")) {
return true;
}
return false;
}
});
File[] tpatchs = debugDirectory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if(filename.endsWith(".tpatch")){
return true;
}
return false;
}
});
UpdateInfo info = null;
File tpatchFile = null;
if(bundles!=null && bundles.length>0){
// so patch
if(bundles.length>1){
for(File item : bundles){
item.delete();
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(RuntimeVariables.androidApplication,"不支持同时patch多个文件,取消本次patch,请重新执行快速构建",Toast.LENGTH_LONG).show();
}
});
return;
}
String pkgname;
boolean isMainDex;
if (bundles[0].getName().startsWith("kernal") || bundles[0].getName().contains("com_taobao_mainDex")) {
//主dexbundle
pkgname = "com.taobao.maindex";
isMainDex = true;
} else {
//业务bundle
isMainDex = false;
PackageInfo pkgInfo = RuntimeVariables.androidApplication.getPackageManager().getPackageArchiveInfo(bundles[0].getAbsolutePath(), 0);
if (info != null) {
pkgname = pkgInfo.applicationInfo.packageName;
} else {
String fileName = bundles[0].getName();
fileName = fileName.substring(3, fileName.length() - 3);
pkgname = fileName.replace("_", ".");
}
}
String baseVersion = null;
try {
baseVersion = RuntimeVariables.androidApplication.getPackageManager().getPackageInfo(
RuntimeVariables.androidApplication.getPackageName(),0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if(baseVersion==null){
Log.e("AwoPatchReceiver","get baesVersion failed");
bundles[0].delete();
return;
}
String newVersion = baseVersion+"0.5";
String realJSON = String.format(UPDATE_JSON,
baseVersion,isMainDex,pkgname,baseVersion,newVersion,newVersion);
File tPatch = new File(ATLAS_DEBUG_DIRECTORY,String.format("patch-%s@%s.tpatch",newVersion,baseVersion));
try {
OutputStream os = new FileOutputStream(tPatch.getAbsolutePath());
ZipOutputStream zos = new ZipOutputStream(os);
zos.putNextEntry(new ZipEntry(isMainDex ? "libcom_taobao_maindex.so" : bundles[0].getName()));
FileInputStream fis = new FileInputStream(bundles[0]);
int j = 0;
byte[] buffer = new byte[512];
while((j = fis.read(buffer)) !=-1){
zos.write(buffer,0,j);
}
fis.close();
zos.closeEntry();
zos.close();
} catch (Throwable e) {
e.printStackTrace();
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(RuntimeVariables.androidApplication,"tPatch文件合成失败,请重试",Toast.LENGTH_LONG).show();
}
});
}
bundles[0].delete();
//start install
info = parseObject(realJSON);
tpatchFile = tPatch;
if(action.equals(DEX_PATCH_ACTION)) {
info.dexPatchVersion = System.currentTimeMillis();
}
}else if(tpatchs!=null && tpatchs.length>0){
// tpatch
if(tpatchs.length>1){
for(File item : tpatchs){
item.delete();
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(RuntimeVariables.androidApplication,"不支持同时patch多个tpatch文件,取消本次patch,请重新执行快速构建",Toast.LENGTH_LONG).show();
}
});
return;
}
File updateInfofile = new File(tpatchs[0].getParent(),tpatchs[0].getName().substring(0,tpatchs[0].getName().length()-7)+".json");
String jsonStr = getFromFile(updateInfofile.getAbsolutePath());
info = parseObject(jsonStr);
}
try {
AtlasUpdater.update(info, tpatchFile);
} catch (MergeException e) {
e.printStackTrace();
} catch (BundleException e) {
e.printStackTrace();
}
}
}
private String getFromFile(String fileName){
File file = new File(fileName);
Long fileLength = file.length();
byte[] filecontent = new byte[fileLength.intValue()];
try {
FileInputStream in = new FileInputStream(file);
in.read(filecontent);
in.close();
return new String(filecontent,"UTF-8");
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
private void kill() {
try {
ActivityManager am = (ActivityManager) RuntimeVariables.androidApplication.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> a = am.getRunningAppProcesses();
for (int i = 0; i < a.size(); i++) {
ActivityManager.RunningAppProcessInfo b = a.get(i);
if (b.processName.startsWith(RuntimeVariables.androidApplication.getPackageName()+":")) {
android.os.Process.killProcess(b.pid);
continue;
}
}
} catch (Exception e) {
}
}
}
public static UpdateInfo parseObject(String updateJsonStr){
if(updateJsonStr==null){
return null;
}
UpdateInfo info = new UpdateInfo();
info.updateBundles = new ArrayList<UpdateInfo.Item>();
try {
JSONObject json = new JSONObject(updateJsonStr);
info.baseVersion = json.getString("baseVersion");
info.updateVersion = json.getString("updateVersion");
JSONArray updateBundlesArray = json.getJSONArray("updateBundles");
for(int x=0; x<updateBundlesArray.length(); x++){
JSONObject updateBundleInfo = updateBundlesArray.getJSONObject(x);
UpdateInfo.Item item = new UpdateInfo.Item();
item.isMainDex = updateBundleInfo.getBoolean("isMainDex");
item.name = updateBundleInfo.getString("name");
item.version = updateBundleInfo.getString("version");
JSONArray array = updateBundleInfo.optJSONArray("dependency");
if(array!=null && array.length()>0) {
List<String> dependencies = new ArrayList<String>();
for(int n=0; n<array.length(); n++){
dependencies.add(dependencies.get(n));
}
item.dependency = dependencies;
}
info.updateBundles.add(item);
}
return info;
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
}