Android: Resync Google expansion.downloader library with upstream, unmodified

Synced with 9ecf54e5ce.
This commit is contained in:
Rémi Verschelde 2019-08-27 14:07:17 +02:00
parent ce60217894
commit ee5898f58a
17 changed files with 3127 additions and 3048 deletions

View file

@ -3,7 +3,17 @@
This file list third-party libraries used in the Android source folder, This file list third-party libraries used in the Android source folder,
with their provenance and, when relevant, modifications made to those files. with their provenance and, when relevant, modifications made to those files.
## Google's licensing library ## com.google.android.vending.expansion.downloader
- Upstream: https://github.com/google/play-apk-expansion/tree/master/apkx_library
- Version: git (9ecf54e, 2017)
- License: Apache 2.0
Overwrite all files under:
- `src/com/google/android/vending/expansion/downloader`
## com.google.android.vending.licensing
- Upstream: https://github.com/google/play-licensing/tree/master/lvl_library/ - Upstream: https://github.com/google/play-licensing/tree/master/lvl_library/
- Version: git (eb57657, 2018) with modifications - Version: git (eb57657, 2018) with modifications

View file

@ -18,113 +18,115 @@ package com.google.android.vending.expansion.downloader;
import java.io.File; import java.io.File;
/** /**
* Contains the internal constants that are used in the download manager. * Contains the internal constants that are used in the download manager.
* As a general rule, modifying these constants should be done with care. * As a general rule, modifying these constants should be done with care.
*/ */
public class Constants { public class Constants {
/** Tag used for debugging/logging */ /** Tag used for debugging/logging */
public static final String TAG = "LVLDL"; public static final String TAG = "LVLDL";
/** /**
* Expansion path where we store obb files * Expansion path where we store obb files
*/ */
public static final String EXP_PATH = File.separator + "Android" + File.separator + "obb" + File.separator; public static final String EXP_PATH = File.separator + "Android"
+ File.separator + "obb" + File.separator;
/** The intent that gets sent when the service must wake up for a retry */ /** The intent that gets sent when the service must wake up for a retry */
public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP";
/** the intent that gets sent when clicking a successful download */ /** the intent that gets sent when clicking a successful download */
public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN";
/** the intent that gets sent when clicking an incomplete/failed download */ /** the intent that gets sent when clicking an incomplete/failed download */
public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST";
/** the intent that gets sent when deleting the notification of a completed download */ /** the intent that gets sent when deleting the notification of a completed download */
public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE";
/** /**
* When a number has to be appended to the filename, this string is used to separate the * When a number has to be appended to the filename, this string is used to separate the
* base filename from the sequence number * base filename from the sequence number
*/ */
public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; public static final String FILENAME_SEQUENCE_SEPARATOR = "-";
/** The default user agent used for downloads */ /** The default user agent used for downloads */
public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; public static final String DEFAULT_USER_AGENT = "Android.LVLDM";
/** The buffer size used to stream the data */ /** The buffer size used to stream the data */
public static final int BUFFER_SIZE = 4096; public static final int BUFFER_SIZE = 4096;
/** The minimum amount of progress that has to be done before the progress bar gets updated */ /** The minimum amount of progress that has to be done before the progress bar gets updated */
public static final int MIN_PROGRESS_STEP = 4096; public static final int MIN_PROGRESS_STEP = 4096;
/** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */ /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */
public static final long MIN_PROGRESS_TIME = 1000; public static final long MIN_PROGRESS_TIME = 1000;
/** The maximum number of rows in the database (FIFO) */ /** The maximum number of rows in the database (FIFO) */
public static final int MAX_DOWNLOADS = 1000; public static final int MAX_DOWNLOADS = 1000;
/** /**
* The number of times that the download manager will retry its network * The number of times that the download manager will retry its network
* operations when no progress is happening before it gives up. * operations when no progress is happening before it gives up.
*/ */
public static final int MAX_RETRIES = 5; public static final int MAX_RETRIES = 5;
/** /**
* The minimum amount of time that the download manager accepts for * The minimum amount of time that the download manager accepts for
* a Retry-After response header with a parameter in delta-seconds. * a Retry-After response header with a parameter in delta-seconds.
*/ */
public static final int MIN_RETRY_AFTER = 30; // 30s public static final int MIN_RETRY_AFTER = 30; // 30s
/** /**
* The maximum amount of time that the download manager accepts for * The maximum amount of time that the download manager accepts for
* a Retry-After response header with a parameter in delta-seconds. * a Retry-After response header with a parameter in delta-seconds.
*/ */
public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h
/** /**
* The maximum number of redirects. * The maximum number of redirects.
*/ */
public static final int MAX_REDIRECTS = 5; // can't be more than 7. public static final int MAX_REDIRECTS = 5; // can't be more than 7.
/** /**
* The time between a failure and the first retry after an IOException. * The time between a failure and the first retry after an IOException.
* Each subsequent retry grows exponentially, doubling each time. * Each subsequent retry grows exponentially, doubling each time.
* The time is in seconds. * The time is in seconds.
*/ */
public static final int RETRY_FIRST_DELAY = 30; public static final int RETRY_FIRST_DELAY = 30;
/** Enable separate connectivity logging */ /** Enable separate connectivity logging */
public static final boolean LOGX = true; public static final boolean LOGX = true;
/** Enable verbose logging */ /** Enable verbose logging */
public static final boolean LOGV = false; public static final boolean LOGV = false;
/** Enable super-verbose logging */ /** Enable super-verbose logging */
private static final boolean LOCAL_LOGVV = false; private static final boolean LOCAL_LOGVV = false;
public static final boolean LOGVV = LOCAL_LOGVV && LOGV; public static final boolean LOGVV = LOCAL_LOGVV && LOGV;
/** /**
* This download has successfully completed. * This download has successfully completed.
* Warning: there might be other status values that indicate success * Warning: there might be other status values that indicate success
* in the future. * in the future.
* Use isSucccess() to capture the entire category. * Use isSucccess() to capture the entire category.
*/ */
public static final int STATUS_SUCCESS = 200; public static final int STATUS_SUCCESS = 200;
/** /**
* This request couldn't be parsed. This is also used when processing * This request couldn't be parsed. This is also used when processing
* requests with unknown/unsupported URI schemes. * requests with unknown/unsupported URI schemes.
*/ */
public static final int STATUS_BAD_REQUEST = 400; public static final int STATUS_BAD_REQUEST = 400;
/** /**
* This download can't be performed because the content type cannot be * This download can't be performed because the content type cannot be
* handled. * handled.
*/ */
public static final int STATUS_NOT_ACCEPTABLE = 406; public static final int STATUS_NOT_ACCEPTABLE = 406;
/** /**
* This download cannot be performed because the length cannot be * This download cannot be performed because the length cannot be
* determined accurately. This is the code for the HTTP error "Length * determined accurately. This is the code for the HTTP error "Length
* Required", which is typically used when making requests that require * Required", which is typically used when making requests that require
@ -133,101 +135,102 @@ public class Constants {
* accurately (therefore making it impossible to know when a download * accurately (therefore making it impossible to know when a download
* completes). * completes).
*/ */
public static final int STATUS_LENGTH_REQUIRED = 411; public static final int STATUS_LENGTH_REQUIRED = 411;
/** /**
* This download was interrupted and cannot be resumed. * This download was interrupted and cannot be resumed.
* This is the code for the HTTP error "Precondition Failed", and it is * This is the code for the HTTP error "Precondition Failed", and it is
* also used in situations where the client doesn't have an ETag at all. * also used in situations where the client doesn't have an ETag at all.
*/ */
public static final int STATUS_PRECONDITION_FAILED = 412; public static final int STATUS_PRECONDITION_FAILED = 412;
/** /**
* The lowest-valued error status that is not an actual HTTP status code. * The lowest-valued error status that is not an actual HTTP status code.
*/ */
public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;
/** /**
* The requested destination file already exists. * The requested destination file already exists.
*/ */
public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;
/** /**
* Some possibly transient error occurred, but we can't resume the download. * Some possibly transient error occurred, but we can't resume the download.
*/ */
public static final int STATUS_CANNOT_RESUME = 489; public static final int STATUS_CANNOT_RESUME = 489;
/** /**
* This download was canceled * This download was canceled
*/ */
public static final int STATUS_CANCELED = 490; public static final int STATUS_CANCELED = 490;
/** /**
* This download has completed with an error. * This download has completed with an error.
* Warning: there will be other status values that indicate errors in * Warning: there will be other status values that indicate errors in
* the future. Use isStatusError() to capture the entire category. * the future. Use isStatusError() to capture the entire category.
*/ */
public static final int STATUS_UNKNOWN_ERROR = 491; public static final int STATUS_UNKNOWN_ERROR = 491;
/** /**
* This download couldn't be completed because of a storage issue. * This download couldn't be completed because of a storage issue.
* Typically, that's because the filesystem is missing or full. * Typically, that's because the filesystem is missing or full.
* Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
* and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
*/ */
public static final int STATUS_FILE_ERROR = 492; public static final int STATUS_FILE_ERROR = 492;
/** /**
* This download couldn't be completed because of an HTTP * This download couldn't be completed because of an HTTP
* redirect response that the download manager couldn't * redirect response that the download manager couldn't
* handle. * handle.
*/ */
public static final int STATUS_UNHANDLED_REDIRECT = 493; public static final int STATUS_UNHANDLED_REDIRECT = 493;
/** /**
* This download couldn't be completed because of an * This download couldn't be completed because of an
* unspecified unhandled HTTP code. * unspecified unhandled HTTP code.
*/ */
public static final int STATUS_UNHANDLED_HTTP_CODE = 494; public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
/** /**
* This download couldn't be completed because of an * This download couldn't be completed because of an
* error receiving or processing data at the HTTP level. * error receiving or processing data at the HTTP level.
*/ */
public static final int STATUS_HTTP_DATA_ERROR = 495; public static final int STATUS_HTTP_DATA_ERROR = 495;
/** /**
* This download couldn't be completed because of an * This download couldn't be completed because of an
* HttpException while setting up the request. * HttpException while setting up the request.
*/ */
public static final int STATUS_HTTP_EXCEPTION = 496; public static final int STATUS_HTTP_EXCEPTION = 496;
/** /**
* This download couldn't be completed because there were * This download couldn't be completed because there were
* too many redirects. * too many redirects.
*/ */
public static final int STATUS_TOO_MANY_REDIRECTS = 497; public static final int STATUS_TOO_MANY_REDIRECTS = 497;
/** /**
* This download couldn't be completed due to insufficient storage * This download couldn't be completed due to insufficient storage
* space. Typically, this is because the SD card is full. * space. Typically, this is because the SD card is full.
*/ */
public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
/** /**
* This download couldn't be completed because no external storage * This download couldn't be completed because no external storage
* device was found. Typically, this is because the SD card is not * device was found. Typically, this is because the SD card is not
* mounted. * mounted.
*/ */
public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
/** /**
* The wake duration to check to see if a download is possible. * The wake duration to check to see if a download is possible.
*/ */
public static final long WATCHDOG_WAKE_TIMER = 60 * 1000; public static final long WATCHDOG_WAKE_TIMER = 60*1000;
/** /**
* The wake duration to check to see if the process was killed. * The wake duration to check to see if the process was killed.
*/ */
public static final long ACTIVE_THREAD_WATCHDOG = 5 * 1000; public static final long ACTIVE_THREAD_WATCHDOG = 5*1000;
} }

View file

@ -19,6 +19,7 @@ package com.google.android.vending.expansion.downloader;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
/** /**
* This class contains progress information about the active download(s). * This class contains progress information about the active download(s).
* *
@ -30,49 +31,50 @@ import android.os.Parcelable;
* as the progress so far, time remaining and current speed. * as the progress so far, time remaining and current speed.
*/ */
public class DownloadProgressInfo implements Parcelable { public class DownloadProgressInfo implements Parcelable {
public long mOverallTotal; public long mOverallTotal;
public long mOverallProgress; public long mOverallProgress;
public long mTimeRemaining; // time remaining public long mTimeRemaining; // time remaining
public float mCurrentSpeed; // speed in KB/S public float mCurrentSpeed; // speed in KB/S
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
} }
@Override @Override
public void writeToParcel(Parcel p, int i) { public void writeToParcel(Parcel p, int i) {
p.writeLong(mOverallTotal); p.writeLong(mOverallTotal);
p.writeLong(mOverallProgress); p.writeLong(mOverallProgress);
p.writeLong(mTimeRemaining); p.writeLong(mTimeRemaining);
p.writeFloat(mCurrentSpeed); p.writeFloat(mCurrentSpeed);
} }
public DownloadProgressInfo(Parcel p) { public DownloadProgressInfo(Parcel p) {
mOverallTotal = p.readLong(); mOverallTotal = p.readLong();
mOverallProgress = p.readLong(); mOverallProgress = p.readLong();
mTimeRemaining = p.readLong(); mTimeRemaining = p.readLong();
mCurrentSpeed = p.readFloat(); mCurrentSpeed = p.readFloat();
} }
public DownloadProgressInfo(long overallTotal, long overallProgress, public DownloadProgressInfo(long overallTotal, long overallProgress,
long timeRemaining, long timeRemaining,
float currentSpeed) { float currentSpeed) {
this.mOverallTotal = overallTotal; this.mOverallTotal = overallTotal;
this.mOverallProgress = overallProgress; this.mOverallProgress = overallProgress;
this.mTimeRemaining = timeRemaining; this.mTimeRemaining = timeRemaining;
this.mCurrentSpeed = currentSpeed; this.mCurrentSpeed = currentSpeed;
} }
public static final Creator<DownloadProgressInfo> CREATOR = new Creator<DownloadProgressInfo>() { public static final Creator<DownloadProgressInfo> CREATOR = new Creator<DownloadProgressInfo>() {
@Override @Override
public DownloadProgressInfo createFromParcel(Parcel parcel) { public DownloadProgressInfo createFromParcel(Parcel parcel) {
return new DownloadProgressInfo(parcel); return new DownloadProgressInfo(parcel);
} }
@Override
public DownloadProgressInfo[] newArray(int i) {
return new DownloadProgressInfo[i];
}
};
@Override
public DownloadProgressInfo[] newArray(int i) {
return new DownloadProgressInfo[i];
}
};
} }

