添加https支持

pull/9/head
AriaLyy 8 years ago
parent d37b67cb24
commit a99a4f05bb
  1. 2
      .idea/misc.xml
  2. 55
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  3. 107
      Aria/src/main/java/com/arialyy/aria/core/task/DownloadUtil.java
  4. 9
      Aria/src/main/java/com/arialyy/aria/util/CAConfiguration.java
  5. 112
      Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java
  6. 7
      app/src/main/java/com/arialyy/simple/single_task/SingleTaskActivity.java

@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" /> <ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" /> <ConfirmationsSetting value="0" id="Remove" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

@ -30,6 +30,7 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import com.arialyy.aria.core.command.CmdFactory; import com.arialyy.aria.core.command.CmdFactory;
import com.arialyy.aria.util.CAConfiguration;
import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.core.command.IDownloadCmd; import com.arialyy.aria.core.command.IDownloadCmd;
@ -48,12 +49,12 @@ import java.util.Set;
* Aria管理器任务操作在这里执行 * Aria管理器任务操作在这里执行
*/ */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class AriaManager { @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class AriaManager {
private static final String TAG = "AriaManager"; private static final String TAG = "AriaManager";
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
private static volatile AriaManager INSTANCE = null; private static volatile AriaManager INSTANCE = null;
private Map<String, AMReceiver> mTargets = new HashMap<>(); private Map<String, AMReceiver> mTargets = new HashMap<>();
private DownloadManager mManager; private DownloadManager mManager;
private LifeCallback mLifeCallback; private LifeCallback mLifeCallback;
private AriaManager(Context context) { private AriaManager(Context context) {
regAppLifeCallback(context); regAppLifeCallback(context);
@ -73,6 +74,24 @@ import java.util.Set;
return getTarget(obj); return getTarget(obj);
} }
/**
* 设置CA证书信息
*
* @param caAlias ca证书别名
* @param caPath assets 文件夹下的ca证书完整路径
*/
public void setCAInfo(String caAlias, String caPath) {
if (TextUtils.isEmpty(caAlias)) {
Log.e(TAG, "ca证书别名不能为null");
return;
} else if (TextUtils.isEmpty(caPath)) {
Log.e(TAG, "ca证书路径不能为null");
return;
}
CAConfiguration.CA_ALIAS = caAlias;
CAConfiguration.CA_PATH = caPath;
}
/** /**
* 获取下载列表 * 获取下载列表
*/ */
@ -100,7 +119,7 @@ import java.util.Set;
*/ */
public void stopAllTask() { public void stopAllTask() {
List<DownloadEntity> allEntity = mManager.getAllDownloadEntity(); List<DownloadEntity> allEntity = mManager.getAllDownloadEntity();
List<IDownloadCmd> stopCmds = new ArrayList<>(); List<IDownloadCmd> stopCmds = new ArrayList<>();
for (DownloadEntity entity : allEntity) { for (DownloadEntity entity : allEntity) {
if (entity.getState() == DownloadEntity.STATE_DOWNLOAD_ING) { if (entity.getState() == DownloadEntity.STATE_DOWNLOAD_ING) {
stopCmds.add(CommonUtil.createCmd(entity, CmdFactory.TASK_STOP)); stopCmds.add(CommonUtil.createCmd(entity, CmdFactory.TASK_STOP));
@ -159,8 +178,8 @@ import java.util.Set;
* 删除所有任务 * 删除所有任务
*/ */
public void cancelAllTask() { public void cancelAllTask() {
List<DownloadEntity> allEntity = mManager.getAllDownloadEntity(); List<DownloadEntity> allEntity = mManager.getAllDownloadEntity();
List<IDownloadCmd> cancelCmds = new ArrayList<>(); List<IDownloadCmd> cancelCmds = new ArrayList<>();
for (DownloadEntity entity : allEntity) { for (DownloadEntity entity : allEntity) {
cancelCmds.add(CommonUtil.createCmd(entity, CmdFactory.TASK_CANCEL)); cancelCmds.add(CommonUtil.createCmd(entity, CmdFactory.TASK_CANCEL));
} }
@ -174,9 +193,9 @@ import java.util.Set;
} }
private AMReceiver putTarget(Object obj) { private AMReceiver putTarget(Object obj) {
String clsName = obj.getClass().getName(); String clsName = obj.getClass().getName();
AMReceiver target = null; AMReceiver target = null;
String key = ""; String key = "";
if (!(obj instanceof Activity)) { if (!(obj instanceof Activity)) {
if (obj instanceof android.support.v4.app.Fragment) { if (obj instanceof android.support.v4.app.Fragment) {
key = clsName + "_" + ((Fragment) obj).getActivity().getClass().getName(); key = clsName + "_" + ((Fragment) obj).getActivity().getClass().getName();
@ -249,12 +268,12 @@ import java.util.Set;
*/ */
private void handleDialogLift(Dialog dialog) { private void handleDialogLift(Dialog dialog) {
try { try {
Field dismissField = CommonUtil.getField(dialog.getClass(), "mDismissMessage"); Field dismissField = CommonUtil.getField(dialog.getClass(), "mDismissMessage");
Message dismissMsg = (Message) dismissField.get(dialog); Message dismissMsg = (Message) dismissField.get(dialog);
//如果Dialog已经设置Dismiss事件,则查找cancel事件 //如果Dialog已经设置Dismiss事件,则查找cancel事件
if (dismissMsg != null) { if (dismissMsg != null) {
Field cancelField = CommonUtil.getField(dialog.getClass(), "mCancelMessage"); Field cancelField = CommonUtil.getField(dialog.getClass(), "mCancelMessage");
Message cancelMsg = (Message) cancelField.get(dialog); Message cancelMsg = (Message) cancelField.get(dialog);
if (cancelMsg != null) { if (cancelMsg != null) {
Log.e(TAG, "你已经对Dialog设置了Dismiss和cancel事件。为了防止内存泄露," Log.e(TAG, "你已经对Dialog设置了Dismiss和cancel事件。为了防止内存泄露,"
+ "请在dismiss方法中调用Aria.whit(this).removeSchedulerListener();来注销事件"); + "请在dismiss方法中调用Aria.whit(this).removeSchedulerListener();来注销事件");
@ -316,12 +335,12 @@ import java.util.Set;
* onDestroy * onDestroy
*/ */
private void destroySchedulerListener(Object obj) { private void destroySchedulerListener(Object obj) {
Set<String> keys = mTargets.keySet(); Set<String> keys = mTargets.keySet();
String clsName = obj.getClass().getName(); String clsName = obj.getClass().getName();
for (Iterator<Map.Entry<String, AMReceiver>> iter = mTargets.entrySet().iterator(); for (Iterator<Map.Entry<String, AMReceiver>> iter = mTargets.entrySet().iterator();
iter.hasNext(); ) { iter.hasNext(); ) {
Map.Entry<String, AMReceiver> entry = iter.next(); Map.Entry<String, AMReceiver> entry = iter.next();
String key = entry.getKey(); String key = entry.getKey();
if (key.equals(clsName) || key.contains(clsName)) { if (key.equals(clsName) || key.contains(clsName)) {
AMReceiver receiver = mTargets.get(key); AMReceiver receiver = mTargets.get(key);
if (receiver.obj != null) { if (receiver.obj != null) {

@ -20,7 +20,9 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import com.arialyy.aria.core.DownloadEntity; import com.arialyy.aria.core.DownloadEntity;
import com.arialyy.aria.util.CAConfiguration;
import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.SSLContextUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -29,43 +31,47 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.ProtocolException; import java.net.ProtocolException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
/** /**
* Created by lyy on 2015/8/25. * Created by lyy on 2015/8/25.
* 下载工具类 * 下载工具类
*/ */
final class DownloadUtil implements IDownloadUtil, Runnable { final class DownloadUtil implements IDownloadUtil, Runnable {
private static final String TAG = "DownloadUtil"; private static final String TAG = "DownloadUtil";
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
/** /**
* 线程数 * 线程数
*/ */
private final int THREAD_NUM; private final int THREAD_NUM;
//下载监听 //下载监听
private IDownloadListener mListener; private IDownloadListener mListener;
private int mConnectTimeOut = 5000 * 4; //连接超时时间 private int mConnectTimeOut = 5000 * 4; //连接超时时间
private int mReadTimeOut = 5000 * 20; //流读取的超时时间 private int mReadTimeOut = 5000 * 20; //流读取的超时时间
/** /**
* 已经完成下载任务的线程数量 * 已经完成下载任务的线程数量
*/ */
private boolean isDownloading = false; private boolean isDownloading = false;
private boolean isStop = false; private boolean isStop = false;
private boolean isCancel = false; private boolean isCancel = false;
private boolean isNewTask = true; private boolean isNewTask = true;
private boolean isSupportBreakpoint = true; private boolean isSupportBreakpoint = true;
private int mCompleteThreadNum = 0; private int mCompleteThreadNum = 0;
private int mCancelNum = 0; private int mCancelNum = 0;
private long mCurrentLocation = 0; private long mCurrentLocation = 0;
private int mStopNum = 0; private int mStopNum = 0;
private int mFailNum = 0; private int mFailNum = 0;
private Context mContext; private Context mContext;
private DownloadEntity mDownloadEntity; private DownloadEntity mDownloadEntity;
private ExecutorService mFixedThreadPool; private ExecutorService mFixedThreadPool;
private File mDownloadFile; //下载的文件 private File mDownloadFile; //下载的文件
private File mConfigFile;//下载信息配置文件 private File mConfigFile;//下载信息配置文件
private SparseArray<Runnable> mTask = new SparseArray<>(); private SparseArray<Runnable> mTask = new SparseArray<>();
DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) { DownloadUtil(Context context, DownloadEntity entity, IDownloadListener downloadListener) {
@ -222,8 +228,8 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
@Override public void run() { @Override public void run() {
try { try {
URL url = new URL(mDownloadEntity.getDownloadUrl()); URL url = new URL(mDownloadEntity.getDownloadUrl());
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); HttpURLConnection conn = handleConnection(url);
setConnectParam(conn); setConnectParam(conn);
conn.setRequestProperty("Range", "bytes=" + 0 + "-"); conn.setRequestProperty("Range", "bytes=" + 0 + "-");
conn.setConnectTimeout(mConnectTimeOut * 4); conn.setConnectTimeout(mConnectTimeOut * 4);
@ -242,7 +248,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
handleBreakpoint(conn); handleBreakpoint(conn);
} else if (code == HttpURLConnection.HTTP_OK || len < 0) { } else if (code == HttpURLConnection.HTTP_OK || len < 0) {
//在conn.setRequestProperty("Range", "bytes=" + 0 + "-");下,200为不支持断点状态 //在conn.setRequestProperty("Range", "bytes=" + 0 + "-");下,200为不支持断点状态
if (len < 0){ if (len < 0) {
failDownload("任务【" + mDownloadEntity.getDownloadUrl() + "】下载失败,文件长度小于0"); failDownload("任务【" + mDownloadEntity.getDownloadUrl() + "】下载失败,文件长度小于0");
return; return;
} }
@ -263,6 +269,30 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
} }
} }
/**
* 处理链接
*
* @throws IOException
*/
private HttpURLConnection handleConnection(URL url) throws IOException {
HttpURLConnection conn;
URLConnection urlConn = url.openConnection();
if (urlConn instanceof HttpsURLConnection) {
conn = (HttpsURLConnection) urlConn;
SSLContext sslContext =
SSLContextUtil.getSSLContext(CAConfiguration.CA_ALIAS, CAConfiguration.CA_ALIAS);
if (sslContext == null) {
sslContext = SSLContextUtil.getDefaultSLLContext();
}
SSLSocketFactory ssf = sslContext.getSocketFactory();
((HttpsURLConnection) conn).setSSLSocketFactory(ssf);
((HttpsURLConnection) conn).setHostnameVerifier(SSLContextUtil.HOSTNAME_VERIFIER);
} else {
conn = (HttpURLConnection) urlConn;
}
return conn;
}
/** /**
* 处理断点 * 处理断点
*/ */
@ -305,15 +335,15 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
} }
} }
} }
int blockSize = fileLength / THREAD_NUM; int blockSize = fileLength / THREAD_NUM;
int[] recordL = new int[THREAD_NUM]; int[] recordL = new int[THREAD_NUM];
int rl = 0; int rl = 0;
for (int i = 0; i < THREAD_NUM; i++) { for (int i = 0; i < THREAD_NUM; i++) {
recordL[i] = -1; recordL[i] = -1;
} }
for (int i = 0; i < THREAD_NUM; i++) { for (int i = 0; i < THREAD_NUM; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize; long startL = i * blockSize, endL = (i + 1) * blockSize;
Object state = pro.getProperty(mDownloadFile.getName() + "_state_" + i); Object state = pro.getProperty(mDownloadFile.getName() + "_state_" + i);
if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成 if (state != null && Integer.parseInt(state + "") == 1) { //该线程已经完成
mCurrentLocation += endL - startL; mCurrentLocation += endL - startL;
Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++"); Log.d(TAG, "++++++++++ 线程_" + i + "_已经下载完成 ++++++++++");
@ -381,12 +411,12 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
*/ */
private static class ConfigEntity { private static class ConfigEntity {
//文件大小 //文件大小
long fileSize; long fileSize;
String downloadUrl; String downloadUrl;
int threadId; int threadId;
long startLocation; long startLocation;
long endLocation; long endLocation;
File tempFile; File tempFile;
} }
/** /**
@ -395,7 +425,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
private class SingleThreadTask implements Runnable { private class SingleThreadTask implements Runnable {
private static final String TAG = "SingleThreadTask"; private static final String TAG = "SingleThreadTask";
private ConfigEntity configEntity; private ConfigEntity configEntity;
private String configFPath; private String configFPath;
private long currentLocation = 0; private long currentLocation = 0;
private SingleThreadTask(ConfigEntity downloadInfo) { private SingleThreadTask(ConfigEntity downloadInfo) {
@ -410,10 +440,11 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
@Override public void run() { @Override public void run() {
HttpURLConnection conn = null; HttpURLConnection conn = null;
InputStream is = null; InputStream is = null;
try { try {
URL url = new URL(configEntity.downloadUrl); URL url = new URL(configEntity.downloadUrl);
conn = (HttpURLConnection) url.openConnection(); //conn = (HttpURLConnection) url.openConnection();
conn = handleConnection(url);
if (isSupportBreakpoint) { if (isSupportBreakpoint) {
Log.d(TAG, "线程_" Log.d(TAG, "线程_"
+ configEntity.threadId + configEntity.threadId
@ -437,7 +468,7 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
//设置每条线程写入文件的位置 //设置每条线程写入文件的位置
file.seek(configEntity.startLocation); file.seek(configEntity.startLocation);
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int len; int len;
//当前子线程的下载位置 //当前子线程的下载位置
currentLocation = configEntity.startLocation; currentLocation = configEntity.startLocation;
while ((len = is.read(buffer)) != -1) { while ((len = is.read(buffer)) != -1) {
@ -597,8 +628,8 @@ final class DownloadUtil implements IDownloadUtil, Runnable {
* 将记录写入到配置文件 * 将记录写入到配置文件
*/ */
private void writeConfig(String key, String record) throws IOException { private void writeConfig(String key, String record) throws IOException {
File configFile = new File(configFPath); File configFile = new File(configFPath);
Properties pro = CommonUtil.loadConfig(configFile); Properties pro = CommonUtil.loadConfig(configFile);
pro.setProperty(key, record); pro.setProperty(key, record);
CommonUtil.saveConfig(configFile, pro); CommonUtil.saveConfig(configFile, pro);
} }

@ -0,0 +1,9 @@
package com.arialyy.aria.util;
/**
* Created by Aria.Lao on 2017/1/11.
*/
public class CAConfiguration {
public static String CA_PATH, CA_ALIAS;
}

@ -0,0 +1,112 @@
package com.arialyy.aria.util;
import com.arialyy.aria.core.DownloadManager;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* Created by Aria.Lao on 2017/1/11.
*/
public class SSLContextUtil {
/**
* 颁发服务器证书的 CA 未知
*
* @param caAlias CA证书别名
* @param caPath 保存在assets目录下的CA证书完整路径
*/
public static SSLContext getSSLContext(String caAlias, String caPath) {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X.509");
InputStream caInput = DownloadManager.APP.getAssets().open(caPath);
Certificate ca;
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry(caAlias, ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, null);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context;
} catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException | KeyManagementException | UnrecoverableKeyException e) {
e.printStackTrace();
}
return null;
}
/**
* 服务器证书不是由 CA 签署的而是自签署时获取默认的SSL
*/
public static SSLContext getDefaultSLLContext() {
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManagers }, new SecureRandom());
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}
/**
* 创建自己的 TrustManager这次直接信任服务器证书这种方法具有前面所述的将应用与证书直接关联的所有弊端但可以安全地操作
*/
private static TrustManager trustManagers = new X509TrustManager() {
@Override public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
@Override public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
public static final HostnameVerifier HOSTNAME_VERIFIER = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
}

@ -49,10 +49,10 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
public static final int DOWNLOAD_RESUME = 0x05; public static final int DOWNLOAD_RESUME = 0x05;
public static final int DOWNLOAD_COMPLETE = 0x06; public static final int DOWNLOAD_COMPLETE = 0x06;
public static final int DOWNLOAD_RUNNING = 0x07; public static final int DOWNLOAD_RUNNING = 0x07;
private static final String DOWNLOAD_URL = private static final String DOWNLOAD_URL =
"http://static.gaoshouyou.com/d/3a/93/573ae1db9493a801c24bf66128b11e39.apk"; //"http://kotlinlang.org/docs/kotlin-docs.pdf";
//private static final String DOWNLOAD_URL = "https://dl.google.com/dl/android/studio/install/2.2.3.0/android-studio-bundle-145.3537739-windows.exe";
// "http://www.yangqiang.im/wp-content/uploads/2016/10/%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F.png";
@Bind(R.id.progressBar) HorizontalProgressBarWithNumber mPb; @Bind(R.id.progressBar) HorizontalProgressBarWithNumber mPb;
@Bind(R.id.start) Button mStart; @Bind(R.id.start) Button mStart;
@Bind(R.id.stop) Button mStop; @Bind(R.id.stop) Button mStop;
@ -114,6 +114,7 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
setBtState(false); setBtState(false);
break; break;
case DOWNLOAD_COMPLETE: case DOWNLOAD_COMPLETE:
mPb.setProgress(100);
Toast.makeText(SingleTaskActivity.this, "下载完成", Toast.LENGTH_SHORT).show(); Toast.makeText(SingleTaskActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
mStart.setText("重新开始?"); mStart.setText("重新开始?");
mCancel.setEnabled(false); mCancel.setEnabled(false);

Loading…
Cancel
Save