Handle rotation in AppDetails activity
This moves the download thread from an anonymous inner class to the class Downloader, which isn't tied to the activity. The state of the progress dialog is updated by polling the download thread at regular intervals - message passing seemed to need a bunch of synchronized methods and shared variables anyway (to ensure that all messages reached the right activity) so it was much simpler to just take it out. But maybe my approach is completely backward.
This commit is contained in:
parent
fabea142da
commit
84f150ed6a
@ -10,7 +10,7 @@
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
@ -18,17 +18,11 @@
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.app.ProgressDialog;
|
||||
@ -60,8 +54,6 @@ import android.widget.BaseAdapter;
|
||||
|
||||
public class AppDetails extends ListActivity {
|
||||
|
||||
private String LOCAL_PATH = "/sdcard/.fdroid";
|
||||
|
||||
private static final int REQUEST_INSTALL = 0;
|
||||
private static final int REQUEST_UNINSTALL = 1;
|
||||
|
||||
@ -155,9 +147,10 @@ public class AppDetails extends ListActivity {
|
||||
private DB.Apk curapk;
|
||||
private String appid;
|
||||
private PackageManager mPm;
|
||||
private ProgressDialog pd;
|
||||
private DB.Apk.CompatibilityChecker compatChecker;
|
||||
private volatile boolean cancelDownload;
|
||||
private Downloader download;
|
||||
private DownloadHandler downloadHandler;
|
||||
private boolean stateRetained;
|
||||
|
||||
private Context mctx = this;
|
||||
|
||||
@ -199,7 +192,10 @@ public class AppDetails extends ListActivity {
|
||||
pref_expert = prefs.getBoolean("expert", false);
|
||||
viewResetRequired = true;
|
||||
compatChecker = DB.Apk.CompatibilityChecker.getChecker(this);
|
||||
|
||||
download = (Downloader)getLastNonConfigurationInstance();
|
||||
if (download != null && download.isAlive()) {
|
||||
downloadHandler = new DownloadHandler();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -219,6 +215,23 @@ public class AppDetails extends ListActivity {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
stateRetained = true;
|
||||
return download;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (download != null && !stateRetained) {
|
||||
download.interrupt();
|
||||
}
|
||||
if (downloadHandler != null) {
|
||||
downloadHandler.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// Reset the display and list contents. Used when entering the activity, and
|
||||
// also when something has been installed/uninstalled.
|
||||
private void reset() {
|
||||
@ -383,7 +396,6 @@ public class AppDetails extends ListActivity {
|
||||
|
||||
// Install the version of this app denoted by 'curapk'.
|
||||
private void install() {
|
||||
|
||||
if (mInstalledSigID != null && curapk.sig != null
|
||||
&& !curapk.sig.equals(mInstalledSigID)) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
@ -398,192 +410,11 @@ public class AppDetails extends ListActivity {
|
||||
alert.show();
|
||||
return;
|
||||
}
|
||||
|
||||
cancelDownload = false;
|
||||
|
||||
pd = new ProgressDialog(this);
|
||||
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
pd.setMessage(getString(R.string.download_server));
|
||||
pd.setCancelable(true);
|
||||
pd.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
cancelDownload = true;
|
||||
}
|
||||
});
|
||||
pd.setButton(getString(R.string.cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
pd.show();
|
||||
|
||||
new Thread() {
|
||||
public void run() {
|
||||
|
||||
// Download the apk file from the repository...
|
||||
File f;
|
||||
String apk_file = null;
|
||||
String apkname = curapk.apkName;
|
||||
String localfile = new String(LOCAL_PATH + "/" + apkname);
|
||||
try {
|
||||
|
||||
// See if we already have this apk cached...
|
||||
f = new File(localfile);
|
||||
if (f.exists()) {
|
||||
// We do - if its hash matches, we'll use it...
|
||||
Hasher hash = new Hasher(curapk.hashType, f);
|
||||
if (hash.match(curapk.hash)) {
|
||||
apk_file = localfile;
|
||||
Log.d("FDroid", "Using cached apk at " + localfile);
|
||||
Message msg = new Message();
|
||||
msg.arg1 = 0;
|
||||
msg.arg2 = 1;
|
||||
msg.obj = new String(localfile);
|
||||
download_handler.sendMessage(msg);
|
||||
msg = new Message();
|
||||
msg.arg1 = 1;
|
||||
download_handler.sendMessage(msg);
|
||||
} else {
|
||||
Log.d("FDroid", "Not using cached apk at "
|
||||
+ localfile);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't got the apk locally, we'll have to download
|
||||
// it...
|
||||
if (apk_file == null) {
|
||||
|
||||
String remotefile;
|
||||
if (curapk.apkSource == null) {
|
||||
remotefile = curapk.server + "/"
|
||||
+ apkname.replace(" ", "%20");
|
||||
} else {
|
||||
remotefile = curapk.apkSource;
|
||||
}
|
||||
Log.d("FDroid", "Downloading apk from " + remotefile);
|
||||
|
||||
Message msg = new Message();
|
||||
msg.arg1 = 0;
|
||||
msg.arg2 = curapk.size;
|
||||
msg.obj = new String(remotefile);
|
||||
download_handler.sendMessage(msg);
|
||||
|
||||
BufferedInputStream getit = new BufferedInputStream(
|
||||
new URL(remotefile).openStream(), 8192);
|
||||
|
||||
FileOutputStream saveit = new FileOutputStream(
|
||||
localfile);
|
||||
BufferedOutputStream bout = new BufferedOutputStream(
|
||||
saveit, 1024);
|
||||
byte data[] = new byte[1024];
|
||||
|
||||
int totalRead = 0;
|
||||
int bytesRead = getit.read(data, 0, 1024);
|
||||
while (bytesRead != -1) {
|
||||
if (cancelDownload) {
|
||||
Log.d("FDroid", "Download cancelled!");
|
||||
break;
|
||||
}
|
||||
bout.write(data, 0, bytesRead);
|
||||
totalRead += bytesRead;
|
||||
msg = new Message();
|
||||
msg.arg1 = totalRead;
|
||||
download_handler.sendMessage(msg);
|
||||
bytesRead = getit.read(data, 0, 1024);
|
||||
}
|
||||
bout.close();
|
||||
getit.close();
|
||||
saveit.close();
|
||||
f = new File(localfile);
|
||||
if (cancelDownload) {
|
||||
f.delete();
|
||||
msg = download_cancelled_handler.obtainMessage();
|
||||
msg.sendToTarget();
|
||||
return;
|
||||
}
|
||||
Hasher hash = new Hasher(curapk.hashType, f);
|
||||
if (hash.match(curapk.hash)) {
|
||||
apk_file = localfile;
|
||||
} else {
|
||||
msg = new Message();
|
||||
msg.obj = getString(R.string.corrupt_download);
|
||||
download_error_handler.sendMessage(msg);
|
||||
Log.d("FDroid", "Downloaded file hash of "
|
||||
+ hash.getHash() + " did not match repo's "
|
||||
+ curapk.hash);
|
||||
// No point keeping a bad file, whether we're
|
||||
// caching or
|
||||
// not.
|
||||
f = new File(localfile);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("FDroid", "Download failed:\n"
|
||||
+ Log.getStackTraceString(e));
|
||||
Message msg = new Message();
|
||||
msg.obj = e.getMessage();
|
||||
download_error_handler.sendMessage(msg);
|
||||
// Get rid of any partial download...
|
||||
f = new File(localfile);
|
||||
f.delete();
|
||||
}
|
||||
|
||||
if (apk_file != null) {
|
||||
Message msg = new Message();
|
||||
msg.obj = apk_file;
|
||||
download_complete_handler.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
downloadHandler = new DownloadHandler();
|
||||
download = new Downloader(curapk);
|
||||
download.start();
|
||||
}
|
||||
|
||||
// Handler used to update the progress dialog while downloading. The
|
||||
// message contains the progress (bytes read) in arg1. If this is 0,
|
||||
// the message also contains the total bytes to read in arg2, and the
|
||||
// message in obj.
|
||||
private Handler download_handler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
pd.setProgress(msg.arg1);
|
||||
if (msg.arg1 == 0) {
|
||||
pd.setMessage(getString(R.string.download_server) + ":\n "
|
||||
+ msg.obj.toString());
|
||||
pd.setMax(msg.arg2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private Handler download_error_handler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
pd.dismiss();
|
||||
Toast.makeText(mctx, (String) msg.obj, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
};
|
||||
|
||||
private Handler download_complete_handler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
String apk_file = (String) msg.obj;
|
||||
installApk(apk_file);
|
||||
|
||||
pd.dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
private Handler download_cancelled_handler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
Toast.makeText(mctx, getString(R.string.download_cancelled),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
|
||||
private void removeApk(String id) {
|
||||
PackageInfo pkginfo;
|
||||
try {
|
||||
@ -606,24 +437,116 @@ public class AppDetails extends ListActivity {
|
||||
startActivityForResult(intent, REQUEST_INSTALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
|
||||
if (requestCode == REQUEST_INSTALL) {
|
||||
// If we're not meant to be caching, delete the apk file we just
|
||||
// installed (or maybe the user cancelled the install - doesn't
|
||||
// matter)
|
||||
// from the SD card...
|
||||
if (!pref_cacheDownloaded) {
|
||||
String apkname = curapk.apkName;
|
||||
String apk_file = new String(LOCAL_PATH + "/" + apkname);
|
||||
File file = new File(apk_file);
|
||||
file.delete();
|
||||
}
|
||||
|
||||
viewResetRequired = true;
|
||||
}
|
||||
|
||||
private ProgressDialog createProgressDialog(String file, int p, int max) {
|
||||
final ProgressDialog pd = new ProgressDialog(this);
|
||||
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
pd.setMessage(getString(R.string.download_server) + ":\n " + file);
|
||||
pd.setMax(max);
|
||||
pd.setProgress(p);
|
||||
pd.setCancelable(true);
|
||||
pd.setOnCancelListener(
|
||||
new DialogInterface.OnCancelListener() {
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
if (download != null) {
|
||||
download.interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
pd.setButton(getString(R.string.cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
pd.cancel();
|
||||
}
|
||||
});
|
||||
pd.show();
|
||||
return pd;
|
||||
}
|
||||
|
||||
// Handler used to update the progress dialog while downloading.
|
||||
private class DownloadHandler extends Handler {
|
||||
private ProgressDialog pd;
|
||||
|
||||
public DownloadHandler() {
|
||||
sendEmptyMessage(0); // Start updating progress
|
||||
}
|
||||
|
||||
public boolean updateProgress() {
|
||||
boolean finished = false;
|
||||
switch (download.getStatus()) {
|
||||
case RUNNING:
|
||||
if (pd == null) {
|
||||
pd = createProgressDialog(download.remoteFile(),
|
||||
download.getProgress(),
|
||||
download.getMax());
|
||||
} else {
|
||||
pd.setProgress(download.getProgress());
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
if (pd != null) pd.dismiss();
|
||||
String text;
|
||||
if (download.getErrorType() == Downloader.Error.CORRUPT)
|
||||
text = getString(R.string.corrupt_download);
|
||||
else
|
||||
text = download.getErrorMessage();
|
||||
Toast.makeText(AppDetails.this, text, Toast.LENGTH_LONG).show();
|
||||
finished = true;
|
||||
break;
|
||||
case DONE:
|
||||
if (pd != null) pd.dismiss();
|
||||
installApk(download.localFile());
|
||||
finished = true;
|
||||
break;
|
||||
case CANCELLED:
|
||||
Toast.makeText(AppDetails.this,
|
||||
getString(R.string.download_cancelled),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
return finished;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
// The dialog can't be dismissed when it's not displayed,
|
||||
// so do it when the activity is being destroyed.
|
||||
if (pd != null) {
|
||||
pd.dismiss();
|
||||
pd = null;
|
||||
}
|
||||
// Cancel any scheduled updates so that we don't
|
||||
// accidentally recreate the progress dialog.
|
||||
removeMessages(0);
|
||||
}
|
||||
|
||||
// Repeatedly run updateProgress() until it's finished.
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
boolean finished = updateProgress();
|
||||
if (!finished) {
|
||||
sendMessageDelayed(obtainMessage(), 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
switch(requestCode) {
|
||||
case REQUEST_INSTALL:
|
||||
// If we're not meant to be caching, delete the apk file we just
|
||||
// installed (or maybe the user cancelled the install - doesn't
|
||||
// matter) from the SD card...
|
||||
if (!pref_cacheDownloaded && download != null) {
|
||||
File file = new File(download.localFile());
|
||||
Log.d("FDroid", "Cleaning up: " + file);
|
||||
file.delete();
|
||||
}
|
||||
viewResetRequired = true;
|
||||
break;
|
||||
case REQUEST_UNINSTALL:
|
||||
viewResetRequired = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
210
src/org/fdroid/fdroid/Downloader.java
Normal file
210
src/org/fdroid/fdroid/Downloader.java
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2011 Ciaran Gultnieks <ciaran@ciarang.com>
|
||||
* Copyright (C) 2011 Henrik Tunedal <tunedal@gmail.com>
|
||||
*
|
||||
* 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 2
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.util.Log;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
|
||||
public class Downloader extends Thread {
|
||||
|
||||
private static final String LOCAL_PATH = "/sdcard/.fdroid";
|
||||
|
||||
private DB.Apk curapk;
|
||||
private String filename;
|
||||
private String localfile;
|
||||
|
||||
public static enum Status { STARTING, RUNNING, ERROR, DONE, CANCELLED };
|
||||
public static enum Error { CORRUPT, UNKNOWN };
|
||||
|
||||
private Status status = Status.STARTING;
|
||||
private Error error;
|
||||
private int progress;
|
||||
private int max;
|
||||
private String errorMessage;
|
||||
|
||||
Downloader(DB.Apk apk) {
|
||||
synchronized (this) {
|
||||
curapk = apk;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Current progress and maximum value for progress dialog
|
||||
public synchronized int getProgress() {
|
||||
return progress;
|
||||
}
|
||||
public synchronized int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
// Error code and error message, only valid if status is ERROR
|
||||
public synchronized Error getErrorType() {
|
||||
return error;
|
||||
}
|
||||
public synchronized String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
// The URL being downloaded or path to a cached file
|
||||
public synchronized String remoteFile() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
// Path to the downloaded APK
|
||||
public synchronized String localFile() {
|
||||
return localfile;
|
||||
}
|
||||
|
||||
// The APK being downloaded
|
||||
public synchronized DB.Apk getApk() {
|
||||
return curapk;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
// Download the apk file from the repository...
|
||||
File f;
|
||||
String apk_file = null;
|
||||
String apkname = curapk.apkName;
|
||||
synchronized (this) {
|
||||
localfile = new String(LOCAL_PATH + "/" + apkname);
|
||||
}
|
||||
try {
|
||||
|
||||
// See if we already have this apk cached...
|
||||
f = new File(localfile);
|
||||
if (f.exists()) {
|
||||
// We do - if its hash matches, we'll use it...
|
||||
Hasher hash = new Hasher(curapk.hashType, f);
|
||||
if (hash.match(curapk.hash)) {
|
||||
Log.d("FDroid", "Using cached apk at " + localfile);
|
||||
synchronized (this) {
|
||||
filename = apk_file = localfile;
|
||||
progress = 1;
|
||||
max = 1;
|
||||
status = Status.RUNNING;
|
||||
}
|
||||
} else {
|
||||
Log.d("FDroid", "Not using cached apk at "
|
||||
+ localfile);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't got the apk locally, we'll have to download
|
||||
// it...
|
||||
if (apk_file == null) {
|
||||
|
||||
String remotefile;
|
||||
if (curapk.apkSource == null) {
|
||||
remotefile = curapk.server + "/"
|
||||
+ apkname.replace(" ", "%20");
|
||||
} else {
|
||||
remotefile = curapk.apkSource;
|
||||
}
|
||||
Log.d("FDroid", "Downloading apk from " + remotefile);
|
||||
synchronized (this) {
|
||||
filename = remotefile;
|
||||
progress = 0;
|
||||
max = curapk.size;
|
||||
status = Status.RUNNING;
|
||||
}
|
||||
|
||||
BufferedInputStream getit =
|
||||
new BufferedInputStream(new URL(remotefile).openStream(),
|
||||
8192);
|
||||
|
||||
FileOutputStream saveit =
|
||||
new FileOutputStream(localfile);
|
||||
BufferedOutputStream bout =
|
||||
new BufferedOutputStream(saveit, 1024);
|
||||
byte data[] = new byte[1024];
|
||||
|
||||
int totalRead = 0;
|
||||
int bytesRead = getit.read(data, 0, 1024);
|
||||
while (bytesRead != -1) {
|
||||
if (isInterrupted()) {
|
||||
Log.d("FDroid", "Download cancelled!");
|
||||
break;
|
||||
}
|
||||
bout.write(data, 0, bytesRead);
|
||||
totalRead += bytesRead;
|
||||
synchronized (this) {
|
||||
progress = totalRead;
|
||||
}
|
||||
bytesRead = getit.read(data, 0, 1024);
|
||||
}
|
||||
bout.close();
|
||||
getit.close();
|
||||
saveit.close();
|
||||
f = new File(localfile);
|
||||
if (isInterrupted()) {
|
||||
f.delete();
|
||||
synchronized (this) {
|
||||
status = Status.CANCELLED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Hasher hash = new Hasher(curapk.hashType, f);
|
||||
if (hash.match(curapk.hash)) {
|
||||
apk_file = localfile;
|
||||
} else {
|
||||
synchronized (this) {
|
||||
error = Error.CORRUPT;
|
||||
errorMessage = null;
|
||||
status = Status.ERROR;
|
||||
}
|
||||
Log.d("FDroid", "Downloaded file hash of "
|
||||
+ hash.getHash() + " did not match repo's "
|
||||
+ curapk.hash);
|
||||
// No point keeping a bad file, whether we're
|
||||
// caching or not.
|
||||
f = new File(localfile);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("FDroid", "Download failed:\n"
|
||||
+ Log.getStackTraceString(e));
|
||||
synchronized (this) {
|
||||
error = Error.UNKNOWN;
|
||||
errorMessage = e.getMessage();
|
||||
status = Status.ERROR;
|
||||
}
|
||||
// Get rid of any partial download...
|
||||
f = new File(localfile);
|
||||
f.delete();
|
||||
}
|
||||
|
||||
if (apk_file != null) {
|
||||
synchronized (this) {
|
||||
status = Status.DONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user