View file

@ -32,7 +32,7 @@ import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import java.lang.ref.WeakReference;
/** /**
* This class binds the service API to your application client. It contains the IDownloaderClient proxy, * This class binds the service API to your application client. It contains the IDownloaderClient proxy,
@ -58,172 +58,158 @@ import java.lang.ref.WeakReference;
* interface. * interface.
*/ */
public class DownloaderClientMarshaller { public class DownloaderClientMarshaller {
public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10; public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10;
public static final int MSG_ONDOWNLOADPROGRESS = 11; public static final int MSG_ONDOWNLOADPROGRESS = 11;
public static final int MSG_ONSERVICECONNECTED = 12; public static final int MSG_ONSERVICECONNECTED = 12;
public static final String PARAM_NEW_STATE = "newState"; public static final String PARAM_NEW_STATE = "newState";
public static final String PARAM_PROGRESS = "progress"; public static final String PARAM_PROGRESS = "progress";
public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;
public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED; public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED;
public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED; public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED;
public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED; public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED;
private static class Proxy implements IDownloaderClient { private static class Proxy implements IDownloaderClient {
private Messenger mServiceMessenger; private Messenger mServiceMessenger;
@Override @Override
public void onDownloadStateChanged(int newState) { public void onDownloadStateChanged(int newState) {
Bundle params = new Bundle(1); Bundle params = new Bundle(1);
params.putInt(PARAM_NEW_STATE, newState); params.putInt(PARAM_NEW_STATE, newState);
send(MSG_ONDOWNLOADSTATE_CHANGED, params); send(MSG_ONDOWNLOADSTATE_CHANGED, params);
} }
@Override @Override
public void onDownloadProgress(DownloadProgressInfo progress) { public void onDownloadProgress(DownloadProgressInfo progress) {
Bundle params = new Bundle(1); Bundle params = new Bundle(1);
params.putParcelable(PARAM_PROGRESS, progress); params.putParcelable(PARAM_PROGRESS, progress);
send(MSG_ONDOWNLOADPROGRESS, params); send(MSG_ONDOWNLOADPROGRESS, params);
} }
private void send(int method, Bundle params) { private void send(int method, Bundle params) {
Message m = Message.obtain(null, method); Message m = Message.obtain(null, method);
m.setData(params); m.setData(params);
try { try {
mServiceMessenger.send(m); mServiceMessenger.send(m);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public Proxy(Messenger msg) { public Proxy(Messenger msg) {
mServiceMessenger = msg; mServiceMessenger = msg;
} }
@Override @Override
public void onServiceConnected(Messenger m) { public void onServiceConnected(Messenger m) {
/** /**
* This is never called through the proxy. * This is never called through the proxy.
*/ */
} }
} }
private static class Stub implements IStub { private static class Stub implements IStub {
private IDownloaderClient mItf = null; private IDownloaderClient mItf = null;
private Class<?> mDownloaderServiceClass; private Class<?> mDownloaderServiceClass;
private boolean mBound; private boolean mBound;
private Messenger mServiceMessenger; private Messenger mServiceMessenger;
private Context mContext; private Context mContext;
/** /**
* Target we publish for clients to send messages to IncomingHandler. * Target we publish for clients to send messages to IncomingHandler.
*/ */
private final MessengerHandlerClient mMsgHandler = new MessengerHandlerClient(this); final Messenger mMessenger = new Messenger(new Handler() {
final Messenger mMessenger = new Messenger(mMsgHandler); @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ONDOWNLOADPROGRESS:
Bundle bun = msg.getData();
if ( null != mContext ) {
bun.setClassLoader(mContext.getClassLoader());
DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData()
.getParcelable(PARAM_PROGRESS);
mItf.onDownloadProgress(dpi);
}
break;
case MSG_ONDOWNLOADSTATE_CHANGED:
mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
break;
case MSG_ONSERVICECONNECTED:
mItf.onServiceConnected(
(Messenger) msg.getData().getParcelable(PARAM_MESSENGER));
break;
}
}
});
private static class MessengerHandlerClient extends Handler { public Stub(IDownloaderClient itf, Class<?> downloaderService) {
private final WeakReference<Stub> mDownloader; mItf = itf;
public MessengerHandlerClient(Stub downloader) { mDownloaderServiceClass = downloaderService;
mDownloader = new WeakReference<>(downloader); }
}
@Override /**
public void handleMessage(Message msg) {
Stub downloader = mDownloader.get();
if (downloader != null) {
downloader.handleMessage(msg);
}
}
}
private void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ONDOWNLOADPROGRESS:
Bundle bun = msg.getData();
if (null != mContext) {
bun.setClassLoader(mContext.getClassLoader());
DownloadProgressInfo dpi = (DownloadProgressInfo)msg.getData()
.getParcelable(PARAM_PROGRESS);
mItf.onDownloadProgress(dpi);
}
break;
case MSG_ONDOWNLOADSTATE_CHANGED:
mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
break;
case MSG_ONSERVICECONNECTED:
mItf.onServiceConnected(
(Messenger)msg.getData().getParcelable(PARAM_MESSENGER));
break;
}
}
public Stub(IDownloaderClient itf, Class<?> downloaderService) {
mItf = itf;
mDownloaderServiceClass = downloaderService;
}
/**
* Class for interacting with the main interface of the service. * Class for interacting with the main interface of the service.
*/ */
private ServiceConnection mConnection = new ServiceConnection() { private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been // This is called when the connection with the service has been
// established, giving us the object we can use to // established, giving us the object we can use to
// interact with the service. We are communicating with the // interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side // service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object. // representation of that from the raw IBinder object.
mServiceMessenger = new Messenger(service); mServiceMessenger = new Messenger(service);
mItf.onServiceConnected( mItf.onServiceConnected(
mServiceMessenger); mServiceMessenger);
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been // This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed. // unexpectedly disconnected -- that is, its process crashed.
mServiceMessenger = null; mServiceMessenger = null;
} }
}; };
@Override @Override
public void connect(Context c) { public void connect(Context c) {
mContext = c; mContext = c;
Intent bindIntent = new Intent(c, mDownloaderServiceClass); Intent bindIntent = new Intent(c, mDownloaderServiceClass);
bindIntent.putExtra(PARAM_MESSENGER, mMessenger); bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
if (!c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND)) { if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
if (Constants.LOGVV) { if ( Constants.LOGVV ) {
Log.d(Constants.TAG, "Service Unbound"); Log.d(Constants.TAG, "Service Unbound");
} }
} else { } else {
mBound = true; mBound = true;
} }
}
@Override }
public void disconnect(Context c) {
if (mBound) {
c.unbindService(mConnection);
mBound = false;
}
mContext = null;
}
@Override @Override
public Messenger getMessenger() { public void disconnect(Context c) {
return mMessenger; if (mBound) {
} c.unbindService(mConnection);
} mBound = false;
}
mContext = null;
}
/** @Override
public Messenger getMessenger() {
return mMessenger;
}
}
/**
* Returns a proxy that will marshal calls to IDownloaderClient methods * Returns a proxy that will marshal calls to IDownloaderClient methods
* *
* @param msg * @param msg
* @return * @return
*/ */
public static IDownloaderClient CreateProxy(Messenger msg) { public static IDownloaderClient CreateProxy(Messenger msg) {
return new Proxy(msg); return new Proxy(msg);
} }
/** /**
* Returns a stub object that, when connected, will listen for marshaled * Returns a stub object that, when connected, will listen for marshaled
* {@link IDownloaderClient} methods and translate them into calls to the supplied * {@link IDownloaderClient} methods and translate them into calls to the supplied
* interface. * interface.
@ -235,11 +221,11 @@ public class DownloaderClientMarshaller {
* @return The {@link IStub} that allows you to connect to the service such that * @return The {@link IStub} that allows you to connect to the service such that
* your {@link IDownloaderClient} receives status updates. * your {@link IDownloaderClient} receives status updates.
*/ */
public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) { public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) {
return new Stub(itf, downloaderService); return new Stub(itf, downloaderService);
} }
/** /**
* Starts the download if necessary. This function starts a flow that does ` * Starts the download if necessary. This function starts a flow that does `
* many things. 1) Checks to see if the APK version has been checked and * many things. 1) Checks to see if the APK version has been checked and
* the metadata database updated 2) If the APK version does not match, * the metadata database updated 2) If the APK version does not match,
@ -262,14 +248,14 @@ public class DownloaderClientMarshaller {
* #DOWNLOAD_REQUIRED}. * #DOWNLOAD_REQUIRED}.
* @throws NameNotFoundException * @throws NameNotFoundException
*/ */
public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient,
Class<?> serviceClass) Class<?> serviceClass)
throws NameNotFoundException { throws NameNotFoundException {
return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
serviceClass); serviceClass);
} }
/** /**
* This version assumes that the intent contains the pending intent as a parameter. This * This version assumes that the intent contains the pending intent as a parameter. This
* is used for responding to alarms. * is used for responding to alarms.
* <p>The pending intent must be in an extra with the key {@link * <p>The pending intent must be in an extra with the key {@link
@ -281,10 +267,11 @@ public class DownloaderClientMarshaller {
* @return * @return
* @throws NameNotFoundException * @throws NameNotFoundException
*/ */
public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, public static int startDownloadServiceIfRequired(Context context, Intent notificationClient,
Class<?> serviceClass) Class<?> serviceClass)
throws NameNotFoundException { throws NameNotFoundException {
return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
serviceClass); serviceClass);
} }
} }

View file

@ -25,7 +25,7 @@ import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import java.lang.ref.WeakReference;
/** /**
* This class is used by the client activity to proxy requests to the Downloader * This class is used by the client activity to proxy requests to the Downloader
@ -38,147 +38,134 @@ import java.lang.ref.WeakReference;
*/ */
public class DownloaderServiceMarshaller { public class DownloaderServiceMarshaller {
public static final int MSG_REQUEST_ABORT_DOWNLOAD = public static final int MSG_REQUEST_ABORT_DOWNLOAD =
1; 1;
public static final int MSG_REQUEST_PAUSE_DOWNLOAD = public static final int MSG_REQUEST_PAUSE_DOWNLOAD =
2; 2;
public static final int MSG_SET_DOWNLOAD_FLAGS = public static final int MSG_SET_DOWNLOAD_FLAGS =
3; 3;
public static final int MSG_REQUEST_CONTINUE_DOWNLOAD = public static final int MSG_REQUEST_CONTINUE_DOWNLOAD =
4; 4;
public static final int MSG_REQUEST_DOWNLOAD_STATE = public static final int MSG_REQUEST_DOWNLOAD_STATE =
5; 5;
public static final int MSG_REQUEST_CLIENT_UPDATE = public static final int MSG_REQUEST_CLIENT_UPDATE =
6; 6;
public static final String PARAMS_FLAGS = "flags"; public static final String PARAMS_FLAGS = "flags";
public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;
private static class Proxy implements IDownloaderService { private static class Proxy implements IDownloaderService {
private Messenger mMsg; private Messenger mMsg;
private void send(int method, Bundle params) { private void send(int method, Bundle params) {
Message m = Message.obtain(null, method); Message m = Message.obtain(null, method);
m.setData(params); m.setData(params);
try { try {
mMsg.send(m); mMsg.send(m);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public Proxy(Messenger msg) { public Proxy(Messenger msg) {
mMsg = msg; mMsg = msg;
} }
@Override @Override
public void requestAbortDownload() { public void requestAbortDownload() {
send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle()); send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle());
} }
@Override @Override
public void requestPauseDownload() { public void requestPauseDownload() {
send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle()); send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle());
} }
@Override @Override
public void setDownloadFlags(int flags) { public void setDownloadFlags(int flags) {
Bundle params = new Bundle(); Bundle params = new Bundle();
params.putInt(PARAMS_FLAGS, flags); params.putInt(PARAMS_FLAGS, flags);
send(MSG_SET_DOWNLOAD_FLAGS, params); send(MSG_SET_DOWNLOAD_FLAGS, params);
} }
@Override @Override
public void requestContinueDownload() { public void requestContinueDownload() {
send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle()); send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle());
} }
@Override @Override
public void requestDownloadStatus() { public void requestDownloadStatus() {
send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle()); send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle());
} }
@Override @Override
public void onClientUpdated(Messenger clientMessenger) { public void onClientUpdated(Messenger clientMessenger) {
Bundle bundle = new Bundle(1); Bundle bundle = new Bundle(1);
bundle.putParcelable(PARAM_MESSENGER, clientMessenger); bundle.putParcelable(PARAM_MESSENGER, clientMessenger);
send(MSG_REQUEST_CLIENT_UPDATE, bundle); send(MSG_REQUEST_CLIENT_UPDATE, bundle);
} }
} }
private static class Stub implements IStub { private static class Stub implements IStub {
private IDownloaderService mItf = null; private IDownloaderService mItf = null;
private final MessengerHandlerServer mMsgHandler = new MessengerHandlerServer(this); final Messenger mMessenger = new Messenger(new Handler() {
final Messenger mMessenger = new Messenger(mMsgHandler); @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REQUEST_ABORT_DOWNLOAD:
mItf.requestAbortDownload();
break;
case MSG_REQUEST_CONTINUE_DOWNLOAD:
mItf.requestContinueDownload();
break;
case MSG_REQUEST_PAUSE_DOWNLOAD:
mItf.requestPauseDownload();
break;
case MSG_SET_DOWNLOAD_FLAGS:
mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
break;
case MSG_REQUEST_DOWNLOAD_STATE:
mItf.requestDownloadStatus();
break;
case MSG_REQUEST_CLIENT_UPDATE:
mItf.onClientUpdated((Messenger) msg.getData().getParcelable(
PARAM_MESSENGER));
break;
}
}
});
private static class MessengerHandlerServer extends Handler { public Stub(IDownloaderService itf) {
private final WeakReference<Stub> mDownloader; mItf = itf;
public MessengerHandlerServer(Stub downloader) { }
mDownloader = new WeakReference<>(downloader);
}
@Override @Override
public void handleMessage(Message msg) { public Messenger getMessenger() {
Stub downloader = mDownloader.get(); return mMessenger;
if (downloader != null) { }
downloader.handleMessage(msg);
}
}
}
private void handleMessage(Message msg) { @Override
switch (msg.what) { public void connect(Context c) {
case MSG_REQUEST_ABORT_DOWNLOAD:
mItf.requestAbortDownload();
break;
case MSG_REQUEST_CONTINUE_DOWNLOAD:
mItf.requestContinueDownload();
break;
case MSG_REQUEST_PAUSE_DOWNLOAD:
mItf.requestPauseDownload();
break;
case MSG_SET_DOWNLOAD_FLAGS:
mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
break;
case MSG_REQUEST_DOWNLOAD_STATE:
mItf.requestDownloadStatus();
break;
case MSG_REQUEST_CLIENT_UPDATE:
mItf.onClientUpdated((Messenger)msg.getData().getParcelable(
PARAM_MESSENGER));
break;
}
}
public Stub(IDownloaderService itf) { }
mItf = itf;
}
@Override @Override
public Messenger getMessenger() { public void disconnect(Context c) {
return mMessenger;
}
@Override }
public void connect(Context c) { }
}
@Override /**
public void disconnect(Context c) {
}
}
/**
* Returns a proxy that will marshall calls to IDownloaderService methods * Returns a proxy that will marshall calls to IDownloaderService methods
* *
* @param ctx * @param ctx
* @return * @return
*/ */
public static IDownloaderService CreateProxy(Messenger msg) { public static IDownloaderService CreateProxy(Messenger msg) {
return new Proxy(msg); return new Proxy(msg);
} }
/** /**
* Returns a stub object that, when connected, will listen for marshalled * Returns a stub object that, when connected, will listen for marshalled
* IDownloaderService methods and translate them into calls to the supplied * IDownloaderService methods and translate them into calls to the supplied
* interface. * interface.
@ -187,7 +174,8 @@ public class DownloaderServiceMarshaller {
* when remote method calls are unmarshalled. * when remote method calls are unmarshalled.
* @return * @return
*/ */
public static IStub CreateStub(IDownloaderService itf) { public static IStub CreateStub(IDownloaderService itf) {
return new Stub(itf); return new Stub(itf);
} }
} }

