Merge branch 'simplify-downloaders' into 'master'
Simplify Downloaders This is some groundwork to simplify the Downloader stuff in preparation to moving it to something like an `IntentService`, as part of #601. This mostly removes unused bits that I've found in the process of writing the `DownloaderService`. Some of these events will be added back in a more consistent way, so that there is one event type for the same idea throughout the code base. See merge request !236
This commit is contained in:
commit
ec53f4e05c
@ -19,6 +19,7 @@ dependencies {
|
|||||||
compile 'eu.chainfire:libsuperuser:1.0.0.201602271131'
|
compile 'eu.chainfire:libsuperuser:1.0.0.201602271131'
|
||||||
compile 'cc.mvdan.accesspoint:library:0.1.3'
|
compile 'cc.mvdan.accesspoint:library:0.1.3'
|
||||||
compile 'info.guardianproject.netcipher:netcipher:1.2.1'
|
compile 'info.guardianproject.netcipher:netcipher:1.2.1'
|
||||||
|
compile 'commons-io:commons-io:2.4'
|
||||||
compile 'commons-net:commons-net:3.4'
|
compile 'commons-net:commons-net:3.4'
|
||||||
compile 'org.openhab.jmdns:jmdns:3.4.2'
|
compile 'org.openhab.jmdns:jmdns:3.4.2'
|
||||||
compile('ch.acra:acra:4.8.2') {
|
compile('ch.acra:acra:4.8.2') {
|
||||||
@ -72,6 +73,7 @@ if (!hasProperty('sourceDeps')) {
|
|||||||
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
||||||
'eu.chainfire:libsuperuser:018344ff19ee94d252c14b4a503ee8b519184db473a5af83513f5837c413b128',
|
'eu.chainfire:libsuperuser:018344ff19ee94d252c14b4a503ee8b519184db473a5af83513f5837c413b128',
|
||||||
'cc.mvdan.accesspoint:library:dc89a085d6bc40381078b8dd7776b12bde0dbaf8ffbcddb17ec4ebc3edecc7ba',
|
'cc.mvdan.accesspoint:library:dc89a085d6bc40381078b8dd7776b12bde0dbaf8ffbcddb17ec4ebc3edecc7ba',
|
||||||
|
'commons-io:commons-io:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581',
|
||||||
'commons-net:commons-net:38cf2eca826b8bcdb236fc1f2e79e0c6dd8e7e0f5c44a3b8e839a1065b2fbe2e',
|
'commons-net:commons-net:38cf2eca826b8bcdb236fc1f2e79e0c6dd8e7e0f5c44a3b8e839a1065b2fbe2e',
|
||||||
'info.guardianproject.netcipher:netcipher:611ec5bde9d799fd57e1efec5c375f9f460de2cdda98918541decc9a7d02f2ad',
|
'info.guardianproject.netcipher:netcipher:611ec5bde9d799fd57e1efec5c375f9f460de2cdda98918541decc9a7d02f2ad',
|
||||||
'org.openhab.jmdns:jmdns:7a4b34b5606bbd2aff7fdfe629edcb0416fccd367fb59a099f210b9aba4f0bce',
|
'org.openhab.jmdns:jmdns:7a4b34b5606bbd2aff7fdfe629edcb0416fccd367fb59a099f210b9aba4f0bce',
|
||||||
|
@ -437,14 +437,6 @@
|
|||||||
<action android:name="android.net.wifi.STATE_CHANGE" />
|
<action android:name="android.net.wifi.STATE_CHANGE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name=".receiver.DownloadManagerReceiver" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<service android:name=".UpdateService" />
|
<service android:name=".UpdateService" />
|
||||||
<service android:name=".net.WifiStateChangeService" />
|
<service android:name=".net.WifiStateChangeService" />
|
||||||
|
@ -1,231 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* 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 org.apache.commons.io.input;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a stream that will only supply bytes up to a certain length - if its
|
|
||||||
* position goes above that, it will stop.
|
|
||||||
* <p>
|
|
||||||
* This is useful to wrap ServletInputStreams. The ServletInputStream will block
|
|
||||||
* if you try to read content from it that isn't there, because it doesn't know
|
|
||||||
* whether the content hasn't arrived yet or whether the content has finished.
|
|
||||||
* So, one of these, initialized with the Content-length sent in the
|
|
||||||
* ServletInputStream's header, will stop it blocking, providing it's been sent
|
|
||||||
* with a correct content length.
|
|
||||||
*
|
|
||||||
* @version $Id: BoundedInputStream.java 1307462 2012-03-30 15:13:11Z ggregory $
|
|
||||||
* @since 2.0
|
|
||||||
*/
|
|
||||||
public class BoundedInputStream extends InputStream {
|
|
||||||
|
|
||||||
/** the wrapped input stream */
|
|
||||||
private final InputStream in;
|
|
||||||
|
|
||||||
/** the max length to provide */
|
|
||||||
private final long max;
|
|
||||||
|
|
||||||
/** the number of bytes already returned */
|
|
||||||
private long pos;
|
|
||||||
|
|
||||||
/** the marked position */
|
|
||||||
private long mark = -1;
|
|
||||||
|
|
||||||
/** flag if close shoud be propagated */
|
|
||||||
private boolean propagateClose = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new <code>BoundedInputStream</code> that wraps the given input
|
|
||||||
* stream and limits it to a certain size.
|
|
||||||
*
|
|
||||||
* @param in The wrapped input stream
|
|
||||||
* @param size The maximum number of bytes to return
|
|
||||||
*/
|
|
||||||
public BoundedInputStream(InputStream in, long size) {
|
|
||||||
// Some badly designed methods - eg the servlet API - overload length
|
|
||||||
// such that "-1" means stream finished
|
|
||||||
this.max = size;
|
|
||||||
this.in = in;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new <code>BoundedInputStream</code> that wraps the given input
|
|
||||||
* stream and is unlimited.
|
|
||||||
*
|
|
||||||
* @param in The wrapped input stream
|
|
||||||
*/
|
|
||||||
public BoundedInputStream(InputStream in) {
|
|
||||||
this(in, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>read()</code> method if
|
|
||||||
* the current position is less than the limit.
|
|
||||||
* @return the byte read or -1 if the end of stream or
|
|
||||||
* the limit has been reached.
|
|
||||||
* @throws IOException if an I/O error occurs
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (max >= 0 && pos >= max) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int result = in.read();
|
|
||||||
pos++;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>read(byte[])</code> method.
|
|
||||||
* @param b the buffer to read the bytes into
|
|
||||||
* @return the number of bytes read or -1 if the end of stream or
|
|
||||||
* the limit has been reached.
|
|
||||||
* @throws IOException if an I/O error occurs
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b) throws IOException {
|
|
||||||
return this.read(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>read(byte[], int, int)</code> method.
|
|
||||||
* @param b the buffer to read the bytes into
|
|
||||||
* @param off The start offset
|
|
||||||
* @param len The number of bytes to read
|
|
||||||
* @return the number of bytes read or -1 if the end of stream or
|
|
||||||
* the limit has been reached.
|
|
||||||
* @throws IOException if an I/O error occurs
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
if (max >= 0 && pos >= max) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
long maxRead = max >= 0 ? Math.min(len, max - pos) : len;
|
|
||||||
int bytesRead = in.read(b, off, (int) maxRead);
|
|
||||||
|
|
||||||
if (bytesRead == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos += bytesRead;
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>skip(long)</code> method.
|
|
||||||
* @param n the number of bytes to skip
|
|
||||||
* @return the actual number of bytes skipped
|
|
||||||
* @throws IOException if an I/O error occurs
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long skip(long n) throws IOException {
|
|
||||||
long toSkip = max >= 0 ? Math.min(n, max - pos) : n;
|
|
||||||
long skippedBytes = in.skip(toSkip);
|
|
||||||
pos += skippedBytes;
|
|
||||||
return skippedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int available() throws IOException {
|
|
||||||
if (max >= 0 && pos >= max) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return in.available();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>toString()</code> method.
|
|
||||||
* @return the delegate's <code>toString()</code>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return in.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>close()</code> method
|
|
||||||
* if {@link #isPropagateClose()} is {@code true}.
|
|
||||||
* @throws IOException if an I/O error occurs
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
if (propagateClose) {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>reset()</code> method.
|
|
||||||
* @throws IOException if an I/O error occurs
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public synchronized void reset() throws IOException {
|
|
||||||
in.reset();
|
|
||||||
pos = mark;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>mark(int)</code> method.
|
|
||||||
* @param readlimit read ahead limit
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public synchronized void mark(int readlimit) {
|
|
||||||
in.mark(readlimit);
|
|
||||||
mark = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the delegate's <code>markSupported()</code> method.
|
|
||||||
* @return true if mark is supported, otherwise false
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean markSupported() {
|
|
||||||
return in.markSupported();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the {@link #close()} method
|
|
||||||
* should propagate to the underling {@link InputStream}.
|
|
||||||
*
|
|
||||||
* @return {@code true} if calling {@link #close()}
|
|
||||||
* propagates to the <code>close()</code> method of the
|
|
||||||
* underlying stream or {@code false} if it does not.
|
|
||||||
*/
|
|
||||||
public boolean isPropagateClose() {
|
|
||||||
return propagateClose;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set whether the {@link #close()} method
|
|
||||||
* should propagate to the underling {@link InputStream}.
|
|
||||||
*
|
|
||||||
* @param propagateClose {@code true} if calling
|
|
||||||
* {@link #close()} propagates to the <code>close()</code>
|
|
||||||
* method of the underlying stream or
|
|
||||||
* {@code false} if it does not.
|
|
||||||
*/
|
|
||||||
public void setPropagateClose(boolean propagateClose) {
|
|
||||||
this.propagateClose = propagateClose;
|
|
||||||
}
|
|
||||||
}
|
|
@ -92,7 +92,6 @@ import org.fdroid.fdroid.installer.Installer;
|
|||||||
import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException;
|
import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException;
|
||||||
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
|
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
|
||||||
import org.fdroid.fdroid.net.ApkDownloader;
|
import org.fdroid.fdroid.net.ApkDownloader;
|
||||||
import org.fdroid.fdroid.net.AsyncDownloaderFromAndroid;
|
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -434,18 +433,6 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
|
|||||||
}
|
}
|
||||||
|
|
||||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||||
|
|
||||||
// Check if a download is running for this app
|
|
||||||
if (AsyncDownloaderFromAndroid.isDownloading(this, app.packageName) >= 0) {
|
|
||||||
// call install() to re-setup the listeners and downloaders
|
|
||||||
// the AsyncDownloader will not restart the download since the download is running,
|
|
||||||
// and thus the version we pass to install() is not important
|
|
||||||
refreshHeader();
|
|
||||||
refreshApkList();
|
|
||||||
final Apk apkToInstall = ApkProvider.Helper.find(this, app.packageName, app.suggestedVercode);
|
|
||||||
install(apkToInstall);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -102,7 +102,7 @@ public class RepoUpdater {
|
|||||||
repo.getCredentials()
|
repo.getCredentials()
|
||||||
);
|
);
|
||||||
downloader.setCacheTag(repo.lastetag);
|
downloader.setCacheTag(repo.lastetag);
|
||||||
downloader.downloadUninterrupted();
|
downloader.download();
|
||||||
|
|
||||||
if (downloader.isCached()) {
|
if (downloader.isCached()) {
|
||||||
// The index is unchanged since we last read it. We just mark
|
// The index is unchanged since we last read it. We just mark
|
||||||
@ -118,6 +118,9 @@ public class RepoUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new UpdateException(repo, "Error getting index file", e);
|
throw new UpdateException(repo, "Error getting index file", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignored if canceled, the local database just won't be updated
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return downloader;
|
return downloader;
|
||||||
}
|
}
|
||||||
|
@ -51,15 +51,12 @@ public class ApkDownloader implements AsyncDownloader.Listener {
|
|||||||
private static final String TAG = "ApkDownloader";
|
private static final String TAG = "ApkDownloader";
|
||||||
|
|
||||||
public static final String EVENT_APK_DOWNLOAD_COMPLETE = "apkDownloadComplete";
|
public static final String EVENT_APK_DOWNLOAD_COMPLETE = "apkDownloadComplete";
|
||||||
public static final String EVENT_APK_DOWNLOAD_CANCELLED = "apkDownloadCancelled";
|
|
||||||
public static final String EVENT_ERROR = "apkDownloadError";
|
public static final String EVENT_ERROR = "apkDownloadError";
|
||||||
|
|
||||||
public static final String ACTION_STATUS = "apkDownloadStatus";
|
public static final String ACTION_STATUS = "apkDownloadStatus";
|
||||||
public static final String EXTRA_TYPE = "apkDownloadStatusType";
|
|
||||||
public static final String EXTRA_URL = "apkDownloadUrl";
|
public static final String EXTRA_URL = "apkDownloadUrl";
|
||||||
|
|
||||||
public static final int ERROR_HASH_MISMATCH = 101;
|
public static final int ERROR_HASH_MISMATCH = 101;
|
||||||
public static final int ERROR_DOWNLOAD_FAILED = 102;
|
|
||||||
|
|
||||||
private static final String EVENT_SOURCE_ID = "sourceId";
|
private static final String EVENT_SOURCE_ID = "sourceId";
|
||||||
private static long downloadIdCounter;
|
private static long downloadIdCounter;
|
||||||
@ -197,7 +194,7 @@ public class ApkDownloader implements AsyncDownloader.Listener {
|
|||||||
Utils.debugLog(TAG, "Downloading apk from " + remoteAddress + " to " + localFile);
|
Utils.debugLog(TAG, "Downloading apk from " + remoteAddress + " to " + localFile);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dlWrapper = DownloaderFactory.createAsync(context, remoteAddress, localFile, app.name + " " + curApk.version, curApk.packageName, credentials, this);
|
dlWrapper = DownloaderFactory.createAsync(context, remoteAddress, localFile, credentials, this);
|
||||||
dlWrapper.download();
|
dlWrapper.download();
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -228,7 +225,6 @@ public class ApkDownloader implements AsyncDownloader.Listener {
|
|||||||
|
|
||||||
Intent intent = new Intent(ACTION_STATUS);
|
Intent intent = new Intent(ACTION_STATUS);
|
||||||
intent.putExtras(event.getData());
|
intent.putExtras(event.getData());
|
||||||
intent.putExtra(EXTRA_TYPE, event.type);
|
|
||||||
intent.putExtra(EXTRA_URL, Utils.getApkUrl(repoAddress, curApk));
|
intent.putExtra(EXTRA_URL, Utils.getApkUrl(repoAddress, curApk));
|
||||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
@ -236,7 +232,6 @@ public class ApkDownloader implements AsyncDownloader.Listener {
|
|||||||
@Override
|
@Override
|
||||||
public void onErrorDownloading(String localisedExceptionDetails) {
|
public void onErrorDownloading(String localisedExceptionDetails) {
|
||||||
Log.e(TAG, "Download failed: " + localisedExceptionDetails);
|
Log.e(TAG, "Download failed: " + localisedExceptionDetails);
|
||||||
sendError(ERROR_DOWNLOAD_FAILED);
|
|
||||||
delete(localFile);
|
delete(localFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,11 +256,6 @@ public class ApkDownloader implements AsyncDownloader.Listener {
|
|||||||
prepareApkFileAndSendCompleteMessage();
|
prepareApkFileAndSendCompleteMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDownloadCancelled() {
|
|
||||||
sendMessage(EVENT_APK_DOWNLOAD_CANCELLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(Event event) {
|
public void onProgress(Event event) {
|
||||||
sendProgressEvent(event);
|
sendProgressEvent(event);
|
||||||
|
@ -12,7 +12,6 @@ class AsyncDownloadWrapper extends Handler implements AsyncDownloader {
|
|||||||
private static final String TAG = "AsyncDownloadWrapper";
|
private static final String TAG = "AsyncDownloadWrapper";
|
||||||
|
|
||||||
private static final int MSG_DOWNLOAD_COMPLETE = 2;
|
private static final int MSG_DOWNLOAD_COMPLETE = 2;
|
||||||
private static final int MSG_DOWNLOAD_CANCELLED = 3;
|
|
||||||
private static final int MSG_ERROR = 4;
|
private static final int MSG_ERROR = 4;
|
||||||
private static final String MSG_DATA = "data";
|
private static final String MSG_DATA = "data";
|
||||||
|
|
||||||
@ -61,9 +60,6 @@ class AsyncDownloadWrapper extends Handler implements AsyncDownloader {
|
|||||||
case MSG_DOWNLOAD_COMPLETE:
|
case MSG_DOWNLOAD_COMPLETE:
|
||||||
listener.onDownloadComplete();
|
listener.onDownloadComplete();
|
||||||
break;
|
break;
|
||||||
case MSG_DOWNLOAD_CANCELLED:
|
|
||||||
listener.onDownloadCancelled();
|
|
||||||
break;
|
|
||||||
case MSG_ERROR:
|
case MSG_ERROR:
|
||||||
listener.onErrorDownloading(message.getData().getString(MSG_DATA));
|
listener.onErrorDownloading(message.getData().getString(MSG_DATA));
|
||||||
break;
|
break;
|
||||||
@ -77,7 +73,7 @@ class AsyncDownloadWrapper extends Handler implements AsyncDownloader {
|
|||||||
downloader.download();
|
downloader.download();
|
||||||
sendMessage(MSG_DOWNLOAD_COMPLETE);
|
sendMessage(MSG_DOWNLOAD_COMPLETE);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
sendMessage(MSG_DOWNLOAD_CANCELLED);
|
// ignored
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "I/O exception in download thread", e);
|
Log.e(TAG, "I/O exception in download thread", e);
|
||||||
Bundle data = new Bundle(1);
|
Bundle data = new Bundle(1);
|
||||||
|
@ -8,8 +8,6 @@ public interface AsyncDownloader {
|
|||||||
void onErrorDownloading(String localisedExceptionDetails);
|
void onErrorDownloading(String localisedExceptionDetails);
|
||||||
|
|
||||||
void onDownloadComplete();
|
void onDownloadComplete();
|
||||||
|
|
||||||
void onDownloadCancelled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int getBytesRead();
|
int getBytesRead();
|
||||||
|
@ -1,396 +0,0 @@
|
|||||||
package org.fdroid.fdroid.net;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileDescriptor;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A downloader that uses Android's DownloadManager to perform a download.
|
|
||||||
*/
|
|
||||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
|
|
||||||
public class AsyncDownloaderFromAndroid implements AsyncDownloader {
|
|
||||||
private final Context context;
|
|
||||||
private final DownloadManager dm;
|
|
||||||
private final LocalBroadcastManager localBroadcastManager;
|
|
||||||
private final File localFile;
|
|
||||||
private final String remoteAddress;
|
|
||||||
private final String downloadTitle;
|
|
||||||
private final String uniqueDownloadId;
|
|
||||||
private final Listener listener;
|
|
||||||
private boolean isCancelled;
|
|
||||||
|
|
||||||
private long downloadManagerId = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normally the listener would be provided using a setListener method.
|
|
||||||
* However for the purposes of this async downloader, it doesn't make
|
|
||||||
* sense to have an async task without any way to notify the outside
|
|
||||||
* world about completion. Therefore, we require the listener as a
|
|
||||||
* parameter to the constructor.
|
|
||||||
*/
|
|
||||||
public AsyncDownloaderFromAndroid(Context context, Listener listener, String downloadTitle, String downloadId, String remoteAddress, File localFile) {
|
|
||||||
this.context = context;
|
|
||||||
this.uniqueDownloadId = downloadId;
|
|
||||||
this.remoteAddress = remoteAddress;
|
|
||||||
this.listener = listener;
|
|
||||||
this.localFile = localFile;
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(downloadTitle)) {
|
|
||||||
this.downloadTitle = remoteAddress;
|
|
||||||
} else {
|
|
||||||
this.downloadTitle = downloadTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
localBroadcastManager = LocalBroadcastManager.getInstance(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void download() {
|
|
||||||
isCancelled = false;
|
|
||||||
|
|
||||||
// check if download failed
|
|
||||||
if (downloadManagerId >= 0) {
|
|
||||||
int status = validDownload(context, downloadManagerId);
|
|
||||||
if (status > 0) {
|
|
||||||
// error downloading
|
|
||||||
dm.remove(downloadManagerId);
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onErrorDownloading(context.getString(R.string.download_error));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the download is complete
|
|
||||||
downloadManagerId = isDownloadComplete(context, uniqueDownloadId);
|
|
||||||
if (downloadManagerId > 0) {
|
|
||||||
// clear the download
|
|
||||||
dm.remove(downloadManagerId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// write the downloaded file to the expected location
|
|
||||||
ParcelFileDescriptor fd = dm.openDownloadedFile(downloadManagerId);
|
|
||||||
copyFile(fd.getFileDescriptor(), localFile);
|
|
||||||
listener.onDownloadComplete();
|
|
||||||
} catch (IOException e) {
|
|
||||||
listener.onErrorDownloading(e.getLocalizedMessage());
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the download is still in progress
|
|
||||||
if (downloadManagerId < 0) {
|
|
||||||
downloadManagerId = isDownloading(context, uniqueDownloadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a new download
|
|
||||||
if (downloadManagerId < 0) {
|
|
||||||
// set up download request
|
|
||||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteAddress));
|
|
||||||
request.setTitle(downloadTitle);
|
|
||||||
request.setDescription(uniqueDownloadId); // we will retrieve this later from the description field
|
|
||||||
this.downloadManagerId = dm.enqueue(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.registerReceiver(receiver,
|
|
||||||
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
|
||||||
|
|
||||||
Thread progressThread = new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (!isCancelled && isDownloading(context, uniqueDownloadId) >= 0) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
sendProgress(getBytesRead(), getTotalBytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
progressThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy input file to output file
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private void copyFile(FileDescriptor inputFile, File outputFile) throws IOException {
|
|
||||||
InputStream input = null;
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
input = new FileInputStream(inputFile);
|
|
||||||
output = new FileOutputStream(outputFile);
|
|
||||||
Utils.copy(input, output);
|
|
||||||
} finally {
|
|
||||||
Utils.closeQuietly(output);
|
|
||||||
Utils.closeQuietly(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBytesRead() {
|
|
||||||
if (downloadManagerId < 0) return 0;
|
|
||||||
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
query.setFilterById(downloadManagerId);
|
|
||||||
Cursor c = dm.query(query);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
// we use the description column to store the unique id of this download
|
|
||||||
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
|
|
||||||
return c.getInt(columnIndex);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTotalBytes() {
|
|
||||||
if (downloadManagerId < 0) return 0;
|
|
||||||
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
query.setFilterById(downloadManagerId);
|
|
||||||
Cursor c = dm.query(query);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
// we use the description column to store the unique id for this download
|
|
||||||
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
|
|
||||||
return c.getInt(columnIndex);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendProgress(int bytesRead, int totalBytes) {
|
|
||||||
Intent intent = new Intent(Downloader.LOCAL_ACTION_PROGRESS);
|
|
||||||
intent.putExtra(Downloader.EXTRA_ADDRESS, remoteAddress);
|
|
||||||
intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead);
|
|
||||||
intent.putExtra(Downloader.EXTRA_TOTAL_BYTES, totalBytes);
|
|
||||||
localBroadcastManager.sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void attemptCancel(boolean userRequested) {
|
|
||||||
isCancelled = true;
|
|
||||||
try {
|
|
||||||
context.unregisterReceiver(receiver);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore if receiver already unregistered
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userRequested && downloadManagerId >= 0) {
|
|
||||||
dm.remove(downloadManagerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the uniqueDownloadId from a given download id.
|
|
||||||
* @return - uniqueDownloadId or null if not found
|
|
||||||
*/
|
|
||||||
public static String getDownloadId(Context context, long downloadId) {
|
|
||||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
query.setFilterById(downloadId);
|
|
||||||
Cursor c = dm.query(query);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
// we use the description column to store the unique id for this download
|
|
||||||
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
|
|
||||||
return c.getString(columnIndex);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the download title from a given download id.
|
|
||||||
* @return - title or null if not found
|
|
||||||
*/
|
|
||||||
public static String getDownloadTitle(Context context, long downloadId) {
|
|
||||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
query.setFilterById(downloadId);
|
|
||||||
Cursor c = dm.query(query);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE);
|
|
||||||
return c.getString(columnIndex);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the downloadManagerId from an Intent sent by the DownloadManagerReceiver
|
|
||||||
*/
|
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
|
||||||
public static long getDownloadId(Intent intent) {
|
|
||||||
if (intent != null) {
|
|
||||||
if (intent.hasExtra(DownloadManager.EXTRA_DOWNLOAD_ID)) {
|
|
||||||
// we have been passed a DownloadManager download id, so get the unique id for that download
|
|
||||||
return intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent.hasExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS)) {
|
|
||||||
// we have been passed multiple download id's - just return the first one
|
|
||||||
long[] downloadIds = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
|
|
||||||
if (downloadIds != null && downloadIds.length > 0) {
|
|
||||||
return downloadIds[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a download is running for the specified id
|
|
||||||
* @return -1 if not downloading, else the id from the Android download manager
|
|
||||||
*/
|
|
||||||
public static long isDownloading(Context context, String uniqueDownloadId) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
|
|
||||||
// TODO: remove. This is necessary because AppDetails calls this
|
|
||||||
// static method directly, without using the whole pipe through
|
|
||||||
// DownloaderFactory. This shouldn't be called at all on android-8
|
|
||||||
// devices, since AppDetails is really using the old downloader,
|
|
||||||
// not this one.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
Cursor c = dm.query(query);
|
|
||||||
if (c == null) {
|
|
||||||
// TODO: same as above.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int columnUniqueDownloadId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
|
|
||||||
int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
if (uniqueDownloadId.equals(c.getString(columnUniqueDownloadId))) {
|
|
||||||
return c.getLong(columnId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a specific download is complete.
|
|
||||||
* @return -1 if download is not complete, otherwise the download id
|
|
||||||
*/
|
|
||||||
private static long isDownloadComplete(Context context, String uniqueDownloadId) {
|
|
||||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
|
|
||||||
Cursor c = dm.query(query);
|
|
||||||
int columnUniqueDownloadId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
|
|
||||||
int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
if (uniqueDownloadId.equals(c.getString(columnUniqueDownloadId))) {
|
|
||||||
return c.getLong(columnId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if download was valid, see issue
|
|
||||||
* http://code.google.com/p/android/issues/detail?id=18462
|
|
||||||
* From http://stackoverflow.com/questions/8937817/downloadmanager-action-download-complete-broadcast-receiver-receiving-same-downl
|
|
||||||
* @return 0 if successful, -1 if download doesn't exist, else the DownloadManager.ERROR_... code
|
|
||||||
*/
|
|
||||||
public static int validDownload(Context context, long downloadId) {
|
|
||||||
//Verify if download is a success
|
|
||||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
Cursor c = dm.query(new DownloadManager.Query().setFilterById(downloadId));
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
|
|
||||||
|
|
||||||
if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
|
||||||
return 0; // Download is valid, celebrate
|
|
||||||
}
|
|
||||||
return c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1; // download doesn't exist
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast receiver to listen for ACTION_DOWNLOAD_COMPLETE broadcasts
|
|
||||||
*/
|
|
||||||
private final BroadcastReceiver receiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
|
|
||||||
long dId = getDownloadId(intent);
|
|
||||||
String downloadId = getDownloadId(context, dId);
|
|
||||||
if (listener != null && dId == AsyncDownloaderFromAndroid.this.downloadManagerId && downloadId != null) {
|
|
||||||
// our current download has just completed, so let's throw up install dialog
|
|
||||||
// immediately
|
|
||||||
try {
|
|
||||||
context.unregisterReceiver(receiver);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore if receiver already unregistered
|
|
||||||
}
|
|
||||||
|
|
||||||
// call download() to copy the file and start the installer
|
|
||||||
download();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -91,20 +91,6 @@ public abstract class Downloader {
|
|||||||
|
|
||||||
protected abstract int totalDownloadSize();
|
protected abstract int totalDownloadSize();
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function for synchronous downloads (i.e. those *not* using AsyncDownloadWrapper),
|
|
||||||
* which don't really want to bother dealing with an InterruptedException.
|
|
||||||
* The InterruptedException thrown from download() is there to enable cancelling asynchronous
|
|
||||||
* downloads, but regular synchronous downloads cannot be cancelled because download() will
|
|
||||||
* block until completed.
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void downloadUninterrupted() throws IOException {
|
|
||||||
try {
|
|
||||||
download();
|
|
||||||
} catch (InterruptedException ignored) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void download() throws IOException, InterruptedException;
|
public abstract void download() throws IOException, InterruptedException;
|
||||||
|
|
||||||
public abstract boolean isCached();
|
public abstract boolean isCached();
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
package org.fdroid.fdroid.net;
|
package org.fdroid.fdroid.net;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
import org.fdroid.fdroid.data.Credentials;
|
import org.fdroid.fdroid.data.Credentials;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -27,29 +22,9 @@ public class DownloaderFactory {
|
|||||||
*/
|
*/
|
||||||
public static Downloader create(Context context, String urlString)
|
public static Downloader create(Context context, String urlString)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return create(context, new URL(urlString));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads to a temporary file, which *you must delete yourself when
|
|
||||||
* you are done. It is stored in {@link Context#getCacheDir()} and starts
|
|
||||||
* with the prefix {@code dl-}.
|
|
||||||
*/
|
|
||||||
public static Downloader create(Context context, URL url)
|
|
||||||
throws IOException {
|
|
||||||
File destFile = File.createTempFile("dl-", "", context.getCacheDir());
|
File destFile = File.createTempFile("dl-", "", context.getCacheDir());
|
||||||
destFile.deleteOnExit(); // this probably does nothing, but maybe...
|
destFile.deleteOnExit(); // this probably does nothing, but maybe...
|
||||||
return create(context, url, destFile);
|
return create(context, new URL(urlString), destFile, null);
|
||||||
}
|
|
||||||
|
|
||||||
public static Downloader create(Context context, String urlString, File destFile)
|
|
||||||
throws IOException {
|
|
||||||
return create(context, new URL(urlString), destFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Downloader create(Context context, URL url, File destFile)
|
|
||||||
throws IOException {
|
|
||||||
return create(context, url, destFile, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Downloader create(Context context, URL url, File destFile, Credentials credentials)
|
public static Downloader create(Context context, URL url, File destFile, Credentials credentials)
|
||||||
@ -89,63 +64,13 @@ public class DownloaderFactory {
|
|||||||
return "file".equalsIgnoreCase(url.getProtocol());
|
return "file".equalsIgnoreCase(url.getProtocol());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AsyncDownloader createAsync(Context context, String urlString, File destFile, String title, String id, Credentials credentials, AsyncDownloader.Listener listener) throws IOException {
|
public static AsyncDownloader createAsync(Context context, String urlString, File destFile, Credentials credentials, AsyncDownloader.Listener listener)
|
||||||
return createAsync(context, new URL(urlString), destFile, title, id, credentials, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AsyncDownloader createAsync(Context context, URL url, File destFile, String title, String id, Credentials credentials, AsyncDownloader.Listener listener)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// To re-enable, fix the following:
|
URL url = new URL(urlString);
|
||||||
// * https://gitlab.com/fdroid/fdroidclient/issues/445
|
|
||||||
// * https://gitlab.com/fdroid/fdroidclient/issues/459
|
|
||||||
if (false && canUseDownloadManager(context, url)) {
|
|
||||||
Utils.debugLog(TAG, "Using AsyncDownloaderFromAndroid");
|
|
||||||
return new AsyncDownloaderFromAndroid(context, listener, title, id, url.toString(), destFile);
|
|
||||||
}
|
|
||||||
Utils.debugLog(TAG, "Using AsyncDownloadWrapper");
|
|
||||||
return new AsyncDownloadWrapper(create(context, url, destFile, credentials), listener);
|
return new AsyncDownloadWrapper(create(context, url, destFile, credentials), listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isOnionAddress(URL url) {
|
private static boolean isOnionAddress(URL url) {
|
||||||
return url.getHost().endsWith(".onion");
|
return url.getHost().endsWith(".onion");
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
|
|
||||||
private static boolean hasDownloadManager(Context context) {
|
|
||||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
if (dm == null) {
|
|
||||||
// Service was not found
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DownloadManager.Query query = new DownloadManager.Query();
|
|
||||||
Cursor c = dm.query(query);
|
|
||||||
if (c == null) {
|
|
||||||
// Download Manager was disabled
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
c.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests to see if we can use Android's DownloadManager to download the APK, instead of
|
|
||||||
* a downloader returned from DownloadFactory.
|
|
||||||
*/
|
|
||||||
private static boolean canUseDownloadManager(Context context, URL url) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
|
||||||
// No HTTPS support on 2.3, no DownloadManager on 2.2. Don't have
|
|
||||||
// 3.0 devices to test on, so require 4.0.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isOnionAddress(url)) {
|
|
||||||
// We support onion addresses through our own downloader.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isBluetoothAddress(url)) {
|
|
||||||
// Completely differnet protocol not understood by the download manager.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return hasDownloadManager(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package org.fdroid.fdroid.receiver;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.support.annotation.StringRes;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.AppDetails;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.net.AsyncDownloaderFromAndroid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive notifications from the Android DownloadManager and pass them onto the
|
|
||||||
* AppDetails activity
|
|
||||||
*/
|
|
||||||
@TargetApi(9)
|
|
||||||
public class DownloadManagerReceiver extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
// work out the package name to send to the AppDetails Screen
|
|
||||||
long downloadId = AsyncDownloaderFromAndroid.getDownloadId(intent);
|
|
||||||
String packageName = AsyncDownloaderFromAndroid.getDownloadId(context, downloadId);
|
|
||||||
|
|
||||||
if (packageName == null) {
|
|
||||||
// bogus broadcast (e.g. download cancelled, but system sent a DOWNLOAD_COMPLETE)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
|
|
||||||
int status = AsyncDownloaderFromAndroid.validDownload(context, downloadId);
|
|
||||||
if (status == 0) {
|
|
||||||
// successful download
|
|
||||||
showNotification(context, packageName, intent, downloadId, R.string.tap_to_install);
|
|
||||||
} else {
|
|
||||||
// download failed!
|
|
||||||
showNotification(context, packageName, intent, downloadId, R.string.download_error);
|
|
||||||
|
|
||||||
// clear the download to allow user to download again
|
|
||||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
||||||
dm.remove(downloadId);
|
|
||||||
}
|
|
||||||
} else if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())) {
|
|
||||||
// pass the notification click onto the AppDetails screen and let it handle it
|
|
||||||
Intent appDetails = new Intent(context, AppDetails.class);
|
|
||||||
appDetails.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
appDetails.setAction(intent.getAction());
|
|
||||||
appDetails.putExtras(intent.getExtras());
|
|
||||||
appDetails.putExtra(AppDetails.EXTRA_APPID, packageName);
|
|
||||||
context.startActivity(appDetails);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showNotification(Context context, String packageName, Intent intent, long downloadId,
|
|
||||||
@StringRes int messageResId) {
|
|
||||||
// show a notification the user can click to install the app
|
|
||||||
Intent appDetails = new Intent(context, AppDetails.class);
|
|
||||||
appDetails.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
appDetails.setAction(intent.getAction());
|
|
||||||
appDetails.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
|
|
||||||
appDetails.putExtra(AppDetails.EXTRA_APPID, packageName);
|
|
||||||
|
|
||||||
// set separate pending intents per download id
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(
|
|
||||||
context, (int) downloadId, appDetails, PendingIntent.FLAG_ONE_SHOT);
|
|
||||||
|
|
||||||
// build & show notification
|
|
||||||
String downloadTitle = AsyncDownloaderFromAndroid.getDownloadTitle(context, downloadId);
|
|
||||||
Notification notif = new NotificationCompat.Builder(context)
|
|
||||||
.setContentTitle(downloadTitle)
|
|
||||||
.setContentText(context.getString(messageResId))
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_notify)
|
|
||||||
.setContentIntent(pi)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
nm.notify((int) downloadId, notif);
|
|
||||||
}
|
|
||||||
}
|
|
@ -279,18 +279,8 @@ public class SwapAppsView extends ListView implements
|
|||||||
// apkToInstall. This way, we can wait until we receive an incoming intent (if
|
// apkToInstall. This way, we can wait until we receive an incoming intent (if
|
||||||
// at all) and then lazily load the apk to install.
|
// at all) and then lazily load the apk to install.
|
||||||
String broadcastUrl = intent.getStringExtra(ApkDownloader.EXTRA_URL);
|
String broadcastUrl = intent.getStringExtra(ApkDownloader.EXTRA_URL);
|
||||||
if (!TextUtils.equals(Utils.getApkUrl(apk.repoAddress, apk), broadcastUrl)) {
|
if (TextUtils.equals(Utils.getApkUrl(apk.repoAddress, apk), broadcastUrl)) {
|
||||||
return;
|
resetView();
|
||||||
}
|
|
||||||
|
|
||||||
switch (intent.getStringExtra(ApkDownloader.EXTRA_TYPE)) {
|
|
||||||
// Fallthrough for each of these "downloader no longer going" events...
|
|
||||||
case ApkDownloader.EVENT_APK_DOWNLOAD_COMPLETE:
|
|
||||||
case ApkDownloader.EVENT_APK_DOWNLOAD_CANCELLED:
|
|
||||||
case ApkDownloader.EVENT_ERROR:
|
|
||||||
case ApkDownloader.EVENT_DATA_ERROR_TYPE:
|
|
||||||
resetView();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -789,8 +789,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
case ApkDownloader.EVENT_APK_DOWNLOAD_COMPLETE:
|
case ApkDownloader.EVENT_APK_DOWNLOAD_COMPLETE:
|
||||||
handleDownloadComplete(downloader.localFile(), app.packageName);
|
handleDownloadComplete(downloader.localFile(), app.packageName);
|
||||||
break;
|
break;
|
||||||
case ApkDownloader.EVENT_ERROR:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user