/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dl7.tinkerlib.util;
import android.content.Context;
import android.content.Intent;
import com.dl7.tinkerlib.reporter.SampleTinkerReport;
import com.tencent.tinker.lib.service.TinkerPatchService;
import com.tencent.tinker.lib.tinker.Tinker;
import com.tencent.tinker.lib.tinker.TinkerInstaller;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.lib.util.TinkerServiceInternals;
import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
/**
* optional
* tinker :patch process may killed by some reason, we can retry it to increase upgrade success rate
* if patch file is at sdcard, copy it to dataDir first. because some software may delete it.
*
* Created by zhangshaowen on 16/7/3.
*/
public class UpgradePatchRetry {
private static final String TAG = "Tinker.UpgradePatchRetry";
private static final String RETRY_INFO_NAME = "patch.retry";
private static final String TEMP_PATCH_NAME = "temp.apk";
private static final String RETRY_FILE_MD5_PROPERTY = "md5";
private static final String RETRY_COUNT_PROPERTY = "times";
private static final int RETRY_MAX_COUNT = 4;
private boolean isRetryEnable = false;
private File retryInfoFile = null;
private File tempPatchFile = null;
private Context context = null;
private static UpgradePatchRetry sInstance;
/**
* you must set after tinker has installed
*
* @param context
*/
public UpgradePatchRetry(Context context) {
this.context = context;
retryInfoFile = new File(SharePatchFileUtil.getPatchTempDirectory(context), RETRY_INFO_NAME);
tempPatchFile = new File(SharePatchFileUtil.getPatchTempDirectory(context), TEMP_PATCH_NAME);
}
public static UpgradePatchRetry getInstance(Context context) {
if (sInstance == null) {
sInstance = new UpgradePatchRetry(context);
}
return sInstance;
}
public void onPatchRetryLoad() {
if (!isRetryEnable) {
TinkerLog.w(TAG, "onPatchRetryLoad retry disabled, just return");
return;
}
Tinker tinker = Tinker.with(context);
//only retry on main process
if (!tinker.isMainProcess()) {
TinkerLog.w(TAG, "onPatchRetryLoad retry is not main process, just return");
return;
}
if (!retryInfoFile.exists()) {
TinkerLog.w(TAG, "onPatchRetryLoad retry info not exist, just return");
return;
}
if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) {
TinkerLog.w(TAG, "onPatchRetryLoad tinker service is running, just return");
return;
}
//must use temp file
String path = tempPatchFile.getAbsolutePath();
if (path == null || !new File(path).exists()) {
TinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is not exist, just return", path);
return;
}
TinkerLog.w(TAG, "onPatchRetryLoad patch file: %s is exist, retry to patch", path);
TinkerInstaller.onReceiveUpgradePatch(context, path);
SampleTinkerReport.onReportRetryPatch();
}
public void onPatchServiceStart(Intent intent) {
if (!isRetryEnable) {
TinkerLog.w(TAG, "onPatchServiceStart retry disabled, just return");
return;
}
if (intent == null) {
TinkerLog.e(TAG, "onPatchServiceStart intent is null, just return");
return;
}
String path = TinkerPatchService.getPatchPathExtra(intent);
if (path == null) {
TinkerLog.w(TAG, "onPatchServiceStart patch path is null, just return");
return;
}
RetryInfo retryInfo;
File patchFile = new File(path);
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
if (patchMd5 == null) {
TinkerLog.w(TAG, "onPatchServiceStart patch md5 is null, just return");
return;
}
if (retryInfoFile.exists()) {
retryInfo = RetryInfo.readRetryProperty(retryInfoFile);
if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) {
copyToTempFile(patchFile);
retryInfo.md5 = patchMd5;
retryInfo.times = "1";
} else {
int nowTimes = Integer.parseInt(retryInfo.times);
if (nowTimes >= RETRY_MAX_COUNT) {
SharePatchFileUtil.safeDeleteFile(tempPatchFile);
TinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!");
return;
} else {
retryInfo.times = String.valueOf(nowTimes + 1);
}
}
} else {
copyToTempFile(patchFile);
retryInfo = new RetryInfo(patchMd5, "1");
}
RetryInfo.writeRetryProperty(retryInfoFile, retryInfo);
}
public boolean onPatchListenerCheck(String md5) {
if (!isRetryEnable) {
TinkerLog.w(TAG, "onPatchListenerCheck retry disabled, just return");
return true;
}
if (!retryInfoFile.exists()) {
TinkerLog.w(TAG, "onPatchListenerCheck retry file is not exist, just return");
return true;
}
if (md5 == null) {
TinkerLog.w(TAG, "onPatchListenerCheck md5 is null, just return");
return true;
}
RetryInfo retryInfo = RetryInfo.readRetryProperty(retryInfoFile);
if (md5.equals(retryInfo.md5)) {
int nowTimes = Integer.parseInt(retryInfo.times);
if (nowTimes >= RETRY_MAX_COUNT) {
TinkerLog.w(TAG, "onPatchListenerCheck, retry count %d must exceed than max retry count", nowTimes);
SharePatchFileUtil.safeDeleteFile(tempPatchFile);
return false;
}
}
return true;
}
/**
* if we receive any result, we can delete the temp retry info file
*/
public void onPatchServiceResult() {
if (!isRetryEnable) {
TinkerLog.w(TAG, "onPatchServiceResult retry disabled, just return");
return;
}
//delete temp patch file
if (tempPatchFile.exists()) {
SharePatchFileUtil.safeDeleteFile(tempPatchFile);
}
}
public void setRetryEnable(boolean enable) {
isRetryEnable = enable;
}
private void copyToTempFile(File patchFile) {
if (patchFile.getAbsolutePath().equals(tempPatchFile.getAbsolutePath())) {
return;
}
TinkerLog.w(TAG, "try copy file: %s to %s", patchFile.getAbsolutePath(), tempPatchFile.getAbsolutePath());
try {
SharePatchFileUtil.copyFileUsingStream(patchFile, tempPatchFile);
} catch (IOException e) {
TinkerLog.e(TAG, "fail to copy file: %s to %s", patchFile.getAbsolutePath(), tempPatchFile.getAbsolutePath());
}
}
static class RetryInfo {
String md5;
String times;
RetryInfo(String md5, String times) {
this.md5 = md5;
this.times = times;
}
static RetryInfo readRetryProperty(File infoFile) {
String md5 = null;
String times = null;
Properties properties = new Properties();
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(infoFile);
properties.load(inputStream);
md5 = properties.getProperty(RETRY_FILE_MD5_PROPERTY);
times = properties.getProperty(RETRY_COUNT_PROPERTY);
} catch (IOException e) {
TinkerLog.e(TAG, "fail to readRetryProperty:" + e);
} finally {
SharePatchFileUtil.closeQuietly(inputStream);
}
return new RetryInfo(md5, times);
}
static void writeRetryProperty(File infoFile, RetryInfo info) {
if (info == null) {
return;
}
File parentFile = infoFile.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
Properties newProperties = new Properties();
newProperties.put(RETRY_FILE_MD5_PROPERTY, info.md5);
newProperties.put(RETRY_COUNT_PROPERTY, info.times);
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(infoFile, false);
newProperties.store(outputStream, null);
} catch (Exception e) {
// e.printStackTrace();
TinkerLog.printErrStackTrace(TAG, e, "retry write property fail");
} finally {
SharePatchFileUtil.closeQuietly(outputStream);
}
}
}
}