View file

@ -24,7 +24,7 @@ import android.os.StatFs;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import com.godot.game.R; import com.android.vending.expansion.downloader.R;
import java.io.File; import java.io.File;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -40,95 +40,96 @@ import java.util.regex.Pattern;
*/ */
public class Helpers { public class Helpers {
public static Random sRandom = new Random(SystemClock.uptimeMillis()); public static Random sRandom = new Random(SystemClock.uptimeMillis());
/** Regex used to parse content-disposition headers */ /** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern
.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
private Helpers() { private Helpers() {
} }
/* /*
* Parse the Content-Disposition HTTP Header. The format of the header is defined here: * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
* content that is going to be downloaded to the file system. We only support the attachment * content that is going to be downloaded to the file system. We only support the attachment
* type. * type.
*/ */
static String parseContentDisposition(String contentDisposition) { static String parseContentDisposition(String contentDisposition) {
try { try {
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) { if (m.find()) {
return m.group(1); return m.group(1);
} }
} catch (IllegalStateException ex) { } catch (IllegalStateException ex) {
// This function is defined as returning null when it can't parse // This function is defined as returning null when it can't parse
// the header // the header
} }
return null; return null;
} }
/** /**
* @return the root of the filesystem containing the given path * @return the root of the filesystem containing the given path
*/ */
public static File getFilesystemRoot(String path) { public static File getFilesystemRoot(String path) {
File cache = Environment.getDownloadCacheDirectory(); File cache = Environment.getDownloadCacheDirectory();
if (path.startsWith(cache.getPath())) { if (path.startsWith(cache.getPath())) {
return cache; return cache;
} }
File external = Environment.getExternalStorageDirectory(); File external = Environment.getExternalStorageDirectory();
if (path.startsWith(external.getPath())) { if (path.startsWith(external.getPath())) {
return external; return external;
} }
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Cannot determine filesystem root for " + path); "Cannot determine filesystem root for " + path);
} }
public static boolean isExternalMediaMounted() { public static boolean isExternalMediaMounted() {
if (!Environment.getExternalStorageState().equals( if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) { Environment.MEDIA_MOUNTED)) {
// No SD card found. // No SD card found.
if (Constants.LOGVV) { if (Constants.LOGVV) {
Log.d(Constants.TAG, "no external storage"); Log.d(Constants.TAG, "no external storage");
} }
return false; return false;
} }
return true; return true;
} }
/** /**
* @return the number of bytes available on the filesystem rooted at the given File * @return the number of bytes available on the filesystem rooted at the given File
*/ */
public static long getAvailableBytes(File root) { public static long getAvailableBytes(File root) {
StatFs stat = new StatFs(root.getPath()); StatFs stat = new StatFs(root.getPath());
// put a bit of margin (in case creating the file grows the system by a // put a bit of margin (in case creating the file grows the system by a
// few blocks) // few blocks)
long availableBlocks = (long)stat.getAvailableBlocks() - 4; long availableBlocks = (long) stat.getAvailableBlocks() - 4;
return stat.getBlockSize() * availableBlocks; return stat.getBlockSize() * availableBlocks;
} }
/** /**
* Checks whether the filename looks legitimate * Checks whether the filename looks legitimate
*/ */
public static boolean isFilenameValid(String filename) { public static boolean isFilenameValid(String filename) {
filename = filename.replaceFirst("/+", "/"); // normalize leading filename = filename.replaceFirst("/+", "/"); // normalize leading
// slashes // slashes
return filename.startsWith(Environment.getDownloadCacheDirectory().toString()) || filename.startsWith(Environment.getExternalStorageDirectory().toString()); return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
} || filename.startsWith(Environment.getExternalStorageDirectory().toString());
}
/* /*
* Delete the given file from device * Delete the given file from device
*/ */
/* package */ static void deleteFile(String path) { /* package */static void deleteFile(String path) {
try { try {
File file = new File(path); File file = new File(path);
file.delete(); file.delete();
} catch (Exception e) { } catch (Exception e) {
Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e); Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e);
} }
} }
/** /**
* Showing progress in MB here. It would be nice to choose the unit (KB, MB, GB) based on total * Showing progress in MB here. It would be nice to choose the unit (KB, MB, GB) based on total
* file size, but given what we know about the expected ranges of file sizes for APK expansion * file size, but given what we know about the expected ranges of file sizes for APK expansion
* files, it's probably not necessary. * files, it's probably not necessary.
@ -138,63 +139,65 @@ public class Helpers {
* @return * @return
*/ */
static public String getDownloadProgressString(long overallProgress, long overallTotal) { static public String getDownloadProgressString(long overallProgress, long overallTotal) {
if (overallTotal == 0) { if (overallTotal == 0) {
if (Constants.LOGVV) { if (Constants.LOGVV) {
Log.e(Constants.TAG, "Notification called when total is zero"); Log.e(Constants.TAG, "Notification called when total is zero");
} }
return ""; return "";
} }
return String.format(Locale.ENGLISH, "%.2f", return String.format("%.2f",
(float)overallProgress / (1024.0f * 1024.0f)) + (float) overallProgress / (1024.0f * 1024.0f))
"MB /" + + "MB /" +
String.format(Locale.ENGLISH, "%.2f", (float)overallTotal / (1024.0f * 1024.0f)) + "MB"; String.format("%.2f", (float) overallTotal /
} (1024.0f * 1024.0f))
+ "MB";
}
/** /**
* Adds a percentile to getDownloadProgressString. * Adds a percentile to getDownloadProgressString.
* *
* @param overallProgress * @param overallProgress
* @param overallTotal * @param overallTotal
* @return * @return
*/ */
static public String getDownloadProgressStringNotification(long overallProgress, static public String getDownloadProgressStringNotification(long overallProgress,
long overallTotal) { long overallTotal) {
if (overallTotal == 0) { if (overallTotal == 0) {
if (Constants.LOGVV) { if (Constants.LOGVV) {
Log.e(Constants.TAG, "Notification called when total is zero"); Log.e(Constants.TAG, "Notification called when total is zero");
} }
return ""; return "";
} }
return getDownloadProgressString(overallProgress, overallTotal) + " (" + return getDownloadProgressString(overallProgress, overallTotal) + " (" +
getDownloadProgressPercent(overallProgress, overallTotal) + ")"; getDownloadProgressPercent(overallProgress, overallTotal) + ")";
} }
public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { public static String getDownloadProgressPercent(long overallProgress, long overallTotal) {
if (overallTotal == 0) { if (overallTotal == 0) {
if (Constants.LOGVV) { if (Constants.LOGVV) {
Log.e(Constants.TAG, "Notification called when total is zero"); Log.e(Constants.TAG, "Notification called when total is zero");
} }
return ""; return "";
} }
return Long.toString(overallProgress * 100 / overallTotal) + "%"; return Long.toString(overallProgress * 100 / overallTotal) + "%";
} }
public static String getSpeedString(float bytesPerMillisecond) { public static String getSpeedString(float bytesPerMillisecond) {
return String.format(Locale.ENGLISH, "%.2f", bytesPerMillisecond * 1000 / 1024); return String.format("%.2f", bytesPerMillisecond * 1000 / 1024);
} }
public static String getTimeRemaining(long durationInMilliseconds) { public static String getTimeRemaining(long durationInMilliseconds) {
SimpleDateFormat sdf; SimpleDateFormat sdf;
if (durationInMilliseconds > 1000 * 60 * 60) { if (durationInMilliseconds > 1000 * 60 * 60) {
sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
} else { } else {
sdf = new SimpleDateFormat("mm:ss", Locale.getDefault()); sdf = new SimpleDateFormat("mm:ss", Locale.getDefault());
} }
return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset())); return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset()));
} }
/** /**
* Returns the file name (without full path) for an Expansion APK file from the given context. * Returns the file name (without full path) for an Expansion APK file from the given context.
* *
* @param c the context * @param c the context
@ -202,33 +205,34 @@ public class Helpers {
* @param versionCode the version of the file * @param versionCode the version of the file
* @return String the file name of the expansion file * @return String the file name of the expansion file
*/ */
public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) { public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) {
return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb"; return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb";
} }
/** /**
* Returns the filename (where the file should be saved) from info about a download * Returns the filename (where the file should be saved) from info about a download
*/ */
static public String generateSaveFileName(Context c, String fileName) { static public String generateSaveFileName(Context c, String fileName) {
String path = getSaveFilePath(c) + File.separator + fileName; String path = getSaveFilePath(c)
return path; + File.separator + fileName;
} return path;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
static public String getSaveFilePath(Context c) { static public String getSaveFilePath(Context c) {
// This technically existed since Honeycomb, but it is critical // This technically existed since Honeycomb, but it is critical
// on KitKat and greater versions since it will create the // on KitKat and greater versions since it will create the
// directory if needed // directory if needed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return c.getObbDir().toString(); return c.getObbDir().toString();
} else { } else {
File root = Environment.getExternalStorageDirectory(); File root = Environment.getExternalStorageDirectory();
String path = root.toString() + Constants.EXP_PATH + c.getPackageName(); String path = root.toString() + Constants.EXP_PATH + c.getPackageName();
return path; return path;
} }
} }
/** /**
* Helper function to ascertain the existence of a file and return true/false appropriately * Helper function to ascertain the existence of a file and return true/false appropriately
* *
* @param c the app/activity/service context * @param c the app/activity/service context
@ -237,72 +241,72 @@ public class Helpers {
* @param deleteFileOnMismatch if the file sizes do not match, delete the file * @param deleteFileOnMismatch if the file sizes do not match, delete the file
* @return true if it does exist, false otherwise * @return true if it does exist, false otherwise
*/ */
static public boolean doesFileExist(Context c, String fileName, long fileSize, static public boolean doesFileExist(Context c, String fileName, long fileSize,
boolean deleteFileOnMismatch) { boolean deleteFileOnMismatch) {
// the file may have been delivered by Play --- let's make sure // the file may have been delivered by Play --- let's make sure
// it's the size we expect // it's the size we expect
File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
if (fileForNewFile.exists()) { if (fileForNewFile.exists()) {
if (fileForNewFile.length() == fileSize) { if (fileForNewFile.length() == fileSize) {
return true; return true;
} }
if (deleteFileOnMismatch) { if (deleteFileOnMismatch) {
// delete the file --- we won't be able to resume // delete the file --- we won't be able to resume
// because we cannot confirm the integrity of the file // because we cannot confirm the integrity of the file
fileForNewFile.delete(); fileForNewFile.delete();
} }
} }
return false; return false;
} }
public static final int FS_READABLE = 0; public static final int FS_READABLE = 0;
public static final int FS_DOES_NOT_EXIST = 1; public static final int FS_DOES_NOT_EXIST = 1;
public static final int FS_CANNOT_READ = 2; public static final int FS_CANNOT_READ = 2;
/** /**
* Helper function to ascertain whether a file can be read. * Helper function to ascertain whether a file can be read.
* *
* @param c the app/activity/service context * @param c the app/activity/service context
* @param fileName the name (sans path) of the file to query * @param fileName the name (sans path) of the file to query
* @return true if it does exist, false otherwise * @return true if it does exist, false otherwise
*/ */
static public int getFileStatus(Context c, String fileName) { static public int getFileStatus(Context c, String fileName) {
// the file may have been delivered by Play --- let's make sure // the file may have been delivered by Play --- let's make sure
// it's the size we expect // it's the size we expect
File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
int returnValue; int returnValue;
if (fileForNewFile.exists()) { if (fileForNewFile.exists()) {
if (fileForNewFile.canRead()) { if (fileForNewFile.canRead()) {
returnValue = FS_READABLE; returnValue = FS_READABLE;
} else { } else {
returnValue = FS_CANNOT_READ; returnValue = FS_CANNOT_READ;
} }
} else { } else {
returnValue = FS_DOES_NOT_EXIST; returnValue = FS_DOES_NOT_EXIST;
} }
return returnValue; return returnValue;
} }
/** /**
* Helper function to ascertain whether the application has the correct access to the OBB * Helper function to ascertain whether the application has the correct access to the OBB
* directory to allow an OBB file to be written. * directory to allow an OBB file to be written.
* *
* @param c the app/activity/service context * @param c the app/activity/service context
* @return true if the application can write an OBB file, false otherwise * @return true if the application can write an OBB file, false otherwise
*/ */
static public boolean canWriteOBBFile(Context c) { static public boolean canWriteOBBFile(Context c) {
String path = getSaveFilePath(c); String path = getSaveFilePath(c);
File fileForNewFile = new File(path); File fileForNewFile = new File(path);
boolean canWrite; boolean canWrite;
if (fileForNewFile.exists()) { if (fileForNewFile.exists()) {
canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite(); canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite();
} else { } else {
canWrite = fileForNewFile.mkdirs(); canWrite = fileForNewFile.mkdirs();
} }
return canWrite; return canWrite;
} }
/** /**
* Converts download states that are returned by the * Converts download states that are returned by the
* {@link IDownloaderClient#onDownloadStateChanged} callback into usable strings. This is useful * {@link IDownloaderClient#onDownloadStateChanged} callback into usable strings. This is useful
* if using the state strings built into the library to display user messages. * if using the state strings built into the library to display user messages.
@ -310,46 +314,47 @@ public class Helpers {
* @param state One of the STATE_* constants from {@link IDownloaderClient}. * @param state One of the STATE_* constants from {@link IDownloaderClient}.
* @return string resource ID for the corresponding string. * @return string resource ID for the corresponding string.
*/ */
static public int getDownloaderStringResourceIDFromState(int state) { static public int getDownloaderStringResourceIDFromState(int state) {
switch (state) { switch (state) {
case IDownloaderClient.STATE_IDLE: case IDownloaderClient.STATE_IDLE:
return R.string.state_idle; return R.string.state_idle;
case IDownloaderClient.STATE_FETCHING_URL: case IDownloaderClient.STATE_FETCHING_URL:
return R.string.state_fetching_url; return R.string.state_fetching_url;
case IDownloaderClient.STATE_CONNECTING: case IDownloaderClient.STATE_CONNECTING:
return R.string.state_connecting; return R.string.state_connecting;
case IDownloaderClient.STATE_DOWNLOADING: case IDownloaderClient.STATE_DOWNLOADING:
return R.string.state_downloading; return R.string.state_downloading;
case IDownloaderClient.STATE_COMPLETED: case IDownloaderClient.STATE_COMPLETED:
return R.string.state_completed; return R.string.state_completed;
case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE: case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE:
return R.string.state_paused_network_unavailable; return R.string.state_paused_network_unavailable;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST: case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
return R.string.state_paused_by_request; return R.string.state_paused_by_request;
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
return R.string.state_paused_wifi_disabled; return R.string.state_paused_wifi_disabled;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
return R.string.state_paused_wifi_unavailable; return R.string.state_paused_wifi_unavailable;
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED: case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED:
return R.string.state_paused_wifi_disabled; return R.string.state_paused_wifi_disabled;
case IDownloaderClient.STATE_PAUSED_NEED_WIFI: case IDownloaderClient.STATE_PAUSED_NEED_WIFI:
return R.string.state_paused_wifi_unavailable; return R.string.state_paused_wifi_unavailable;
case IDownloaderClient.STATE_PAUSED_ROAMING: case IDownloaderClient.STATE_PAUSED_ROAMING:
return R.string.state_paused_roaming; return R.string.state_paused_roaming;
case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE: case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE:
return R.string.state_paused_network_setup_failure; return R.string.state_paused_network_setup_failure;
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
return R.string.state_paused_sdcard_unavailable; return R.string.state_paused_sdcard_unavailable;
case IDownloaderClient.STATE_FAILED_UNLICENSED: case IDownloaderClient.STATE_FAILED_UNLICENSED:
return R.string.state_failed_unlicensed; return R.string.state_failed_unlicensed;
case IDownloaderClient.STATE_FAILED_FETCHING_URL: case IDownloaderClient.STATE_FAILED_FETCHING_URL:
return R.string.state_failed_fetching_url; return R.string.state_failed_fetching_url;
case IDownloaderClient.STATE_FAILED_SDCARD_FULL: case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
return R.string.state_failed_sdcard_full; return R.string.state_failed_sdcard_full;
case IDownloaderClient.STATE_FAILED_CANCELED: case IDownloaderClient.STATE_FAILED_CANCELED:
return R.string.state_failed_cancelled; return R.string.state_failed_cancelled;
default: default:
return R.string.state_unknown; return R.string.state_unknown;
} }
} }
} }

