package com.novoda.downloadmanager.lib;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/**
* This class may be used to filter download manager queries.
*/
public class Query {
@Retention(RetentionPolicy.SOURCE)
@IntDef({ORDER_ASCENDING, ORDER_DESCENDING})
public @interface Order {
}
/**
* Constant for use with {@link #orderBy}
*/
public static final int ORDER_ASCENDING = 1;
/**
* Constant for use with {@link #orderBy}
*/
public static final int ORDER_DESCENDING = 2;
private static final String ORDER_BY_LIVENESS = String.format(Locale.US,
"CASE %1$s "
+ "WHEN %2$d THEN 1 "
+ "WHEN %3$d THEN 2 "
+ "WHEN %4$d THEN 3 "
+ "WHEN %5$d THEN 4 "
+ "WHEN %6$d THEN 5 "
+ "ELSE 6 "
+ "END, _id ASC",
DownloadContract.Downloads.COLUMN_STATUS,
DownloadStatus.RUNNING,
DownloadStatus.PENDING,
DownloadStatus.PAUSED_BY_APP,
DownloadStatus.BATCH_FAILED,
DownloadStatus.SUCCESS
);
private long[] downloadIds = null;
private long[] batchIds = null;
private Integer statusFlags = null;
private boolean onlyIncludeVisibleInDownloadsUi = false;
private String[] filterNotificiationExtras;
private String[] filterExtraData;
private String orderString = DownloadContract.Downloads.COLUMN_LAST_MODIFICATION + " DESC";
/**
* Include only the downloads with the given IDs.
*
* @return this object
*/
public Query setFilterById(long... downloadIds) {
this.downloadIds = downloadIds;
return this;
}
/**
* Include only the downloads with the given batch IDs.
*
* @return this object
*/
public Query setFilterByBatchId(long... batchIds) {
this.batchIds = batchIds;
return this;
}
/**
* Include only the downloads with the given notification extras.
*
* @return this object
*/
public Query setFilterByNotificationExtras(String... extras) {
filterNotificiationExtras = extras;
return this;
}
/**
* Include only the downloads with the given extra data.
*
* @return this object
*/
public Query setFilterByExtraData(String... extraData) {
filterExtraData = extraData;
return this;
}
/**
* Include only downloads with status matching any the given status flags.
*
* @param flags any combination of the STATUS_* bit flags
* @return this object
*/
public Query setFilterByStatus(int flags) {
statusFlags = flags;
return this;
}
/**
* Controls whether this query includes downloads not visible in the system's Downloads UI.
*
* @param value if true, this query will only include downloads that should be displayed in
* the system's Downloads UI; if false (the default), this query will include
* both visible and invisible downloads.
* @return this object
*/
public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
onlyIncludeVisibleInDownloadsUi = value;
return this;
}
/**
* Change the sort order of the returned Cursor.
*
* @param column one of the COLUMN_* constants; currently, only
* {DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP} and {DownloadManager.COLUMN_TOTAL_SIZE_BYTES} are
* supported.
* @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
* @return this object
*/
public Query orderBy(String column, @Order int direction) {
if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
throw new IllegalArgumentException("Invalid direction: " + direction);
}
String resolvedOrderColumn;
switch (column) {
case DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP:
resolvedOrderColumn = DownloadContract.Downloads.COLUMN_LAST_MODIFICATION;
break;
case DownloadManager.COLUMN_TOTAL_SIZE_BYTES:
resolvedOrderColumn = DownloadContract.Downloads.COLUMN_TOTAL_BYTES;
break;
default:
throw new IllegalArgumentException("Cannot order by " + column);
}
String orderDirection = (direction == ORDER_ASCENDING ? "ASC" : "DESC");
orderString = resolvedOrderColumn + " " + orderDirection;
return this;
}
/**
* Sorts downloads according to the 'liveness' of the download, i.e. in the order:
* Downloading, queued, other, paused, failed, completed
*
* @return this {@link Query}
*/
public Query orderByLiveness() {
orderString = ORDER_BY_LIVENESS;
return this;
}
/**
* Run this query using the given ContentResolver.
*
* @param projection the projection to pass to ContentResolver.query()
* @return the Cursor returned by ContentResolver.query()
*/
Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
List<String> selectionParts = new ArrayList<>();
String[] selectionArgs = getIdsAsStringArray(downloadIds);
filterByDownloadIds(selectionParts);
filterByBatchIds(selectionParts);
filterByNotificationExtras(selectionParts);
filterByExtraData(selectionParts);
filterByStatus(selectionParts);
if (onlyIncludeVisibleInDownloadsUi) {
selectionParts.add(DownloadContract.Downloads.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
}
// only return rows which are not marked 'deleted = 1'
selectionParts.add(DownloadContract.Downloads.COLUMN_DELETED + " != '1'");
String selection = joinStrings(" AND ", selectionParts);
return resolver.query(baseUri, projection, selection, selectionArgs, orderString);
}
private String[] getIdsAsStringArray(long[] ids) {
if (ids == null) {
return null;
}
return longArrayToStringArray(ids);
}
private void filterByDownloadIds(List<String> selectionParts) {
if (downloadIds == null) {
return;
}
selectionParts.add(DownloadManager.getWhereClauseFor(downloadIds, DownloadContract.Downloads._ID));
}
private void filterByBatchIds(List<String> selectionParts) {
if (batchIds == null || batchIds.length == 0) {
return;
}
selectionParts.add(getWhereClauseForBatchIds(batchIds));
}
/**
* Get a SQL WHERE clause to select a bunch of IDs.
*/
private String getWhereClauseForBatchIds(long[] ids) {
String[] idStrings = longArrayToStringArray(ids);
return DownloadContract.Downloads.COLUMN_BATCH_ID + " IN (" + joinStrings(",", Arrays.asList(idStrings)) + ")";
}
private void filterByNotificationExtras(List<String> selectionParts) {
if (filterNotificiationExtras == null || filterNotificiationExtras.length == 0) {
return;
}
List<String> parts = new ArrayList<>();
for (String filterExtra : filterNotificiationExtras) {
parts.add(notificationExtrasClause(filterExtra));
}
selectionParts.add("(" + joinStrings(" OR ", parts) + ")");
}
private void filterByExtraData(List<String> selectionParts) {
if (filterExtraData == null) {
return;
}
List<String> parts = new ArrayList<>();
for (String filterExtra : filterExtraData) {
parts.add(extraDataClause(filterExtra));
}
selectionParts.add(joinStrings(" OR ", parts));
}
private void filterByStatus(List<String> selectionParts) {
if (statusFlags == null) {
return;
}
List<String> parts = new ArrayList<>();
if ((statusFlags & DownloadManager.STATUS_PENDING) != 0) {
parts.add(statusClause("=", DownloadStatus.PENDING));
}
if ((statusFlags & DownloadManager.STATUS_RUNNING) != 0) {
parts.add(statusClause("=", DownloadStatus.RUNNING));
}
if ((statusFlags & DownloadManager.STATUS_PAUSED) != 0) {
parts.add(statusClause("=", DownloadStatus.PAUSED_BY_APP));
parts.add(statusClause("=", DownloadStatus.WAITING_TO_RETRY));
parts.add(statusClause("=", DownloadStatus.WAITING_FOR_NETWORK));
parts.add(statusClause("=", DownloadStatus.QUEUED_FOR_WIFI));
}
if ((statusFlags & DownloadManager.STATUS_SUCCESSFUL) != 0) {
parts.add(statusClause("=", DownloadStatus.SUCCESS));
}
if ((statusFlags & DownloadManager.STATUS_DELETING) != 0) {
parts.add(statusClause("=", DownloadStatus.DELETING));
}
if ((statusFlags & DownloadManager.STATUS_FAILED) != 0) {
parts.add("(" + statusClause(">=", 400) + " AND " + statusClause("<", 600) + ")");
}
selectionParts.add(joinStrings(" OR ", parts));
}
// copied from AOSP for testability
private static String joinStrings(String joiner, Iterable<String> parts) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (String part : parts) {
if (!first) {
builder.append(joiner);
}
builder.append(part);
first = false;
}
return builder.toString();
}
private static String[] longArrayToStringArray(long[] longs) {
String[] strings = new String[longs.length];
for (int i = 0; i < longs.length; i++) {
strings[i] = Long.toString(longs[i]);
}
return strings;
}
private String notificationExtrasClause(String extra) {
return DownloadContract.Downloads.COLUMN_NOTIFICATION_EXTRAS + " = '" + extra + "'";
}
private String extraDataClause(String extra) {
return DownloadContract.Downloads.COLUMN_EXTRA_DATA + " = '" + extra + "'";
}
private String statusClause(String operator, int value) {
return DownloadContract.Downloads.COLUMN_STATUS + operator + "'" + value + "'";
}
}