View file

@ -23,26 +23,26 @@ import android.os.Messenger;
* downloader. It is used to pass status from the service to the client. * downloader. It is used to pass status from the service to the client.
*/ */
public interface IDownloaderClient { public interface IDownloaderClient {
static final int STATE_IDLE = 1; static final int STATE_IDLE = 1;
static final int STATE_FETCHING_URL = 2; static final int STATE_FETCHING_URL = 2;
static final int STATE_CONNECTING = 3; static final int STATE_CONNECTING = 3;
static final int STATE_DOWNLOADING = 4; static final int STATE_DOWNLOADING = 4;
static final int STATE_COMPLETED = 5; static final int STATE_COMPLETED = 5;
static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6; static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6;
static final int STATE_PAUSED_BY_REQUEST = 7; static final int STATE_PAUSED_BY_REQUEST = 7;
/** /**
* Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and
* STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and
* cellular permission will restart the service. Wi-Fi disabled means that * cellular permission will restart the service. Wi-Fi disabled means that
* the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the
* other case Wi-Fi is enabled but not available. * other case Wi-Fi is enabled but not available.
*/ */
static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8; static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8;
static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9; static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9;
/** /**
* Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that
* Wi-Fi is unavailable and cellular permission will NOT restart the * Wi-Fi is unavailable and cellular permission will NOT restart the
* service. Wi-Fi disabled means that the Wi-Fi manager is returning that * service. Wi-Fi disabled means that the Wi-Fi manager is returning that
@ -53,27 +53,27 @@ public interface IDownloaderClient {
* developers with very large payloads do not allow these payloads to be * developers with very large payloads do not allow these payloads to be
* downloaded over cellular connections. * downloaded over cellular connections.
*/ */
static final int STATE_PAUSED_WIFI_DISABLED = 10; static final int STATE_PAUSED_WIFI_DISABLED = 10;
static final int STATE_PAUSED_NEED_WIFI = 11; static final int STATE_PAUSED_NEED_WIFI = 11;
static final int STATE_PAUSED_ROAMING = 12; static final int STATE_PAUSED_ROAMING = 12;
/** /**
* Scary case. We were on a network that redirected us to another website * Scary case. We were on a network that redirected us to another website
* that delivered us the wrong file. * that delivered us the wrong file.
*/ */
static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13; static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13;
static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14;
static final int STATE_FAILED_UNLICENSED = 15; static final int STATE_FAILED_UNLICENSED = 15;
static final int STATE_FAILED_FETCHING_URL = 16; static final int STATE_FAILED_FETCHING_URL = 16;
static final int STATE_FAILED_SDCARD_FULL = 17; static final int STATE_FAILED_SDCARD_FULL = 17;
static final int STATE_FAILED_CANCELED = 18; static final int STATE_FAILED_CANCELED = 18;
static final int STATE_FAILED = 19; static final int STATE_FAILED = 19;
/** /**
* Called internally by the stub when the service is bound to the client. * Called internally by the stub when the service is bound to the client.
* <p> * <p>
* Critical implementation detail. In onServiceConnected we create the * Critical implementation detail. In onServiceConnected we create the
@ -90,9 +90,9 @@ public interface IDownloaderClient {
* @param m the service Messenger. This Messenger is used to call the * @param m the service Messenger. This Messenger is used to call the
* service API from the client. * service API from the client.
*/ */
void onServiceConnected(Messenger m); void onServiceConnected(Messenger m);
/** /**
* Called when the download state changes. Depending on the state, there may * Called when the download state changes. Depending on the state, there may
* be user requests. The service is free to change the download state in the * be user requests. The service is free to change the download state in the
* middle of a user request, so the client should be able to handle this. * middle of a user request, so the client should be able to handle this.
@ -112,9 +112,9 @@ public interface IDownloaderClient {
* *
* @param newState one of the STATE_* values defined in IDownloaderClient * @param newState one of the STATE_* values defined in IDownloaderClient
*/ */
void onDownloadStateChanged(int newState); void onDownloadStateChanged(int newState);
/** /**
* Shows the download progress. This is intended to be used to fill out a * Shows the download progress. This is intended to be used to fill out a
* client UI. This progress should only be shown in a few states such as * client UI. This progress should only be shown in a few states such as
* STATE_DOWNLOADING. * STATE_DOWNLOADING.
@ -122,5 +122,5 @@ public interface IDownloaderClient {
* @param progress the DownloadProgressInfo object containing the current * @param progress the DownloadProgressInfo object containing the current
* progress of all downloads. * progress of all downloads.
*/ */
void onDownloadProgress(DownloadProgressInfo progress); void onDownloadProgress(DownloadProgressInfo progress);
} }

View file

@ -31,47 +31,47 @@ import android.os.Messenger;
* should immediately call {@link #onClientUpdated}. * should immediately call {@link #onClientUpdated}.
*/ */
public interface IDownloaderService { public interface IDownloaderService {
/** /**
* Set this flag in response to the * Set this flag in response to the
* IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then
* call RequestContinueDownload to resume a download * call RequestContinueDownload to resume a download
*/ */
public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1; public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1;
/** /**
* Request that the service abort the current download. The service should * Request that the service abort the current download. The service should
* respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}. * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}.
*/ */
void requestAbortDownload(); void requestAbortDownload();
/** /**
* Request that the service pause the current download. The service should * Request that the service pause the current download. The service should
* respond by changing the state to * respond by changing the state to
* {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
*/ */
void requestPauseDownload(); void requestPauseDownload();
/** /**
* Request that the service continue a paused download, when in any paused * Request that the service continue a paused download, when in any paused
* or failed state, including * or failed state, including
* {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
*/ */
void requestContinueDownload(); void requestContinueDownload();
/** /**
* Set the flags for this download (e.g. * Set the flags for this download (e.g.
* {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}).
* *
* @param flags * @param flags
*/ */
void setDownloadFlags(int flags); void setDownloadFlags(int flags);
/** /**
* Requests that the download status be sent to the client. * Requests that the download status be sent to the client.
*/ */
void requestDownloadStatus(); void requestDownloadStatus();
/** /**
* Call this when you get {@link * Call this when you get {@link
* IDownloaderClient.onServiceConnected(Messenger m)} from the * IDownloaderClient.onServiceConnected(Messenger m)} from the
* DownloaderClient to register the client with the service. It will * DownloaderClient to register the client with the service. It will
@ -79,5 +79,5 @@ public interface IDownloaderService {
* *
* @param clientMessenger * @param clientMessenger
*/ */
void onClientUpdated(Messenger clientMessenger); void onClientUpdated(Messenger clientMessenger);
} }

View file

@ -33,9 +33,9 @@ import android.os.Messenger;
* {@link IDownloaderService#onClientUpdated}. * {@link IDownloaderService#onClientUpdated}.
*/ */
public interface IStub { public interface IStub {
Messenger getMessenger(); Messenger getMessenger();
void connect(Context c); void connect(Context c);
void disconnect(Context c); void disconnect(Context c);
} }

View file

@ -16,7 +16,6 @@
package com.google.android.vending.expansion.downloader; package com.google.android.vending.expansion.downloader;
import android.annotation.SuppressLint;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
@ -31,96 +30,94 @@ import android.util.Log;
* Contains useful helper functions, typically tied to the application context. * Contains useful helper functions, typically tied to the application context.
*/ */
class SystemFacade { class SystemFacade {
private Context mContext; private Context mContext;
private NotificationManager mNotificationManager; private NotificationManager mNotificationManager;
public SystemFacade(Context context) { public SystemFacade(Context context) {
mContext = context; mContext = context;
mNotificationManager = (NotificationManager) mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE); mContext.getSystemService(Context.NOTIFICATION_SERVICE);
} }
public long currentTimeMillis() { public long currentTimeMillis() {
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
public Integer getActiveNetworkType() { public Integer getActiveNetworkType() {
ConnectivityManager connectivity = ConnectivityManager connectivity =
(ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) { if (connectivity == null) {
Log.w(Constants.TAG, "couldn't get connectivity manager"); Log.w(Constants.TAG, "couldn't get connectivity manager");
return null; return null;
} }
@SuppressLint("MissingPermission") NetworkInfo activeInfo = connectivity.getActiveNetworkInfo();
NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); if (activeInfo == null) {
if (activeInfo == null) { if (Constants.LOGVV) {
if (Constants.LOGVV) { Log.v(Constants.TAG, "network is not available");
Log.v(Constants.TAG, "network is not available"); }
} return null;
return null; }
} return activeInfo.getType();
return activeInfo.getType(); }
}
public boolean isNetworkRoaming() { public boolean isNetworkRoaming() {
ConnectivityManager connectivity = ConnectivityManager connectivity =
(ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) { if (connectivity == null) {
Log.w(Constants.TAG, "couldn't get connectivity manager"); Log.w(Constants.TAG, "couldn't get connectivity manager");
return false; return false;
} }
@SuppressLint("MissingPermission") NetworkInfo info = connectivity.getActiveNetworkInfo();
NetworkInfo info = connectivity.getActiveNetworkInfo(); boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE);
boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); TelephonyManager tm = (TelephonyManager) mContext
TelephonyManager tm = (TelephonyManager)mContext .getSystemService(Context.TELEPHONY_SERVICE);
.getSystemService(Context.TELEPHONY_SERVICE); if (null == tm) {
if (null == tm) { Log.w(Constants.TAG, "couldn't get telephony manager");
Log.w(Constants.TAG, "couldn't get telephony manager"); return false;
return false; }
} boolean isRoaming = isMobile && tm.isNetworkRoaming();
boolean isRoaming = isMobile && tm.isNetworkRoaming(); if (Constants.LOGVV && isRoaming) {
if (Constants.LOGVV && isRoaming) { Log.v(Constants.TAG, "network is roaming");
Log.v(Constants.TAG, "network is roaming"); }
} return isRoaming;
return isRoaming; }
}
public Long getMaxBytesOverMobile() { public Long getMaxBytesOverMobile() {
return (long)Integer.MAX_VALUE; return (long) Integer.MAX_VALUE;
} }
public Long getRecommendedMaxBytesOverMobile() { public Long getRecommendedMaxBytesOverMobile() {
return 2097152L; return 2097152L;
} }
public void sendBroadcast(Intent intent) { public void sendBroadcast(Intent intent) {
mContext.sendBroadcast(intent); mContext.sendBroadcast(intent);
} }
public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException { public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException {
return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid; return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid;
} }
public void postNotification(long id, Notification notification) { public void postNotification(long id, Notification notification) {
/** /**
* TODO: The system notification manager takes ints, not longs, as IDs, * TODO: The system notification manager takes ints, not longs, as IDs,
* but the download manager uses IDs take straight from the database, * but the download manager uses IDs take straight from the database,
* which are longs. This will have to be dealt with at some point. * which are longs. This will have to be dealt with at some point.
*/ */
mNotificationManager.notify((int)id, notification); mNotificationManager.notify((int) id, notification);
} }
public void cancelNotification(long id) { public void cancelNotification(long id) {
mNotificationManager.cancel((int)id); mNotificationManager.cancel((int) id);
} }
public void cancelAllNotifications() { public void cancelAllNotifications() {
mNotificationManager.cancelAll(); mNotificationManager.cancelAll();
} }
public void startThread(Thread thread) { public void startThread(Thread thread) {
thread.start(); thread.start();
} }
} }

View file

@ -32,80 +32,81 @@ import android.util.Log;
* intent, it does not queue up batches of intents of the same type. * intent, it does not queue up batches of intents of the same type.
*/ */
public abstract class CustomIntentService extends Service { public abstract class CustomIntentService extends Service {
private String mName; private String mName;
private boolean mRedelivery; private boolean mRedelivery;
private volatile ServiceHandler mServiceHandler; private volatile ServiceHandler mServiceHandler;
private volatile Looper mServiceLooper; private volatile Looper mServiceLooper;
private static final String LOG_TAG = "CustomIntentService"; private static final String LOG_TAG = "CustomIntentService";
private static final int WHAT_MESSAGE = -10; private static final int WHAT_MESSAGE = -10;
public CustomIntentService(String paramString) { public CustomIntentService(String paramString) {
this.mName = paramString; this.mName = paramString;
} }
@Override @Override
public IBinder onBind(Intent paramIntent) { public IBinder onBind(Intent paramIntent) {
return null; return null;
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
HandlerThread localHandlerThread = new HandlerThread("IntentService[" + this.mName + "]"); HandlerThread localHandlerThread = new HandlerThread("IntentService["
localHandlerThread.start(); + this.mName + "]");
this.mServiceLooper = localHandlerThread.getLooper(); localHandlerThread.start();
this.mServiceHandler = new ServiceHandler(this.mServiceLooper); this.mServiceLooper = localHandlerThread.getLooper();
} this.mServiceHandler = new ServiceHandler(this.mServiceLooper);
}
@Override @Override
public void onDestroy() { public void onDestroy() {
Thread localThread = this.mServiceLooper.getThread(); Thread localThread = this.mServiceLooper.getThread();
if ((localThread != null) && (localThread.isAlive())) { if ((localThread != null) && (localThread.isAlive())) {
localThread.interrupt(); localThread.interrupt();
} }
this.mServiceLooper.quit(); this.mServiceLooper.quit();
Log.d(LOG_TAG, "onDestroy"); Log.d(LOG_TAG, "onDestroy");
} }
protected abstract void onHandleIntent(Intent paramIntent); protected abstract void onHandleIntent(Intent paramIntent);
protected abstract boolean shouldStop(); protected abstract boolean shouldStop();
@Override @Override
public void onStart(Intent paramIntent, int startId) { public void onStart(Intent paramIntent, int startId) {
if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) { if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) {
Message localMessage = this.mServiceHandler.obtainMessage(); Message localMessage = this.mServiceHandler.obtainMessage();
localMessage.arg1 = startId; localMessage.arg1 = startId;
localMessage.obj = paramIntent; localMessage.obj = paramIntent;
localMessage.what = WHAT_MESSAGE; localMessage.what = WHAT_MESSAGE;
this.mServiceHandler.sendMessage(localMessage); this.mServiceHandler.sendMessage(localMessage);
} }
} }
@Override @Override
public int onStartCommand(Intent paramIntent, int flags, int startId) { public int onStartCommand(Intent paramIntent, int flags, int startId) {
onStart(paramIntent, startId); onStart(paramIntent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
} }
public void setIntentRedelivery(boolean enabled) { public void setIntentRedelivery(boolean enabled) {
this.mRedelivery = enabled; this.mRedelivery = enabled;
} }
private final class ServiceHandler extends Handler { private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) { public ServiceHandler(Looper looper) {
super(looper); super(looper);
} }
@Override @Override
public void handleMessage(Message paramMessage) { public void handleMessage(Message paramMessage) {
CustomIntentService.this CustomIntentService.this
.onHandleIntent((Intent)paramMessage.obj); .onHandleIntent((Intent) paramMessage.obj);
if (shouldStop()) { if (shouldStop()) {
Log.d(LOG_TAG, "stopSelf"); Log.d(LOG_TAG, "stopSelf");
CustomIntentService.this.stopSelf(paramMessage.arg1); CustomIntentService.this.stopSelf(paramMessage.arg1);
Log.d(LOG_TAG, "afterStopSelf"); Log.d(LOG_TAG, "afterStopSelf");
} }
} }
} }
} }

View file

@ -25,68 +25,68 @@ import android.util.Log;
* Representation of information about an individual download from the database. * Representation of information about an individual download from the database.
*/ */
public class DownloadInfo { public class DownloadInfo {
public String mUri; public String mUri;
public final int mIndex; public final int mIndex;
public final String mFileName; public final String mFileName;
public String mETag; public String mETag;
public long mTotalBytes; public long mTotalBytes;
public long mCurrentBytes; public long mCurrentBytes;
public long mLastMod; public long mLastMod;
public int mStatus; public int mStatus;
public int mControl; public int mControl;
public int mNumFailed; public int mNumFailed;
public int mRetryAfter; public int mRetryAfter;
public int mRedirectCount; public int mRedirectCount;
boolean mInitialized; boolean mInitialized;
public int mFuzz; public int mFuzz;
public DownloadInfo(int index, String fileName, String pkg) { public DownloadInfo(int index, String fileName, String pkg) {
mFuzz = Helpers.sRandom.nextInt(1001); mFuzz = Helpers.sRandom.nextInt(1001);
mFileName = fileName; mFileName = fileName;
mIndex = index; mIndex = index;
} }
public void resetDownload() { public void resetDownload() {
mCurrentBytes = 0; mCurrentBytes = 0;
mETag = ""; mETag = "";
mLastMod = 0; mLastMod = 0;
mStatus = 0; mStatus = 0;
mControl = 0; mControl = 0;
mNumFailed = 0; mNumFailed = 0;
mRetryAfter = 0; mRetryAfter = 0;
mRedirectCount = 0; mRedirectCount = 0;
} }
/** /**
* Returns the time when a download should be restarted. * Returns the time when a download should be restarted.
*/ */
public long restartTime(long now) { public long restartTime(long now) {
if (mNumFailed == 0) { if (mNumFailed == 0) {
return now; return now;
} }
if (mRetryAfter > 0) { if (mRetryAfter > 0) {
return mLastMod + mRetryAfter; return mLastMod + mRetryAfter;
} }
return mLastMod + return mLastMod +
Constants.RETRY_FIRST_DELAY * Constants.RETRY_FIRST_DELAY *
(1000 + mFuzz) * (1 << (mNumFailed - 1)); (1000 + mFuzz) * (1 << (mNumFailed - 1));
} }
public void logVerboseInfo() { public void logVerboseInfo() {
Log.v(Constants.TAG, "Service adding new entry"); Log.v(Constants.TAG, "Service adding new entry");
Log.v(Constants.TAG, "FILENAME: " + mFileName); Log.v(Constants.TAG, "FILENAME: " + mFileName);
Log.v(Constants.TAG, "URI : " + mUri); Log.v(Constants.TAG, "URI : " + mUri);
Log.v(Constants.TAG, "FILENAME: " + mFileName); Log.v(Constants.TAG, "FILENAME: " + mFileName);
Log.v(Constants.TAG, "CONTROL : " + mControl); Log.v(Constants.TAG, "CONTROL : " + mControl);
Log.v(Constants.TAG, "STATUS : " + mStatus); Log.v(Constants.TAG, "STATUS : " + mStatus);
Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); Log.v(Constants.TAG, "FAILED_C: " + mNumFailed);
Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter);
Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount);
Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); Log.v(Constants.TAG, "LAST_MOD: " + mLastMod);
Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); Log.v(Constants.TAG, "TOTAL : " + mTotalBytes);
Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes);
Log.v(Constants.TAG, "ETAG : " + mETag); Log.v(Constants.TAG, "ETAG : " + mETag);
} }
} }

View file

@ -16,7 +16,7 @@
package com.google.android.vending.expansion.downloader.impl; package com.google.android.vending.expansion.downloader.impl;
import com.godot.game.R; import com.android.vending.expansion.downloader.R;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo; import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.Helpers; import com.google.android.vending.expansion.downloader.Helpers;
@ -42,183 +42,184 @@ import android.support.v4.app.NotificationCompat;
*/ */
public class DownloadNotification implements IDownloaderClient { public class DownloadNotification implements IDownloaderClient {
private int mState; private int mState;
private final Context mContext; private final Context mContext;
private final NotificationManager mNotificationManager; private final NotificationManager mNotificationManager;
private CharSequence mCurrentTitle; private CharSequence mCurrentTitle;
private IDownloaderClient mClientProxy; private IDownloaderClient mClientProxy;
private NotificationCompat.Builder mActiveDownloadBuilder; private NotificationCompat.Builder mActiveDownloadBuilder;
private NotificationCompat.Builder mBuilder; private NotificationCompat.Builder mBuilder;
private NotificationCompat.Builder mCurrentBuilder; private NotificationCompat.Builder mCurrentBuilder;
private CharSequence mLabel; private CharSequence mLabel;
private String mCurrentText; private String mCurrentText;
private DownloadProgressInfo mProgressInfo; private DownloadProgressInfo mProgressInfo;
private PendingIntent mContentIntent; private PendingIntent mContentIntent;
static final String LOGTAG = "DownloadNotification"; static final String LOGTAG = "DownloadNotification";
static final int NOTIFICATION_ID = LOGTAG.hashCode(); static final int NOTIFICATION_ID = LOGTAG.hashCode();
public PendingIntent getClientIntent() { public PendingIntent getClientIntent() {
return mContentIntent; return mContentIntent;
} }
public void setClientIntent(PendingIntent clientIntent) { public void setClientIntent(PendingIntent clientIntent) {
this.mBuilder.setContentIntent(clientIntent); this.mBuilder.setContentIntent(clientIntent);
this.mActiveDownloadBuilder.setContentIntent(clientIntent); this.mActiveDownloadBuilder.setContentIntent(clientIntent);
this.mContentIntent = clientIntent; this.mContentIntent = clientIntent;
} }
public void resendState() { public void resendState() {
if (null != mClientProxy) { if (null != mClientProxy) {
mClientProxy.onDownloadStateChanged(mState); mClientProxy.onDownloadStateChanged(mState);
} }
} }
@Override @Override
public void onDownloadStateChanged(int newState) { public void onDownloadStateChanged(int newState) {
if (null != mClientProxy) { if (null != mClientProxy) {
mClientProxy.onDownloadStateChanged(newState); mClientProxy.onDownloadStateChanged(newState);
} }
if (newState != mState) { if (newState != mState) {
mState = newState; mState = newState;
if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) { if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) {
return; return;
} }
int stringDownloadID; int stringDownloadID;
int iconResource; int iconResource;
boolean ongoingEvent; boolean ongoingEvent;
// get the new title string and paused text // get the new title string and paused text
switch (newState) { switch (newState) {
case 0: case 0:
iconResource = android.R.drawable.stat_sys_warning; iconResource = android.R.drawable.stat_sys_warning;
stringDownloadID = R.string.state_unknown; stringDownloadID = R.string.state_unknown;
ongoingEvent = false; ongoingEvent = false;
break; break;
case IDownloaderClient.STATE_DOWNLOADING: case IDownloaderClient.STATE_DOWNLOADING:
iconResource = android.R.drawable.stat_sys_download; iconResource = android.R.drawable.stat_sys_download;
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
ongoingEvent = true; ongoingEvent = true;
break; break;
case IDownloaderClient.STATE_FETCHING_URL: case IDownloaderClient.STATE_FETCHING_URL:
case IDownloaderClient.STATE_CONNECTING: case IDownloaderClient.STATE_CONNECTING:
iconResource = android.R.drawable.stat_sys_download_done; iconResource = android.R.drawable.stat_sys_download_done;
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
ongoingEvent = true; ongoingEvent = true;
break; break;
case IDownloaderClient.STATE_COMPLETED: case IDownloaderClient.STATE_COMPLETED:
case IDownloaderClient.STATE_PAUSED_BY_REQUEST: case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
iconResource = android.R.drawable.stat_sys_download_done; iconResource = android.R.drawable.stat_sys_download_done;
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
ongoingEvent = false; ongoingEvent = false;
break; break;
case IDownloaderClient.STATE_FAILED: case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_CANCELED: case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL: case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_SDCARD_FULL: case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
case IDownloaderClient.STATE_FAILED_UNLICENSED: case IDownloaderClient.STATE_FAILED_UNLICENSED:
iconResource = android.R.drawable.stat_sys_warning; iconResource = android.R.drawable.stat_sys_warning;
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
ongoingEvent = false; ongoingEvent = false;
break; break;
default: default:
iconResource = android.R.drawable.stat_sys_warning; iconResource = android.R.drawable.stat_sys_warning;
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
ongoingEvent = true; ongoingEvent = true;
break; break;
} }
mCurrentText = mContext.getString(stringDownloadID); mCurrentText = mContext.getString(stringDownloadID);
mCurrentTitle = mLabel; mCurrentTitle = mLabel;
mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText); mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText);
mCurrentBuilder.setSmallIcon(iconResource); mCurrentBuilder.setSmallIcon(iconResource);
mCurrentBuilder.setContentTitle(mCurrentTitle); mCurrentBuilder.setContentTitle(mCurrentTitle);
mCurrentBuilder.setContentText(mCurrentText); mCurrentBuilder.setContentText(mCurrentText);
if (ongoingEvent) { if (ongoingEvent) {
mCurrentBuilder.setOngoing(true); mCurrentBuilder.setOngoing(true);
} else { } else {
mCurrentBuilder.setOngoing(false); mCurrentBuilder.setOngoing(false);
mCurrentBuilder.setAutoCancel(true); mCurrentBuilder.setAutoCancel(true);
} }
mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build()); mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
} }
} }
@Override @Override
public void onDownloadProgress(DownloadProgressInfo progress) { public void onDownloadProgress(DownloadProgressInfo progress) {
mProgressInfo = progress; mProgressInfo = progress;
if (null != mClientProxy) { if (null != mClientProxy) {
mClientProxy.onDownloadProgress(progress); mClientProxy.onDownloadProgress(progress);
} }
if (progress.mOverallTotal <= 0) { if (progress.mOverallTotal <= 0) {
// we just show the text // we just show the text
mBuilder.setTicker(mCurrentTitle); mBuilder.setTicker(mCurrentTitle);
mBuilder.setSmallIcon(android.R.drawable.stat_sys_download); mBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
mBuilder.setContentTitle(mCurrentTitle); mBuilder.setContentTitle(mCurrentTitle);
mBuilder.setContentText(mCurrentText); mBuilder.setContentText(mCurrentText);
mCurrentBuilder = mBuilder; mCurrentBuilder = mBuilder;
} else { } else {
mActiveDownloadBuilder.setProgress((int)progress.mOverallTotal, (int)progress.mOverallProgress, false); mActiveDownloadBuilder.setProgress((int) progress.mOverallTotal, (int) progress.mOverallProgress, false);
mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal)); mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
mActiveDownloadBuilder.setSmallIcon(android.R.drawable.stat_sys_download); mActiveDownloadBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
mActiveDownloadBuilder.setTicker(mLabel + ": " + mCurrentText); mActiveDownloadBuilder.setTicker(mLabel + ": " + mCurrentText);
mActiveDownloadBuilder.setContentTitle(mLabel); mActiveDownloadBuilder.setContentTitle(mLabel);
mActiveDownloadBuilder.setContentInfo(mContext.getString(R.string.time_remaining_notification, mActiveDownloadBuilder.setContentInfo(mContext.getString(R.string.time_remaining_notification,
Helpers.getTimeRemaining(progress.mTimeRemaining))); Helpers.getTimeRemaining(progress.mTimeRemaining)));
mCurrentBuilder = mActiveDownloadBuilder; mCurrentBuilder = mActiveDownloadBuilder;
} }
mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build()); mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
} }
/** /**
* Called in response to onClientUpdated. Creates a new proxy and notifies * Called in response to onClientUpdated. Creates a new proxy and notifies
* it of the current state. * it of the current state.
* *
* @param msg the client Messenger to notify * @param msg the client Messenger to notify
*/ */
public void setMessenger(Messenger msg) { public void setMessenger(Messenger msg) {
mClientProxy = DownloaderClientMarshaller.CreateProxy(msg); mClientProxy = DownloaderClientMarshaller.CreateProxy(msg);
if (null != mProgressInfo) { if (null != mProgressInfo) {
mClientProxy.onDownloadProgress(mProgressInfo); mClientProxy.onDownloadProgress(mProgressInfo);
} }
if (mState != -1) { if (mState != -1) {
mClientProxy.onDownloadStateChanged(mState); mClientProxy.onDownloadStateChanged(mState);
} }
} }
/** /**
* Constructor * Constructor
* *
* @param ctx The context to use to obtain access to the Notification * @param ctx The context to use to obtain access to the Notification
* Service * Service
*/ */
DownloadNotification(Context ctx, CharSequence applicationLabel) { DownloadNotification(Context ctx, CharSequence applicationLabel) {
mState = -1; mState = -1;
mContext = ctx; mContext = ctx;
mLabel = applicationLabel; mLabel = applicationLabel;
mNotificationManager = (NotificationManager) mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE); mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mActiveDownloadBuilder = new NotificationCompat.Builder(ctx); mActiveDownloadBuilder = new NotificationCompat.Builder(ctx);
mBuilder = new NotificationCompat.Builder(ctx); mBuilder = new NotificationCompat.Builder(ctx);
// Set Notification category and priorities to something that makes sense for a long // Set Notification category and priorities to something that makes sense for a long
// lived background task. // lived background task.
mActiveDownloadBuilder.setPriority(NotificationCompat.PRIORITY_LOW); mActiveDownloadBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
mActiveDownloadBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS); mActiveDownloadBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS);
mBuilder.setPriority(NotificationCompat.PRIORITY_LOW); mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
mBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS); mBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS);
mCurrentBuilder = mBuilder; mCurrentBuilder = mBuilder;
} }
@Override
public void onServiceConnected(Messenger m) {
}
@Override
public void onServiceConnected(Messenger m) {
}
} }

View file

@ -27,443 +27,484 @@ import android.provider.BaseColumns;
import android.util.Log; import android.util.Log;
public class DownloadsDB { public class DownloadsDB {
private static final String DATABASE_NAME = "DownloadsDB"; private static final String DATABASE_NAME = "DownloadsDB";
private static final int DATABASE_VERSION = 7; private static final int DATABASE_VERSION = 7;
public static final String LOG_TAG = DownloadsDB.class.getName(); public static final String LOG_TAG = DownloadsDB.class.getName();
final SQLiteOpenHelper mHelper; final SQLiteOpenHelper mHelper;
SQLiteStatement mGetDownloadByIndex; SQLiteStatement mGetDownloadByIndex;
SQLiteStatement mUpdateCurrentBytes; SQLiteStatement mUpdateCurrentBytes;
private static DownloadsDB mDownloadsDB; private static DownloadsDB mDownloadsDB;
long mMetadataRowID = -1; long mMetadataRowID = -1;
int mVersionCode = -1; int mVersionCode = -1;
int mStatus = -1; int mStatus = -1;
int mFlags; int mFlags;
static public synchronized DownloadsDB getDB(Context paramContext) { static public synchronized DownloadsDB getDB(Context paramContext) {
if (null == mDownloadsDB) { if (null == mDownloadsDB) {
return new DownloadsDB(paramContext); return new DownloadsDB(paramContext);
} }
return mDownloadsDB; return mDownloadsDB;
} }
private SQLiteStatement getDownloadByIndexStatement() { private SQLiteStatement getDownloadByIndexStatement() {
if (null == mGetDownloadByIndex) { if (null == mGetDownloadByIndex) {
mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement( mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement(
"SELECT " + BaseColumns._ID + " FROM " + DownloadColumns.TABLE_NAME + " WHERE " + DownloadColumns.INDEX + " = ?"); "SELECT " + BaseColumns._ID + " FROM "
} + DownloadColumns.TABLE_NAME + " WHERE "
return mGetDownloadByIndex; + DownloadColumns.INDEX + " = ?");
} }
return mGetDownloadByIndex;
}
private SQLiteStatement getUpdateCurrentBytesStatement() { private SQLiteStatement getUpdateCurrentBytesStatement() {
if (null == mUpdateCurrentBytes) { if (null == mUpdateCurrentBytes) {
mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement( mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement(
"UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES + " = ?" "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES
+ + " = ?" +
" WHERE " + DownloadColumns.INDEX + " = ?"); " WHERE " + DownloadColumns.INDEX + " = ?");
} }
return mUpdateCurrentBytes; return mUpdateCurrentBytes;
} }
private DownloadsDB(Context paramContext) { private DownloadsDB(Context paramContext) {
this.mHelper = new DownloadsContentDBHelper(paramContext); this.mHelper = new DownloadsContentDBHelper(paramContext);
final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
// Query for the version code, the row ID of the metadata (for future // Query for the version code, the row ID of the metadata (for future
// updating) the status and the flags // updating) the status and the flags
Cursor cur = sqldb.rawQuery("SELECT " + Cursor cur = sqldb.rawQuery("SELECT " +
MetadataColumns.APKVERSION + "," + MetadataColumns.APKVERSION + "," +
BaseColumns._ID + "," + BaseColumns._ID + "," +
MetadataColumns.DOWNLOAD_STATUS + "," + MetadataColumns.DOWNLOAD_STATUS + "," +
MetadataColumns.FLAGS + MetadataColumns.FLAGS +
" FROM " + MetadataColumns.TABLE_NAME + " LIMIT 1", " FROM "
null); + MetadataColumns.TABLE_NAME + " LIMIT 1", null);
if (null != cur && cur.moveToFirst()) { if (null != cur && cur.moveToFirst()) {
mVersionCode = cur.getInt(0); mVersionCode = cur.getInt(0);
mMetadataRowID = cur.getLong(1); mMetadataRowID = cur.getLong(1);
mStatus = cur.getInt(2); mStatus = cur.getInt(2);
mFlags = cur.getInt(3); mFlags = cur.getInt(3);
cur.close(); cur.close();
} }
mDownloadsDB = this; mDownloadsDB = this;
} }
protected DownloadInfo getDownloadInfoByFileName(String fileName) { protected DownloadInfo getDownloadInfoByFileName(String fileName) {
final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
Cursor itemcur = null; Cursor itemcur = null;
try { try {
itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION,
DownloadColumns.FILENAME + " = ?", DownloadColumns.FILENAME + " = ?",
new String[] { new String[] {
fileName }, fileName
null, null, null); }, null, null, null);
if (null != itemcur && itemcur.moveToFirst()) { if (null != itemcur && itemcur.moveToFirst()) {
return getDownloadInfoFromCursor(itemcur); return getDownloadInfoFromCursor(itemcur);
} }
} finally { } finally {
if (null != itemcur) if (null != itemcur)
itemcur.close(); itemcur.close();
} }
return null; return null;
} }
public long getIDForDownloadInfo(final DownloadInfo di) { public long getIDForDownloadInfo(final DownloadInfo di) {
return getIDByIndex(di.mIndex); return getIDByIndex(di.mIndex);
} }
public long getIDByIndex(int index) { public long getIDByIndex(int index) {
SQLiteStatement downloadByIndex = getDownloadByIndexStatement(); SQLiteStatement downloadByIndex = getDownloadByIndexStatement();
downloadByIndex.clearBindings(); downloadByIndex.clearBindings();
downloadByIndex.bindLong(1, index); downloadByIndex.bindLong(1, index);
try { try {
return downloadByIndex.simpleQueryForLong(); return downloadByIndex.simpleQueryForLong();
} catch (SQLiteDoneException e) { } catch (SQLiteDoneException e) {
return -1; return -1;
} }
} }
public void updateDownloadCurrentBytes(final DownloadInfo di) { public void updateDownloadCurrentBytes(final DownloadInfo di) {
SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement(); SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement();
downloadCurrentBytes.clearBindings(); downloadCurrentBytes.clearBindings();
downloadCurrentBytes.bindLong(1, di.mCurrentBytes); downloadCurrentBytes.bindLong(1, di.mCurrentBytes);
downloadCurrentBytes.bindLong(2, di.mIndex); downloadCurrentBytes.bindLong(2, di.mIndex);
downloadCurrentBytes.execute(); downloadCurrentBytes.execute();
} }
public void close() { public void close() {
this.mHelper.close(); this.mHelper.close();
} }
protected static class DownloadsContentDBHelper extends SQLiteOpenHelper { protected static class DownloadsContentDBHelper extends SQLiteOpenHelper {
DownloadsContentDBHelper(Context paramContext) { DownloadsContentDBHelper(Context paramContext) {
super(paramContext, DATABASE_NAME, null, DATABASE_VERSION); super(paramContext, DATABASE_NAME, null, DATABASE_VERSION);
} }
private String createTableQueryFromArray(String paramString, private String createTableQueryFromArray(String paramString,
String[][] paramArrayOfString) { String[][] paramArrayOfString) {
StringBuilder localStringBuilder = new StringBuilder(); StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append("CREATE TABLE "); localStringBuilder.append("CREATE TABLE ");
localStringBuilder.append(paramString); localStringBuilder.append(paramString);
localStringBuilder.append(" ("); localStringBuilder.append(" (");
int i = paramArrayOfString.length; int i = paramArrayOfString.length;
for (int j = 0;; j++) { for (int j = 0;; j++) {
if (j >= i) { if (j >= i) {
localStringBuilder localStringBuilder
.setLength(localStringBuilder.length() - 1); .setLength(localStringBuilder.length() - 1);
localStringBuilder.append(");"); localStringBuilder.append(");");
return localStringBuilder.toString(); return localStringBuilder.toString();
} }
String[] arrayOfString = paramArrayOfString[j]; String[] arrayOfString = paramArrayOfString[j];
localStringBuilder.append(' '); localStringBuilder.append(' ');
localStringBuilder.append(arrayOfString[0]); localStringBuilder.append(arrayOfString[0]);
localStringBuilder.append(' '); localStringBuilder.append(' ');
localStringBuilder.append(arrayOfString[1]); localStringBuilder.append(arrayOfString[1]);
localStringBuilder.append(','); localStringBuilder.append(',');
} }
} }
/** /**
* These two arrays must match and have the same order. For every Schema * These two arrays must match and have the same order. For every Schema
* there must be a corresponding table name. * there must be a corresponding table name.
*/ */
static final private String[][][] sSchemas = { static final private String[][][] sSchemas = {
DownloadColumns.SCHEMA, MetadataColumns.SCHEMA DownloadColumns.SCHEMA, MetadataColumns.SCHEMA
}; };
static final private String[] sTables = { static final private String[] sTables = {
DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME
}; };
/** /**
* Goes through all of the tables in sTables and drops each table if it * Goes through all of the tables in sTables and drops each table if it
* exists. Altered to no longer make use of reflection. * exists. Altered to no longer make use of reflection.
*/ */
private void dropTables(SQLiteDatabase paramSQLiteDatabase) { private void dropTables(SQLiteDatabase paramSQLiteDatabase) {
for (String table : sTables) { for (String table : sTables) {
try { try {
paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table); paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table);
} catch (Exception localException) { } catch (Exception localException) {
localException.printStackTrace(); localException.printStackTrace();
} }
} }
} }
/** /**
* Goes through all of the tables in sTables and creates a database with * Goes through all of the tables in sTables and creates a database with
* the corresponding schema described in sSchemas. Altered to no longer * the corresponding schema described in sSchemas. Altered to no longer
* make use of reflection. * make use of reflection.
*/ */
public void onCreate(SQLiteDatabase paramSQLiteDatabase) { public void onCreate(SQLiteDatabase paramSQLiteDatabase) {
int numSchemas = sSchemas.length; int numSchemas = sSchemas.length;
for (int i = 0; i < numSchemas; i++) { for (int i = 0; i < numSchemas; i++) {
try { try {
String[][] schema = (String[][])sSchemas[i]; String[][] schema = (String[][]) sSchemas[i];
paramSQLiteDatabase.execSQL(createTableQueryFromArray( paramSQLiteDatabase.execSQL(createTableQueryFromArray(
sTables[i], schema)); sTables[i], schema));
} catch (Exception localException) { } catch (Exception localException) {
while (true) while (true)
localException.printStackTrace(); localException.printStackTrace();
} }
} }
} }
public void onUpgrade(SQLiteDatabase paramSQLiteDatabase, public void onUpgrade(SQLiteDatabase paramSQLiteDatabase,
int paramInt1, int paramInt2) { int paramInt1, int paramInt2) {
Log.w(DownloadsContentDBHelper.class.getName(), Log.w(DownloadsContentDBHelper.class.getName(),
"Upgrading database from version " + paramInt1 + " to " + paramInt2 + ", which will destroy all old data"); "Upgrading database from version " + paramInt1 + " to "
dropTables(paramSQLiteDatabase); + paramInt2 + ", which will destroy all old data");
onCreate(paramSQLiteDatabase); dropTables(paramSQLiteDatabase);
} onCreate(paramSQLiteDatabase);
} }
}
public static class MetadataColumns implements BaseColumns { public static class MetadataColumns implements BaseColumns {
public static final String APKVERSION = "APKVERSION"; public static final String APKVERSION = "APKVERSION";
public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS"; public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS";
public static final String FLAGS = "DOWNLOADFLAGS"; public static final String FLAGS = "DOWNLOADFLAGS";
public static final String[][] SCHEMA = { public static final String[][] SCHEMA = {
{ BaseColumns._ID, "INTEGER PRIMARY KEY" }, {
{ APKVERSION, "INTEGER" }, { DOWNLOAD_STATUS, "INTEGER" }, BaseColumns._ID, "INTEGER PRIMARY KEY"
{ FLAGS, "INTEGER" } },
}; {
public static final String TABLE_NAME = "MetadataColumns"; APKVERSION, "INTEGER"
public static final String _ID = "MetadataColumns._id"; }, {
} DOWNLOAD_STATUS, "INTEGER"
},
{
FLAGS, "INTEGER"
}
};
public static final String TABLE_NAME = "MetadataColumns";
public static final String _ID = "MetadataColumns._id";
}
public static class DownloadColumns implements BaseColumns { public static class DownloadColumns implements BaseColumns {
public static final String INDEX = "FILEIDX"; public static final String INDEX = "FILEIDX";
public static final String URI = "URI"; public static final String URI = "URI";
public static final String FILENAME = "FN"; public static final String FILENAME = "FN";
public static final String ETAG = "ETAG"; public static final String ETAG = "ETAG";
public static final String TOTALBYTES = "TOTALBYTES"; public static final String TOTALBYTES = "TOTALBYTES";
public static final String CURRENTBYTES = "CURRENTBYTES"; public static final String CURRENTBYTES = "CURRENTBYTES";
public static final String LASTMOD = "LASTMOD"; public static final String LASTMOD = "LASTMOD";
public static final String STATUS = "STATUS"; public static final String STATUS = "STATUS";
public static final String CONTROL = "CONTROL"; public static final String CONTROL = "CONTROL";
public static final String NUM_FAILED = "FAILCOUNT"; public static final String NUM_FAILED = "FAILCOUNT";
public static final String RETRY_AFTER = "RETRYAFTER"; public static final String RETRY_AFTER = "RETRYAFTER";
public static final String REDIRECT_COUNT = "REDIRECTCOUNT"; public static final String REDIRECT_COUNT = "REDIRECTCOUNT";
public static final String[][] SCHEMA = { public static final String[][] SCHEMA = {
{ BaseColumns._ID, "INTEGER PRIMARY KEY" }, {
{ INDEX, "INTEGER UNIQUE" }, { URI, "TEXT" }, BaseColumns._ID, "INTEGER PRIMARY KEY"
{ FILENAME, "TEXT UNIQUE" }, { ETAG, "TEXT" }, },
{ TOTALBYTES, "INTEGER" }, { CURRENTBYTES, "INTEGER" }, {
{ LASTMOD, "INTEGER" }, { STATUS, "INTEGER" }, INDEX, "INTEGER UNIQUE"
{ CONTROL, "INTEGER" }, { NUM_FAILED, "INTEGER" }, }, {
{ RETRY_AFTER, "INTEGER" }, { REDIRECT_COUNT, "INTEGER" } URI, "TEXT"
}; },
public static final String TABLE_NAME = "DownloadColumns"; {
public static final String _ID = "DownloadColumns._id"; FILENAME, "TEXT UNIQUE"
} }, {
ETAG, "TEXT"
},
{
TOTALBYTES, "INTEGER"
}, {
CURRENTBYTES, "INTEGER"
},
{
LASTMOD, "INTEGER"
}, {
STATUS, "INTEGER"
},
{
CONTROL, "INTEGER"
}, {
NUM_FAILED, "INTEGER"
},
{
RETRY_AFTER, "INTEGER"
}, {
REDIRECT_COUNT, "INTEGER"
}
};
public static final String TABLE_NAME = "DownloadColumns";
public static final String _ID = "DownloadColumns._id";
}
private static final String[] DC_PROJECTION = { private static final String[] DC_PROJECTION = {
DownloadColumns.FILENAME, DownloadColumns.FILENAME,
DownloadColumns.URI, DownloadColumns.ETAG, DownloadColumns.URI, DownloadColumns.ETAG,
DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES, DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES,
DownloadColumns.LASTMOD, DownloadColumns.STATUS, DownloadColumns.LASTMOD, DownloadColumns.STATUS,
DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED, DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED,
DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT, DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT,
DownloadColumns.INDEX DownloadColumns.INDEX
}; };
private static final int FILENAME_IDX = 0; private static final int FILENAME_IDX = 0;
private static final int URI_IDX = 1; private static final int URI_IDX = 1;
private static final int ETAG_IDX = 2; private static final int ETAG_IDX = 2;
private static final int TOTALBYTES_IDX = 3; private static final int TOTALBYTES_IDX = 3;
private static final int CURRENTBYTES_IDX = 4; private static final int CURRENTBYTES_IDX = 4;
private static final int LASTMOD_IDX = 5; private static final int LASTMOD_IDX = 5;
private static final int STATUS_IDX = 6; private static final int STATUS_IDX = 6;
private static final int CONTROL_IDX = 7; private static final int CONTROL_IDX = 7;
private static final int NUM_FAILED_IDX = 8; private static final int NUM_FAILED_IDX = 8;
private static final int RETRY_AFTER_IDX = 9; private static final int RETRY_AFTER_IDX = 9;
private static final int REDIRECT_COUNT_IDX = 10; private static final int REDIRECT_COUNT_IDX = 10;
private static final int INDEX_IDX = 11; private static final int INDEX_IDX = 11;
/** /**
* This function will add a new file to the database if it does not exist. * This function will add a new file to the database if it does not exist.
* *
* @param di DownloadInfo that we wish to store * @param di DownloadInfo that we wish to store
* @return the row id of the record to be updated/inserted, or -1 * @return the row id of the record to be updated/inserted, or -1
*/ */
public boolean updateDownload(DownloadInfo di) { public boolean updateDownload(DownloadInfo di) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(DownloadColumns.INDEX, di.mIndex); cv.put(DownloadColumns.INDEX, di.mIndex);
cv.put(DownloadColumns.FILENAME, di.mFileName); cv.put(DownloadColumns.FILENAME, di.mFileName);
cv.put(DownloadColumns.URI, di.mUri); cv.put(DownloadColumns.URI, di.mUri);
cv.put(DownloadColumns.ETAG, di.mETag); cv.put(DownloadColumns.ETAG, di.mETag);
cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes); cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes);
cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes); cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes);
cv.put(DownloadColumns.LASTMOD, di.mLastMod); cv.put(DownloadColumns.LASTMOD, di.mLastMod);
cv.put(DownloadColumns.STATUS, di.mStatus); cv.put(DownloadColumns.STATUS, di.mStatus);
cv.put(DownloadColumns.CONTROL, di.mControl); cv.put(DownloadColumns.CONTROL, di.mControl);
cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed); cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed);
cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter); cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter);
cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount); cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount);
return updateDownload(di, cv); return updateDownload(di, cv);
} }
public boolean updateDownload(DownloadInfo di, ContentValues cv) { public boolean updateDownload(DownloadInfo di, ContentValues cv) {
long id = di == null ? -1 : getIDForDownloadInfo(di); long id = di == null ? -1 : getIDForDownloadInfo(di);
try { try {
final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); final SQLiteDatabase sqldb = mHelper.getWritableDatabase();
if (id != -1) { if (id != -1) {
if (1 != sqldb.update(DownloadColumns.TABLE_NAME, if (1 != sqldb.update(DownloadColumns.TABLE_NAME,
cv, DownloadColumns._ID + " = " + id, null)) { cv, DownloadColumns._ID + " = " + id, null)) {
return false; return false;
} }
} else { } else {
return -1 != sqldb.insert(DownloadColumns.TABLE_NAME, return -1 != sqldb.insert(DownloadColumns.TABLE_NAME,
DownloadColumns.URI, cv); DownloadColumns.URI, cv);
} }
} catch (android.database.sqlite.SQLiteException ex) { } catch (android.database.sqlite.SQLiteException ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
return false; return false;
} }
public int getLastCheckedVersionCode() { public int getLastCheckedVersionCode() {
return mVersionCode; return mVersionCode;
} }
public boolean isDownloadRequired() { public boolean isDownloadRequired() {
final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM " + DownloadColumns.TABLE_NAME + " WHERE " + DownloadColumns.STATUS + " <> 0", null); Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM "
try { + DownloadColumns.TABLE_NAME + " WHERE "
if (null != cur && cur.moveToFirst()) { + DownloadColumns.STATUS + " <> 0", null);
return 0 == cur.getInt(0); try {
} if (null != cur && cur.moveToFirst()) {
} finally { return 0 == cur.getInt(0);
if (null != cur) }
cur.close(); } finally {
} if (null != cur)
return true; cur.close();
} }
return true;
}
public int getFlags() { public int getFlags() {
return mFlags; return mFlags;
} }
public boolean updateFlags(int flags) { public boolean updateFlags(int flags) {
if (mFlags != flags) { if (mFlags != flags) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(MetadataColumns.FLAGS, flags); cv.put(MetadataColumns.FLAGS, flags);
if (updateMetadata(cv)) { if (updateMetadata(cv)) {
mFlags = flags; mFlags = flags;
return true; return true;
} else { } else {
return false; return false;
} }
} else { } else {
return true; return true;
} }
}; };
public boolean updateStatus(int status) { public boolean updateStatus(int status) {
if (mStatus != status) { if (mStatus != status) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(MetadataColumns.DOWNLOAD_STATUS, status); cv.put(MetadataColumns.DOWNLOAD_STATUS, status);
if (updateMetadata(cv)) { if (updateMetadata(cv)) {
mStatus = status; mStatus = status;
return true; return true;
} else { } else {
return false; return false;
} }
} else { } else {
return true; return true;
} }
}; };
public boolean updateMetadata(ContentValues cv) { public boolean updateMetadata(ContentValues cv) {
final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); final SQLiteDatabase sqldb = mHelper.getWritableDatabase();
if (-1 == this.mMetadataRowID) { if (-1 == this.mMetadataRowID) {
long newID = sqldb.insert(MetadataColumns.TABLE_NAME, long newID = sqldb.insert(MetadataColumns.TABLE_NAME,
MetadataColumns.APKVERSION, cv); MetadataColumns.APKVERSION, cv);
if (-1 == newID) if (-1 == newID)
return false; return false;
mMetadataRowID = newID; mMetadataRowID = newID;
} else { } else {
if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv, if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv,
BaseColumns._ID + " = " + mMetadataRowID, null)) BaseColumns._ID + " = " + mMetadataRowID, null))
return false; return false;
} }
return true; return true;
} }
public boolean updateMetadata(int apkVersion, int downloadStatus) { public boolean updateMetadata(int apkVersion, int downloadStatus) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put(MetadataColumns.APKVERSION, apkVersion); cv.put(MetadataColumns.APKVERSION, apkVersion);
cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus); cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus);
if (updateMetadata(cv)) { if (updateMetadata(cv)) {
mVersionCode = apkVersion; mVersionCode = apkVersion;
mStatus = downloadStatus; mStatus = downloadStatus;
return true; return true;
} else { } else {
return false; return false;
} }
}; };
public boolean updateFromDb(DownloadInfo di) { public boolean updateFromDb(DownloadInfo di) {
final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
Cursor cur = null; Cursor cur = null;
try { try {
cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION,
DownloadColumns.FILENAME + "= ?", DownloadColumns.FILENAME + "= ?",
new String[] { new String[] {
di.mFileName }, di.mFileName
null, null, null); }, null, null, null);
if (null != cur && cur.moveToFirst()) { if (null != cur && cur.moveToFirst()) {
setDownloadInfoFromCursor(di, cur); setDownloadInfoFromCursor(di, cur);
return true; return true;
} }
return false; return false;
} finally { } finally {
if (null != cur) { if (null != cur) {
cur.close(); cur.close();
} }
} }
} }
public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) { public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) {
di.mUri = cur.getString(URI_IDX); di.mUri = cur.getString(URI_IDX);
di.mETag = cur.getString(ETAG_IDX); di.mETag = cur.getString(ETAG_IDX);
di.mTotalBytes = cur.getLong(TOTALBYTES_IDX); di.mTotalBytes = cur.getLong(TOTALBYTES_IDX);
di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX); di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX);
di.mLastMod = cur.getLong(LASTMOD_IDX); di.mLastMod = cur.getLong(LASTMOD_IDX);
di.mStatus = cur.getInt(STATUS_IDX); di.mStatus = cur.getInt(STATUS_IDX);
di.mControl = cur.getInt(CONTROL_IDX); di.mControl = cur.getInt(CONTROL_IDX);
di.mNumFailed = cur.getInt(NUM_FAILED_IDX); di.mNumFailed = cur.getInt(NUM_FAILED_IDX);
di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX); di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX);
di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX); di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX);
} }
public DownloadInfo getDownloadInfoFromCursor(Cursor cur) { public DownloadInfo getDownloadInfoFromCursor(Cursor cur) {
DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX), DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX),
cur.getString(FILENAME_IDX), this.getClass().getPackage().getName()); cur.getString(FILENAME_IDX), this.getClass().getPackage()
setDownloadInfoFromCursor(di, cur); .getName());
return di; setDownloadInfoFromCursor(di, cur);
} return di;
}
public DownloadInfo[] getDownloads() {
final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
Cursor cur = null;
try {
cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null,
null, null, null, null);
if (null != cur && cur.moveToFirst()) {
DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()];
int idx = 0;
do {
DownloadInfo di = getDownloadInfoFromCursor(cur);
retInfos[idx++] = di;
} while (cur.moveToNext());
return retInfos;
}
return null;
} finally {
if (null != cur) {
cur.close();
}
}
}
public DownloadInfo[] getDownloads() {
final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
Cursor cur = null;
try {
cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null,
null, null, null, null);
if (null != cur && cur.moveToFirst()) {
DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()];
int idx = 0;
do {
DownloadInfo di = getDownloadInfoFromCursor(cur);
retInfos[idx++] = di;
} while (cur.moveToNext());
return retInfos;
}
return null;
} finally {
if (null != cur) {
cur.close();
}
}
}
} }

View file

@ -27,7 +27,7 @@ import java.util.regex.Pattern;
*/ */
public final class HttpDateTime { public final class HttpDateTime {
/* /*
* Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT
* RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850, * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850,
* obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format
@ -37,155 +37,164 @@ public final class HttpDateTime {
* (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first
* digit is zero. Mon can be the full name of the month. * digit is zero. Mon can be the full name of the month.
*/ */
private static final String HTTP_DATE_RFC_REGEXP = private static final String HTTP_DATE_RFC_REGEXP =
"([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
private static final String HTTP_DATE_ANSIC_REGEXP = private static final String HTTP_DATE_ANSIC_REGEXP =
"[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
/** /**
* The compiled version of the HTTP-date regular expressions. * The compiled version of the HTTP-date regular expressions.
*/ */
private static final Pattern HTTP_DATE_RFC_PATTERN = private static final Pattern HTTP_DATE_RFC_PATTERN =
Pattern.compile(HTTP_DATE_RFC_REGEXP); Pattern.compile(HTTP_DATE_RFC_REGEXP);
private static final Pattern HTTP_DATE_ANSIC_PATTERN = private static final Pattern HTTP_DATE_ANSIC_PATTERN =
Pattern.compile(HTTP_DATE_ANSIC_REGEXP); Pattern.compile(HTTP_DATE_ANSIC_REGEXP);
private static class TimeOfDay { private static class TimeOfDay {
TimeOfDay(int h, int m, int s) { TimeOfDay(int h, int m, int s) {
this.hour = h; this.hour = h;
this.minute = m; this.minute = m;
this.second = s; this.second = s;
} }
int hour; int hour;
int minute; int minute;
int second; int second;
} }
public static long parse(String timeString) public static long parse(String timeString)
throws IllegalArgumentException { throws IllegalArgumentException {
int date = 1; int date = 1;
int month = Calendar.JANUARY; int month = Calendar.JANUARY;
int year = 1970; int year = 1970;
TimeOfDay timeOfDay; TimeOfDay timeOfDay;
Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
if (rfcMatcher.find()) { if (rfcMatcher.find()) {
date = getDate(rfcMatcher.group(1)); date = getDate(rfcMatcher.group(1));
month = getMonth(rfcMatcher.group(2)); month = getMonth(rfcMatcher.group(2));
year = getYear(rfcMatcher.group(3)); year = getYear(rfcMatcher.group(3));
timeOfDay = getTime(rfcMatcher.group(4)); timeOfDay = getTime(rfcMatcher.group(4));
} else { } else {
Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
if (ansicMatcher.find()) { if (ansicMatcher.find()) {
month = getMonth(ansicMatcher.group(1)); month = getMonth(ansicMatcher.group(1));
date = getDate(ansicMatcher.group(2)); date = getDate(ansicMatcher.group(2));
timeOfDay = getTime(ansicMatcher.group(3)); timeOfDay = getTime(ansicMatcher.group(3));
year = getYear(ansicMatcher.group(4)); year = getYear(ansicMatcher.group(4));
} else { } else {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
} }
// FIXME: Y2038 BUG! // FIXME: Y2038 BUG!
if (year >= 2038) { if (year >= 2038) {
year = 2038; year = 2038;
month = Calendar.JANUARY; month = Calendar.JANUARY;
date = 1; date = 1;
} }
Time time = new Time(Time.TIMEZONE_UTC); Time time = new Time(Time.TIMEZONE_UTC);
time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
month, year); month, year);
return time.toMillis(false /* use isDst */); return time.toMillis(false /* use isDst */);
} }
private static int getDate(String dateString) { private static int getDate(String dateString) {
if (dateString.length() == 2) { if (dateString.length() == 2) {
return (dateString.charAt(0) - '0') * 10 + (dateString.charAt(1) - '0'); return (dateString.charAt(0) - '0') * 10
} else { + (dateString.charAt(1) - '0');
return (dateString.charAt(0) - '0'); } else {
} return (dateString.charAt(0) - '0');
} }
}
/* /*
* jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0 * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0
* + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20 * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20
* + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19 * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19
* = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9 * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9
*/ */
private static int getMonth(String monthString) { private static int getMonth(String monthString) {
int hash = Character.toLowerCase(monthString.charAt(0)) + int hash = Character.toLowerCase(monthString.charAt(0)) +
Character.toLowerCase(monthString.charAt(1)) + Character.toLowerCase(monthString.charAt(1)) +
Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
switch (hash) { switch (hash) {
case 22: case 22:
return Calendar.JANUARY; return Calendar.JANUARY;
case 10: case 10:
return Calendar.FEBRUARY; return Calendar.FEBRUARY;
case 29: case 29:
return Calendar.MARCH; return Calendar.MARCH;
case 32: case 32:
return Calendar.APRIL; return Calendar.APRIL;
case 36: case 36:
return Calendar.MAY; return Calendar.MAY;
case 42: case 42:
return Calendar.JUNE; return Calendar.JUNE;
case 40: case 40:
return Calendar.JULY; return Calendar.JULY;
case 26: case 26:
return Calendar.AUGUST; return Calendar.AUGUST;
case 37: case 37:
return Calendar.SEPTEMBER; return Calendar.SEPTEMBER;
case 35: case 35:
return Calendar.OCTOBER; return Calendar.OCTOBER;
case 48: case 48:
return Calendar.NOVEMBER; return Calendar.NOVEMBER;
case 9: case 9:
return Calendar.DECEMBER; return Calendar.DECEMBER;
default: default:
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
} }
private static int getYear(String yearString) { private static int getYear(String yearString) {
if (yearString.length() == 2) { if (yearString.length() == 2) {
int year = (yearString.charAt(0) - '0') * 10 + (yearString.charAt(1) - '0'); int year = (yearString.charAt(0) - '0') * 10
if (year >= 70) { + (yearString.charAt(1) - '0');
return year + 1900; if (year >= 70) {
} else { return year + 1900;
return year + 2000; } else {
} return year + 2000;
} else if (yearString.length() == 3) { }
// According to RFC 2822, three digit years should be added to 1900. } else if (yearString.length() == 3) {
int year = (yearString.charAt(0) - '0') * 100 + (yearString.charAt(1) - '0') * 10 + (yearString.charAt(2) - '0'); // According to RFC 2822, three digit years should be added to 1900.
return year + 1900; int year = (yearString.charAt(0) - '0') * 100
} else if (yearString.length() == 4) { + (yearString.charAt(1) - '0') * 10
return (yearString.charAt(0) - '0') * 1000 + (yearString.charAt(1) - '0') * 100 + (yearString.charAt(2) - '0') * 10 + (yearString.charAt(3) - '0'); + (yearString.charAt(2) - '0');
} else { return year + 1900;
return 1970; } else if (yearString.length() == 4) {
} return (yearString.charAt(0) - '0') * 1000
} + (yearString.charAt(1) - '0') * 100
+ (yearString.charAt(2) - '0') * 10
+ (yearString.charAt(3) - '0');
} else {
return 1970;
}
}
private static TimeOfDay getTime(String timeString) { private static TimeOfDay getTime(String timeString) {
// HH might be H // HH might be H
int i = 0; int i = 0;
int hour = timeString.charAt(i++) - '0'; int hour = timeString.charAt(i++) - '0';
if (timeString.charAt(i) != ':') if (timeString.charAt(i) != ':')
hour = hour * 10 + (timeString.charAt(i++) - '0'); hour = hour * 10 + (timeString.charAt(i++) - '0');
// Skip ':' // Skip ':'
i++; i++;
int minute = (timeString.charAt(i++) - '0') * 10 + (timeString.charAt(i++) - '0'); int minute = (timeString.charAt(i++) - '0') * 10
// Skip ':' + (timeString.charAt(i++) - '0');
i++; // Skip ':'
i++;
int second = (timeString.charAt(i++) - '0') * 10 + (timeString.charAt(i++) - '0'); int second = (timeString.charAt(i++) - '0') * 10
+ (timeString.charAt(i++) - '0');
return new TimeOfDay(hour, minute, second); return new TimeOfDay(hour, minute, second);
} }
} }