diff options
author | fhuya <fhuya@google.com> | 2019-09-02 17:31:51 -0700 |
---|---|---|
committer | fhuya <fhuya@google.com> | 2019-09-04 16:20:22 -0700 |
commit | 7fabfd402f235ebcf64cfde3b399b8b62b969243 (patch) | |
tree | 99bb4eacc7828bedae43316f7415091de9782922 /platform/android/java/src/com | |
parent | ba854bbc7bb0eae230299de4da8dfcb7caf74b69 (diff) |
Split the Android platform java logic into an Android library module (`lib`) and an application module (`app`).
The application module `app` serves double duties of providing the prebuilt Godot binaries ('android_debug.apk', 'android_release.apk') and the Godot custom build template ('android_source.zip').
Diffstat (limited to 'platform/android/java/src/com')
33 files changed, 0 insertions, 7568 deletions
diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java deleted file mode 100644 index 1dcc370d83..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import java.io.File; - - -/** - * Contains the internal constants that are used in the download manager. - * As a general rule, modifying these constants should be done with care. - */ -public class Constants { - /** Tag used for debugging/logging */ - public static final String TAG = "LVLDL"; - - /** - * Expansion path where we store obb files - */ - 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 */ - public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; - - /** the intent that gets sent when clicking a successful download */ - public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; - - /** the intent that gets sent when clicking an incomplete/failed download */ - public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; - - /** the intent that gets sent when deleting the notification of a completed download */ - 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 - * base filename from the sequence number - */ - public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; - - /** The default user agent used for downloads */ - public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; - - /** The buffer size used to stream the data */ - public static final int BUFFER_SIZE = 4096; - - /** The minimum amount of progress that has to be done before the progress bar gets updated */ - 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 */ - public static final long MIN_PROGRESS_TIME = 1000; - - /** The maximum number of rows in the database (FIFO) */ - public static final int MAX_DOWNLOADS = 1000; - - /** - * The number of times that the download manager will retry its network - * operations when no progress is happening before it gives up. - */ - public static final int MAX_RETRIES = 5; - - /** - * The minimum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MIN_RETRY_AFTER = 30; // 30s - - /** - * The maximum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h - - /** - * The maximum number of redirects. - */ - 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. - * Each subsequent retry grows exponentially, doubling each time. - * The time is in seconds. - */ - public static final int RETRY_FIRST_DELAY = 30; - - /** Enable separate connectivity logging */ - public static final boolean LOGX = true; - - /** Enable verbose logging */ - public static final boolean LOGV = false; - - /** Enable super-verbose logging */ - private static final boolean LOCAL_LOGVV = false; - public static final boolean LOGVV = LOCAL_LOGVV && LOGV; - - /** - * This download has successfully completed. - * Warning: there might be other status values that indicate success - * in the future. - * Use isSucccess() to capture the entire category. - */ - public static final int STATUS_SUCCESS = 200; - - /** - * This request couldn't be parsed. This is also used when processing - * requests with unknown/unsupported URI schemes. - */ - public static final int STATUS_BAD_REQUEST = 400; - - /** - * This download can't be performed because the content type cannot be - * handled. - */ - public static final int STATUS_NOT_ACCEPTABLE = 406; - - /** - * This download cannot be performed because the length cannot be - * determined accurately. This is the code for the HTTP error "Length - * Required", which is typically used when making requests that require - * a content length but don't have one, and it is also used in the - * client when a response is received whose length cannot be determined - * accurately (therefore making it impossible to know when a download - * completes). - */ - public static final int STATUS_LENGTH_REQUIRED = 411; - - /** - * This download was interrupted and cannot be resumed. - * 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. - */ - public static final int STATUS_PRECONDITION_FAILED = 412; - - /** - * The lowest-valued error status that is not an actual HTTP status code. - */ - public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; - - /** - * The requested destination file already exists. - */ - public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; - - /** - * Some possibly transient error occurred, but we can't resume the download. - */ - public static final int STATUS_CANNOT_RESUME = 489; - - /** - * This download was canceled - */ - public static final int STATUS_CANCELED = 490; - - /** - * This download has completed with an error. - * Warning: there will be other status values that indicate errors in - * the future. Use isStatusError() to capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of a storage issue. - * Typically, that's because the filesystem is missing or full. - * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} - * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. - */ - public static final int STATUS_FILE_ERROR = 492; - - /** - * This download couldn't be completed because of an HTTP - * redirect response that the download manager couldn't - * handle. - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed because of an - * unspecified unhandled HTTP code. - */ - public static final int STATUS_UNHANDLED_HTTP_CODE = 494; - - /** - * This download couldn't be completed because of an - * error receiving or processing data at the HTTP level. - */ - public static final int STATUS_HTTP_DATA_ERROR = 495; - - /** - * This download couldn't be completed because of an - * HttpException while setting up the request. - */ - public static final int STATUS_HTTP_EXCEPTION = 496; - - /** - * This download couldn't be completed because there were - * too many redirects. - */ - public static final int STATUS_TOO_MANY_REDIRECTS = 497; - - /** - * This download couldn't be completed due to insufficient storage - * space. Typically, this is because the SD card is full. - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; - - /** - * This download couldn't be completed because no external storage - * device was found. Typically, this is because the SD card is not - * mounted. - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - - /** - * The wake duration to check to see if a download is possible. - */ - public static final long WATCHDOG_WAKE_TIMER = 60*1000; - - /** - * The wake duration to check to see if the process was killed. - */ - public static final long ACTIVE_THREAD_WATCHDOG = 5*1000; - -}
\ No newline at end of file diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java deleted file mode 100644 index 9cb294d721..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.os.Parcel; -import android.os.Parcelable; - - -/** - * This class contains progress information about the active download(s). - * - * When you build the Activity that initiates a download and tracks the - * progress by implementing the {@link IDownloaderClient} interface, you'll - * receive a DownloadProgressInfo object in each call to the {@link - * IDownloaderClient#onDownloadProgress} method. This allows you to update - * your activity's UI with information about the download progress, such - * as the progress so far, time remaining and current speed. - */ -public class DownloadProgressInfo implements Parcelable { - public long mOverallTotal; - public long mOverallProgress; - public long mTimeRemaining; // time remaining - public float mCurrentSpeed; // speed in KB/S - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel p, int i) { - p.writeLong(mOverallTotal); - p.writeLong(mOverallProgress); - p.writeLong(mTimeRemaining); - p.writeFloat(mCurrentSpeed); - } - - public DownloadProgressInfo(Parcel p) { - mOverallTotal = p.readLong(); - mOverallProgress = p.readLong(); - mTimeRemaining = p.readLong(); - mCurrentSpeed = p.readFloat(); - } - - public DownloadProgressInfo(long overallTotal, long overallProgress, - long timeRemaining, - float currentSpeed) { - this.mOverallTotal = overallTotal; - this.mOverallProgress = overallProgress; - this.mTimeRemaining = timeRemaining; - this.mCurrentSpeed = currentSpeed; - } - - public static final Creator<DownloadProgressInfo> CREATOR = new Creator<DownloadProgressInfo>() { - @Override - public DownloadProgressInfo createFromParcel(Parcel parcel) { - return new DownloadProgressInfo(parcel); - } - - @Override - public DownloadProgressInfo[] newArray(int i) { - return new DownloadProgressInfo[i]; - } - }; - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java deleted file mode 100644 index 452c7d1483..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.util.Log; - -// -- GODOT start -- -import java.lang.ref.WeakReference; -// -- GODOT end -- - - -/** - * This class binds the service API to your application client. It contains the IDownloaderClient proxy, - * which is used to call functions in your client as well as the Stub, which is used to call functions - * in the client implementation of IDownloaderClient. - * - * <p>The IPC is implemented using an Android Messenger and a service Binder. The connect method - * should be called whenever the client wants to bind to the service. It opens up a service connection - * that ends up calling the onServiceConnected client API that passes the service messenger - * in. If the client wants to be notified by the service, it is responsible for then passing its - * messenger to the service in a separate call. - * - * <p>Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}. - * - * <p>When your application first starts, you should first check whether your app's expansion files are - * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which - * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method - * returns a value indicating whether download is required or not. - * - * <p>If a download is required, {@link #startDownloadServiceIfRequired} begins the download through - * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link - * IStub} object that you need in order to receive calls through your {@link IDownloaderClient} - * interface. - */ -public class DownloaderClientMarshaller { - public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10; - public static final int MSG_ONDOWNLOADPROGRESS = 11; - public static final int MSG_ONSERVICECONNECTED = 12; - - public static final String PARAM_NEW_STATE = "newState"; - public static final String PARAM_PROGRESS = "progress"; - 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 LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED; - public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED; - - private static class Proxy implements IDownloaderClient { - private Messenger mServiceMessenger; - - @Override - public void onDownloadStateChanged(int newState) { - Bundle params = new Bundle(1); - params.putInt(PARAM_NEW_STATE, newState); - send(MSG_ONDOWNLOADSTATE_CHANGED, params); - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - Bundle params = new Bundle(1); - params.putParcelable(PARAM_PROGRESS, progress); - send(MSG_ONDOWNLOADPROGRESS, params); - } - - private void send(int method, Bundle params) { - Message m = Message.obtain(null, method); - m.setData(params); - try { - mServiceMessenger.send(m); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public Proxy(Messenger msg) { - mServiceMessenger = msg; - } - - @Override - public void onServiceConnected(Messenger m) { - /** - * This is never called through the proxy. - */ - } - } - - private static class Stub implements IStub { - private IDownloaderClient mItf = null; - private Class<?> mDownloaderServiceClass; - private boolean mBound; - private Messenger mServiceMessenger; - private Context mContext; - /** - * Target we publish for clients to send messages to IncomingHandler. - */ - // -- GODOT start -- - private final MessengerHandlerClient mMsgHandler = new MessengerHandlerClient(this); - final Messenger mMessenger = new Messenger(mMsgHandler); - - private static class MessengerHandlerClient extends Handler { - private final WeakReference<Stub> mDownloader; - public MessengerHandlerClient(Stub downloader) { - 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; - } - } - // -- GODOT end -- - - public Stub(IDownloaderClient itf, Class<?> downloaderService) { - mItf = itf; - mDownloaderServiceClass = downloaderService; - } - - /** - * Class for interacting with the main interface of the service. - */ - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - // This is called when the connection with the service has been - // established, giving us the object we can use to - // interact with the service. We are communicating with the - // service using a Messenger, so here we get a client-side - // representation of that from the raw IBinder object. - mServiceMessenger = new Messenger(service); - mItf.onServiceConnected( - mServiceMessenger); - } - - public void onServiceDisconnected(ComponentName className) { - // This is called when the connection with the service has been - // unexpectedly disconnected -- that is, its process crashed. - mServiceMessenger = null; - } - }; - - @Override - public void connect(Context c) { - mContext = c; - Intent bindIntent = new Intent(c, mDownloaderServiceClass); - bindIntent.putExtra(PARAM_MESSENGER, mMessenger); - if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) { - if ( Constants.LOGVV ) { - Log.d(Constants.TAG, "Service Unbound"); - } - } else { - mBound = true; - } - - } - - @Override - public void disconnect(Context c) { - 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 - * - * @param msg - * @return - */ - public static IDownloaderClient CreateProxy(Messenger msg) { - return new Proxy(msg); - } - - /** - * Returns a stub object that, when connected, will listen for marshaled - * {@link IDownloaderClient} methods and translate them into calls to the supplied - * interface. - * - * @param itf An implementation of IDownloaderClient that will be called - * when remote method calls are unmarshaled. - * @param downloaderService The class for your implementation of {@link - * impl.DownloaderService}. - * @return The {@link IStub} that allows you to connect to the service such that - * your {@link IDownloaderClient} receives status updates. - */ - public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) { - return new Stub(itf, downloaderService); - } - - /** - * 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 - * the metadata database updated 2) If the APK version does not match, - * checks the new LVL status to see if a new download is required 3) If the - * APK version does match, then checks to see if the download(s) have been - * completed 4) If the downloads have been completed, returns - * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the - * startup of an application to quickly ascertain if the application needs - * to wait to hear about any updated APK expansion files. Note that this does - * mean that the application MUST be run for the first time with a network - * connection, even if Market delivers all of the files. - * - * @param context Your application Context. - * @param notificationClient A PendingIntent to start the Activity in your application - * that shows the download progress and which will also start the application when download - * completes. - * @param serviceClass the class of your {@link imp.DownloaderService} implementation - * @return whether the service was started and the reason for starting the service. - * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link - * #DOWNLOAD_REQUIRED}. - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, - Class<?> serviceClass) - throws NameNotFoundException { - return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, - serviceClass); - } - - /** - * This version assumes that the intent contains the pending intent as a parameter. This - * is used for responding to alarms. - * <p>The pending intent must be in an extra with the key {@link - * impl.DownloaderService#EXTRA_PENDING_INTENT}. - * - * @param context - * @param notificationClient - * @param serviceClass the class of the service to start - * @return - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, - Class<?> serviceClass) - throws NameNotFoundException { - return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, - serviceClass); - } - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java deleted file mode 100644 index 3771d19c9b..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; - -// -- GODOT start -- -import java.lang.ref.WeakReference; -// -- GODOT end -- - - -/** - * This class is used by the client activity to proxy requests to the Downloader - * Service. - * - * Most importantly, you must call {@link #CreateProxy} during the {@link - * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate - * an {@link IDownloaderService} object that you can then use to issue commands to the {@link - * DownloaderService} (such as to pause and resume downloads). - */ -public class DownloaderServiceMarshaller { - - public static final int MSG_REQUEST_ABORT_DOWNLOAD = - 1; - public static final int MSG_REQUEST_PAUSE_DOWNLOAD = - 2; - public static final int MSG_SET_DOWNLOAD_FLAGS = - 3; - public static final int MSG_REQUEST_CONTINUE_DOWNLOAD = - 4; - public static final int MSG_REQUEST_DOWNLOAD_STATE = - 5; - public static final int MSG_REQUEST_CLIENT_UPDATE = - 6; - - public static final String PARAMS_FLAGS = "flags"; - public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; - - private static class Proxy implements IDownloaderService { - private Messenger mMsg; - - private void send(int method, Bundle params) { - Message m = Message.obtain(null, method); - m.setData(params); - try { - mMsg.send(m); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public Proxy(Messenger msg) { - mMsg = msg; - } - - @Override - public void requestAbortDownload() { - send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle()); - } - - @Override - public void requestPauseDownload() { - send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle()); - } - - @Override - public void setDownloadFlags(int flags) { - Bundle params = new Bundle(); - params.putInt(PARAMS_FLAGS, flags); - send(MSG_SET_DOWNLOAD_FLAGS, params); - } - - @Override - public void requestContinueDownload() { - send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle()); - } - - @Override - public void requestDownloadStatus() { - send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle()); - } - - @Override - public void onClientUpdated(Messenger clientMessenger) { - Bundle bundle = new Bundle(1); - bundle.putParcelable(PARAM_MESSENGER, clientMessenger); - send(MSG_REQUEST_CLIENT_UPDATE, bundle); - } - } - - private static class Stub implements IStub { - private IDownloaderService mItf = null; - // -- GODOT start -- - private final MessengerHandlerServer mMsgHandler = new MessengerHandlerServer(this); - final Messenger mMessenger = new Messenger(mMsgHandler); - - private static class MessengerHandlerServer extends Handler { - private final WeakReference<Stub> mDownloader; - public MessengerHandlerServer(Stub downloader) { - 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_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; - } - } - // -- GODOT end -- - - public Stub(IDownloaderService itf) { - mItf = itf; - } - - @Override - public Messenger getMessenger() { - return mMessenger; - } - - @Override - public void connect(Context c) { - - } - - @Override - public void disconnect(Context c) { - - } - } - - /** - * Returns a proxy that will marshall calls to IDownloaderService methods - * - * @param ctx - * @return - */ - public static IDownloaderService CreateProxy(Messenger msg) { - return new Proxy(msg); - } - - /** - * Returns a stub object that, when connected, will listen for marshalled - * IDownloaderService methods and translate them into calls to the supplied - * interface. - * - * @param itf An implementation of IDownloaderService that will be called - * when remote method calls are unmarshalled. - * @return - */ - public static IStub CreateStub(IDownloaderService itf) { - return new Stub(itf); - } - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java deleted file mode 100644 index 36cd6aacfe..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.os.Environment; -import android.os.StatFs; -import android.os.SystemClock; -import android.util.Log; - -// -- GODOT start -- -//import com.android.vending.expansion.downloader.R; -import com.godot.game.R; -// -- GODOT end -- - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Random; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Some helper functions for the download manager - */ -public class Helpers { - - public static Random sRandom = new Random(SystemClock.uptimeMillis()); - - /** Regex used to parse content-disposition headers */ - private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern - .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); - - private Helpers() { - } - - /* - * 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 - * content that is going to be downloaded to the file system. We only support the attachment - * type. - */ - static String parseContentDisposition(String contentDisposition) { - try { - Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); - if (m.find()) { - return m.group(1); - } - } catch (IllegalStateException ex) { - // This function is defined as returning null when it can't parse - // the header - } - return null; - } - - /** - * @return the root of the filesystem containing the given path - */ - public static File getFilesystemRoot(String path) { - File cache = Environment.getDownloadCacheDirectory(); - if (path.startsWith(cache.getPath())) { - return cache; - } - File external = Environment.getExternalStorageDirectory(); - if (path.startsWith(external.getPath())) { - return external; - } - throw new IllegalArgumentException( - "Cannot determine filesystem root for " + path); - } - - public static boolean isExternalMediaMounted() { - if (!Environment.getExternalStorageState().equals( - Environment.MEDIA_MOUNTED)) { - // No SD card found. - if (Constants.LOGVV) { - Log.d(Constants.TAG, "no external storage"); - } - return false; - } - return true; - } - - /** - * @return the number of bytes available on the filesystem rooted at the given File - */ - public static long getAvailableBytes(File root) { - StatFs stat = new StatFs(root.getPath()); - // put a bit of margin (in case creating the file grows the system by a - // few blocks) - long availableBlocks = (long) stat.getAvailableBlocks() - 4; - return stat.getBlockSize() * availableBlocks; - } - - /** - * Checks whether the filename looks legitimate - */ - public static boolean isFilenameValid(String filename) { - filename = filename.replaceFirst("/+", "/"); // normalize leading - // slashes - return filename.startsWith(Environment.getDownloadCacheDirectory().toString()) - || filename.startsWith(Environment.getExternalStorageDirectory().toString()); - } - - /* - * Delete the given file from device - */ - /* package */static void deleteFile(String path) { - try { - File file = new File(path); - file.delete(); - } catch (Exception 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 - * file size, but given what we know about the expected ranges of file sizes for APK expansion - * files, it's probably not necessary. - * - * @param overallProgress - * @param overallTotal - * @return - */ - - static public String getDownloadProgressString(long overallProgress, long overallTotal) { - if (overallTotal == 0) { - if (Constants.LOGVV) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - // -- GODOT start -- - return String.format(Locale.ENGLISH, "%.2f", - (float) overallProgress / (1024.0f * 1024.0f)) - + "MB /" + - String.format(Locale.ENGLISH, "%.2f", (float) overallTotal / - (1024.0f * 1024.0f)) - + "MB"; - // -- GODOT end -- - } - - /** - * Adds a percentile to getDownloadProgressString. - * - * @param overallProgress - * @param overallTotal - * @return - */ - static public String getDownloadProgressStringNotification(long overallProgress, - long overallTotal) { - if (overallTotal == 0) { - if (Constants.LOGVV) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return getDownloadProgressString(overallProgress, overallTotal) + " (" + - getDownloadProgressPercent(overallProgress, overallTotal) + ")"; - } - - public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { - if (overallTotal == 0) { - if (Constants.LOGVV) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return Long.toString(overallProgress * 100 / overallTotal) + "%"; - } - - public static String getSpeedString(float bytesPerMillisecond) { - // -- GODOT start -- - return String.format(Locale.ENGLISH, "%.2f", bytesPerMillisecond * 1000 / 1024); - // -- GODOT end -- - } - - public static String getTimeRemaining(long durationInMilliseconds) { - SimpleDateFormat sdf; - if (durationInMilliseconds > 1000 * 60 * 60) { - sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); - } else { - sdf = new SimpleDateFormat("mm:ss", Locale.getDefault()); - } - 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. - * - * @param c the context - * @param mainFile true for main file, false for patch file - * @param versionCode the version of the file - * @return String the file name of the expansion file - */ - public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) { - return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb"; - } - - /** - * Returns the filename (where the file should be saved) from info about a download - */ - static public String generateSaveFileName(Context c, String fileName) { - String path = getSaveFilePath(c) - + File.separator + fileName; - return path; - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - static public String getSaveFilePath(Context c) { - // This technically existed since Honeycomb, but it is critical - // on KitKat and greater versions since it will create the - // directory if needed - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - return c.getObbDir().toString(); - } else { - File root = Environment.getExternalStorageDirectory(); - String path = root.toString() + Constants.EXP_PATH + c.getPackageName(); - return path; - } - } - - /** - * Helper function to ascertain the existence of a file and return true/false appropriately - * - * @param c the app/activity/service context - * @param fileName the name (sans path) of the file to query - * @param fileSize the size that the file must match - * @param deleteFileOnMismatch if the file sizes do not match, delete the file - * @return true if it does exist, false otherwise - */ - static public boolean doesFileExist(Context c, String fileName, long fileSize, - boolean deleteFileOnMismatch) { - // the file may have been delivered by Play --- let's make sure - // it's the size we expect - File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); - if (fileForNewFile.exists()) { - if (fileForNewFile.length() == fileSize) { - return true; - } - if (deleteFileOnMismatch) { - // delete the file --- we won't be able to resume - // because we cannot confirm the integrity of the file - fileForNewFile.delete(); - } - } - return false; - } - - public static final int FS_READABLE = 0; - public static final int FS_DOES_NOT_EXIST = 1; - public static final int FS_CANNOT_READ = 2; - - /** - * Helper function to ascertain whether a file can be read. - * - * @param c the app/activity/service context - * @param fileName the name (sans path) of the file to query - * @return true if it does exist, false otherwise - */ - static public int getFileStatus(Context c, String fileName) { - // the file may have been delivered by Play --- let's make sure - // it's the size we expect - File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); - int returnValue; - if (fileForNewFile.exists()) { - if (fileForNewFile.canRead()) { - returnValue = FS_READABLE; - } else { - returnValue = FS_CANNOT_READ; - } - } else { - returnValue = FS_DOES_NOT_EXIST; - } - return returnValue; - } - - /** - * Helper function to ascertain whether the application has the correct access to the OBB - * directory to allow an OBB file to be written. - * - * @param c the app/activity/service context - * @return true if the application can write an OBB file, false otherwise - */ - static public boolean canWriteOBBFile(Context c) { - String path = getSaveFilePath(c); - File fileForNewFile = new File(path); - boolean canWrite; - if (fileForNewFile.exists()) { - canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite(); - } else { - canWrite = fileForNewFile.mkdirs(); - } - return canWrite; - } - - /** - * Converts download states that are returned by the - * {@link IDownloaderClient#onDownloadStateChanged} callback into usable strings. This is useful - * if using the state strings built into the library to display user messages. - * - * @param state One of the STATE_* constants from {@link IDownloaderClient}. - * @return string resource ID for the corresponding string. - */ - static public int getDownloaderStringResourceIDFromState(int state) { - switch (state) { - case IDownloaderClient.STATE_IDLE: - return R.string.state_idle; - case IDownloaderClient.STATE_FETCHING_URL: - return R.string.state_fetching_url; - case IDownloaderClient.STATE_CONNECTING: - return R.string.state_connecting; - case IDownloaderClient.STATE_DOWNLOADING: - return R.string.state_downloading; - case IDownloaderClient.STATE_COMPLETED: - return R.string.state_completed; - case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE: - return R.string.state_paused_network_unavailable; - case IDownloaderClient.STATE_PAUSED_BY_REQUEST: - return R.string.state_paused_by_request; - case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: - return R.string.state_paused_wifi_disabled; - case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: - return R.string.state_paused_wifi_unavailable; - case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED: - return R.string.state_paused_wifi_disabled; - case IDownloaderClient.STATE_PAUSED_NEED_WIFI: - return R.string.state_paused_wifi_unavailable; - case IDownloaderClient.STATE_PAUSED_ROAMING: - return R.string.state_paused_roaming; - case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE: - return R.string.state_paused_network_setup_failure; - case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: - return R.string.state_paused_sdcard_unavailable; - case IDownloaderClient.STATE_FAILED_UNLICENSED: - return R.string.state_failed_unlicensed; - case IDownloaderClient.STATE_FAILED_FETCHING_URL: - return R.string.state_failed_fetching_url; - case IDownloaderClient.STATE_FAILED_SDCARD_FULL: - return R.string.state_failed_sdcard_full; - case IDownloaderClient.STATE_FAILED_CANCELED: - return R.string.state_failed_cancelled; - default: - return R.string.state_unknown; - } - } - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java deleted file mode 100644 index cef3794701..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.os.Messenger; - -/** - * This interface should be implemented by the client activity for the - * downloader. It is used to pass status from the service to the client. - */ -public interface IDownloaderClient { - static final int STATE_IDLE = 1; - static final int STATE_FETCHING_URL = 2; - static final int STATE_CONNECTING = 3; - static final int STATE_DOWNLOADING = 4; - static final int STATE_COMPLETED = 5; - - static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6; - static final int STATE_PAUSED_BY_REQUEST = 7; - - /** - * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and - * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and - * 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 - * 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_NEED_CELLULAR_PERMISSION = 9; - - /** - * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that - * Wi-Fi is unavailable and cellular permission will NOT restart the - * service. Wi-Fi disabled means that the Wi-Fi manager is returning that - * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not - * available. - * <p> - * The service does not return these values. We recommend that app - * developers with very large payloads do not allow these payloads to be - * downloaded over cellular connections. - */ - static final int STATE_PAUSED_WIFI_DISABLED = 10; - static final int STATE_PAUSED_NEED_WIFI = 11; - - static final int STATE_PAUSED_ROAMING = 12; - - /** - * Scary case. We were on a network that redirected us to another website - * that delivered us the wrong file. - */ - static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13; - - static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; - - static final int STATE_FAILED_UNLICENSED = 15; - static final int STATE_FAILED_FETCHING_URL = 16; - static final int STATE_FAILED_SDCARD_FULL = 17; - static final int STATE_FAILED_CANCELED = 18; - - static final int STATE_FAILED = 19; - - /** - * Called internally by the stub when the service is bound to the client. - * <p> - * Critical implementation detail. In onServiceConnected we create the - * remote service and marshaler. This is how we pass the client information - * back to the service so the client can be properly notified of changes. We - * must do this every time we reconnect to the service. - * <p> - * That is, when you receive this callback, you should call - * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member - * instance of {@link IDownloaderService}, then call - * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved - * from your {@link IStub} proxy object. - * - * @param m the service Messenger. This Messenger is used to call the - * service API from the client. - */ - void onServiceConnected(Messenger m); - - /** - * 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 - * middle of a user request, so the client should be able to handle this. - * <p> - * The Downloader Library includes a collection of string resources that - * correspond to each of the states, which you can use to provide users a - * useful message based on the state provided in this callback. To fetch the - * appropriate string for a state, call - * {@link Helpers#getDownloaderStringResourceIDFromState}. - * <p> - * What this means to the developer: The application has gotten a message - * that the download has paused due to lack of WiFi. The developer should - * then show UI asking the user if they want to enable downloading over - * cellular connections with appropriate warnings. If the application - * suddenly starts downloading, the application should revert to showing the - * progress again, rather than leaving up the download over cellular UI up. - * - * @param newState one of the STATE_* values defined in IDownloaderClient - */ - void onDownloadStateChanged(int newState); - - /** - * 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 - * STATE_DOWNLOADING. - * - * @param progress the DownloadProgressInfo object containing the current - * progress of all downloads. - */ - void onDownloadProgress(DownloadProgressInfo progress); -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java deleted file mode 100644 index 4de9de0c62..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; -import android.os.Messenger; - -/** - * This interface is implemented by the DownloaderService and by the - * DownloaderServiceMarshaller. It contains functions to control the service. - * When a client binds to the service, it must call the onClientUpdated - * function. - * <p> - * You can acquire a proxy that implements this interface for your service by - * calling {@link DownloaderServiceMarshaller#CreateProxy} during the - * {@link IDownloaderClient#onServiceConnected} callback. At which point, you - * should immediately call {@link #onClientUpdated}. - */ -public interface IDownloaderService { - /** - * Set this flag in response to the - * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then - * call RequestContinueDownload to resume a download - */ - public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1; - - /** - * Request that the service abort the current download. The service should - * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}. - */ - void requestAbortDownload(); - - /** - * Request that the service pause the current download. The service should - * respond by changing the state to - * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. - */ - void requestPauseDownload(); - - /** - * Request that the service continue a paused download, when in any paused - * or failed state, including - * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. - */ - void requestContinueDownload(); - - /** - * Set the flags for this download (e.g. - * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). - * - * @param flags - */ - void setDownloadFlags(int flags); - - /** - * Requests that the download status be sent to the client. - */ - void requestDownloadStatus(); - - /** - * Call this when you get {@link - * IDownloaderClient.onServiceConnected(Messenger m)} from the - * DownloaderClient to register the client with the service. It will - * automatically send the current status to the client. - * - * @param clientMessenger - */ - void onClientUpdated(Messenger clientMessenger); -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java deleted file mode 100644 index d5bc3a843e..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.content.Context; -import android.os.Messenger; - -/** - * This is the interface that is used to connect/disconnect from the downloader - * service. - * <p> - * You should get a proxy object that implements this interface by calling - * {@link DownloaderClientMarshaller#CreateStub} in your activity when the - * downloader service starts. Then, call {@link #connect} during your activity's - * onResume() and call {@link #disconnect} during onStop(). - * <p> - * Then during the {@link IDownloaderClient#onServiceConnected} callback, you - * should call {@link #getMessenger} to pass the stub's Messenger object to - * {@link IDownloaderService#onClientUpdated}. - */ -public interface IStub { - Messenger getMessenger(); - - void connect(Context c); - - void disconnect(Context c); -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java deleted file mode 100644 index a0e1165cc4..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.telephony.TelephonyManager; -import android.util.Log; - -// -- GODOT start -- -import android.annotation.SuppressLint; -// -- GODOT end -- - -/** - * Contains useful helper functions, typically tied to the application context. - */ -class SystemFacade { - private Context mContext; - private NotificationManager mNotificationManager; - - public SystemFacade(Context context) { - mContext = context; - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - } - - public long currentTimeMillis() { - return System.currentTimeMillis(); - } - - public Integer getActiveNetworkType() { - ConnectivityManager connectivity = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - return null; - } - - @SuppressLint("MissingPermission") - NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); - if (activeInfo == null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is not available"); - } - return null; - } - return activeInfo.getType(); - } - - public boolean isNetworkRoaming() { - ConnectivityManager connectivity = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - return false; - } - - @SuppressLint("MissingPermission") - NetworkInfo info = connectivity.getActiveNetworkInfo(); - boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); - TelephonyManager tm = (TelephonyManager) mContext - .getSystemService(Context.TELEPHONY_SERVICE); - if (null == tm) { - Log.w(Constants.TAG, "couldn't get telephony manager"); - return false; - } - boolean isRoaming = isMobile && tm.isNetworkRoaming(); - if (Constants.LOGVV && isRoaming) { - Log.v(Constants.TAG, "network is roaming"); - } - return isRoaming; - } - - public Long getMaxBytesOverMobile() { - return (long) Integer.MAX_VALUE; - } - - public Long getRecommendedMaxBytesOverMobile() { - return 2097152L; - } - - public void sendBroadcast(Intent intent) { - mContext.sendBroadcast(intent); - } - - public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException { - return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid; - } - - public void postNotification(long id, Notification notification) { - /** - * TODO: The system notification manager takes ints, not longs, as IDs, - * but the download manager uses IDs take straight from the database, - * which are longs. This will have to be dealt with at some point. - */ - mNotificationManager.notify((int) id, notification); - } - - public void cancelNotification(long id) { - mNotificationManager.cancel((int) id); - } - - public void cancelAllNotifications() { - mNotificationManager.cancelAll(); - } - - public void startThread(Thread thread) { - thread.start(); - } -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java deleted file mode 100644 index 3ccc191c60..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.app.Service; -import android.content.Intent; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.util.Log; - -/** - * This service differs from IntentService in a few minor ways/ It will not - * auto-stop itself after the intent is handled unless the target returns "true" - * in should stop. Since the goal of this service is to handle a single kind of - * intent, it does not queue up batches of intents of the same type. - */ -public abstract class CustomIntentService extends Service { - private String mName; - private boolean mRedelivery; - private volatile ServiceHandler mServiceHandler; - private volatile Looper mServiceLooper; - private static final String LOG_TAG = "CustomIntentService"; - private static final int WHAT_MESSAGE = -10; - - public CustomIntentService(String paramString) { - this.mName = paramString; - } - - @Override - public IBinder onBind(Intent paramIntent) { - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - HandlerThread localHandlerThread = new HandlerThread("IntentService[" - + this.mName + "]"); - localHandlerThread.start(); - this.mServiceLooper = localHandlerThread.getLooper(); - this.mServiceHandler = new ServiceHandler(this.mServiceLooper); - } - - @Override - public void onDestroy() { - Thread localThread = this.mServiceLooper.getThread(); - if ((localThread != null) && (localThread.isAlive())) { - localThread.interrupt(); - } - this.mServiceLooper.quit(); - Log.d(LOG_TAG, "onDestroy"); - } - - protected abstract void onHandleIntent(Intent paramIntent); - - protected abstract boolean shouldStop(); - - @Override - public void onStart(Intent paramIntent, int startId) { - if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) { - Message localMessage = this.mServiceHandler.obtainMessage(); - localMessage.arg1 = startId; - localMessage.obj = paramIntent; - localMessage.what = WHAT_MESSAGE; - this.mServiceHandler.sendMessage(localMessage); - } - } - - @Override - public int onStartCommand(Intent paramIntent, int flags, int startId) { - onStart(paramIntent, startId); - return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; - } - - public void setIntentRedelivery(boolean enabled) { - this.mRedelivery = enabled; - } - - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message paramMessage) { - CustomIntentService.this - .onHandleIntent((Intent) paramMessage.obj); - if (shouldStop()) { - Log.d(LOG_TAG, "stopSelf"); - CustomIntentService.this.stopSelf(paramMessage.arg1); - Log.d(LOG_TAG, "afterStopSelf"); - } - } - } -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java deleted file mode 100644 index 45111b16a3..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.Helpers; - -import android.util.Log; - -/** - * Representation of information about an individual download from the database. - */ -public class DownloadInfo { - public String mUri; - public final int mIndex; - public final String mFileName; - public String mETag; - public long mTotalBytes; - public long mCurrentBytes; - public long mLastMod; - public int mStatus; - public int mControl; - public int mNumFailed; - public int mRetryAfter; - public int mRedirectCount; - - boolean mInitialized; - - public int mFuzz; - - public DownloadInfo(int index, String fileName, String pkg) { - mFuzz = Helpers.sRandom.nextInt(1001); - mFileName = fileName; - mIndex = index; - } - - public void resetDownload() { - mCurrentBytes = 0; - mETag = ""; - mLastMod = 0; - mStatus = 0; - mControl = 0; - mNumFailed = 0; - mRetryAfter = 0; - mRedirectCount = 0; - } - - /** - * Returns the time when a download should be restarted. - */ - public long restartTime(long now) { - if (mNumFailed == 0) { - return now; - } - if (mRetryAfter > 0) { - return mLastMod + mRetryAfter; - } - return mLastMod + - Constants.RETRY_FIRST_DELAY * - (1000 + mFuzz) * (1 << (mNumFailed - 1)); - } - - public void logVerboseInfo() { - Log.v(Constants.TAG, "Service adding new entry"); - Log.v(Constants.TAG, "FILENAME: " + mFileName); - Log.v(Constants.TAG, "URI : " + mUri); - Log.v(Constants.TAG, "FILENAME: " + mFileName); - Log.v(Constants.TAG, "CONTROL : " + mControl); - Log.v(Constants.TAG, "STATUS : " + mStatus); - Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); - Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); - Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); - Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); - Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); - Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); - Log.v(Constants.TAG, "ETAG : " + mETag); - } -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java deleted file mode 100644 index 4b214b22d7..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -// -- GODOT start -- -//import com.android.vending.expansion.downloader.R; -import com.godot.game.R; -// -- GODOT end -- - -import com.google.android.vending.expansion.downloader.DownloadProgressInfo; -import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.os.Build; -import android.os.Messenger; -import android.support.v4.app.NotificationCompat; - -/** - * This class handles displaying the notification associated with the download - * queue going on in the download manager. It handles multiple status types; - * Some require user interaction and some do not. Some of the user interactions - * may be transient. (for example: the user is queried to continue the download - * on 3G when it started on WiFi, but then the phone locks onto WiFi again so - * the prompt automatically goes away) - * <p/> - * The application interface for the downloader also needs to understand and - * handle these transient states. - */ -public class DownloadNotification implements IDownloaderClient { - - private int mState; - private final Context mContext; - private final NotificationManager mNotificationManager; - private CharSequence mCurrentTitle; - - private IDownloaderClient mClientProxy; - private NotificationCompat.Builder mActiveDownloadBuilder; - private NotificationCompat.Builder mBuilder; - private NotificationCompat.Builder mCurrentBuilder; - private CharSequence mLabel; - private String mCurrentText; - private DownloadProgressInfo mProgressInfo; - private PendingIntent mContentIntent; - - static final String LOGTAG = "DownloadNotification"; - static final int NOTIFICATION_ID = LOGTAG.hashCode(); - - public PendingIntent getClientIntent() { - return mContentIntent; - } - - public void setClientIntent(PendingIntent clientIntent) { - this.mBuilder.setContentIntent(clientIntent); - this.mActiveDownloadBuilder.setContentIntent(clientIntent); - this.mContentIntent = clientIntent; - } - - public void resendState() { - if (null != mClientProxy) { - mClientProxy.onDownloadStateChanged(mState); - } - } - - @Override - public void onDownloadStateChanged(int newState) { - if (null != mClientProxy) { - mClientProxy.onDownloadStateChanged(newState); - } - if (newState != mState) { - mState = newState; - if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) { - return; - } - int stringDownloadID; - int iconResource; - boolean ongoingEvent; - - // get the new title string and paused text - switch (newState) { - case 0: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = R.string.state_unknown; - ongoingEvent = false; - break; - - case IDownloaderClient.STATE_DOWNLOADING: - iconResource = android.R.drawable.stat_sys_download; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - - case IDownloaderClient.STATE_FETCHING_URL: - case IDownloaderClient.STATE_CONNECTING: - iconResource = android.R.drawable.stat_sys_download_done; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - - case IDownloaderClient.STATE_COMPLETED: - case IDownloaderClient.STATE_PAUSED_BY_REQUEST: - iconResource = android.R.drawable.stat_sys_download_done; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = false; - break; - - case IDownloaderClient.STATE_FAILED: - case IDownloaderClient.STATE_FAILED_CANCELED: - case IDownloaderClient.STATE_FAILED_FETCHING_URL: - case IDownloaderClient.STATE_FAILED_SDCARD_FULL: - case IDownloaderClient.STATE_FAILED_UNLICENSED: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = false; - break; - - default: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - } - - mCurrentText = mContext.getString(stringDownloadID); - mCurrentTitle = mLabel; - mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText); - mCurrentBuilder.setSmallIcon(iconResource); - mCurrentBuilder.setContentTitle(mCurrentTitle); - mCurrentBuilder.setContentText(mCurrentText); - if (ongoingEvent) { - mCurrentBuilder.setOngoing(true); - } else { - mCurrentBuilder.setOngoing(false); - mCurrentBuilder.setAutoCancel(true); - } - mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build()); - } - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - mProgressInfo = progress; - if (null != mClientProxy) { - mClientProxy.onDownloadProgress(progress); - } - if (progress.mOverallTotal <= 0) { - // we just show the text - mBuilder.setTicker(mCurrentTitle); - mBuilder.setSmallIcon(android.R.drawable.stat_sys_download); - mBuilder.setContentTitle(mCurrentTitle); - mBuilder.setContentText(mCurrentText); - mCurrentBuilder = mBuilder; - } else { - mActiveDownloadBuilder.setProgress((int) progress.mOverallTotal, (int) progress.mOverallProgress, false); - mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal)); - mActiveDownloadBuilder.setSmallIcon(android.R.drawable.stat_sys_download); - mActiveDownloadBuilder.setTicker(mLabel + ": " + mCurrentText); - mActiveDownloadBuilder.setContentTitle(mLabel); - mActiveDownloadBuilder.setContentInfo(mContext.getString(R.string.time_remaining_notification, - Helpers.getTimeRemaining(progress.mTimeRemaining))); - mCurrentBuilder = mActiveDownloadBuilder; - } - mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build()); - } - - /** - * Called in response to onClientUpdated. Creates a new proxy and notifies - * it of the current state. - * - * @param msg the client Messenger to notify - */ - public void setMessenger(Messenger msg) { - mClientProxy = DownloaderClientMarshaller.CreateProxy(msg); - if (null != mProgressInfo) { - mClientProxy.onDownloadProgress(mProgressInfo); - } - if (mState != -1) { - mClientProxy.onDownloadStateChanged(mState); - } - } - - /** - * Constructor - * - * @param ctx The context to use to obtain access to the Notification - * Service - */ - DownloadNotification(Context ctx, CharSequence applicationLabel) { - mState = -1; - mContext = ctx; - mLabel = applicationLabel; - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - mActiveDownloadBuilder = new NotificationCompat.Builder(ctx); - mBuilder = new NotificationCompat.Builder(ctx); - - // Set Notification category and priorities to something that makes sense for a long - // lived background task. - mActiveDownloadBuilder.setPriority(NotificationCompat.PRIORITY_LOW); - mActiveDownloadBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS); - - mBuilder.setPriority(NotificationCompat.PRIORITY_LOW); - mBuilder.setCategory(NotificationCompat.CATEGORY_PROGRESS); - - mCurrentBuilder = mBuilder; - } - - @Override - public void onServiceConnected(Messenger m) { - } - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java deleted file mode 100644 index c114b8a64a..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java +++ /dev/null @@ -1,852 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; - -import android.content.Context; -import android.os.PowerManager; -import android.os.Process; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.SyncFailedException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Locale; - -/** - * Runs an actual download - */ -public class DownloadThread { - - private Context mContext; - private DownloadInfo mInfo; - private DownloaderService mService; - private final DownloadsDB mDB; - private final DownloadNotification mNotification; - private String mUserAgent; - - public DownloadThread(DownloadInfo info, DownloaderService service, - DownloadNotification notification) { - mContext = service; - mInfo = info; - mService = service; - mNotification = notification; - mDB = DownloadsDB.getDB(service); - mUserAgent = "APKXDL (Linux; U; Android " + android.os.Build.VERSION.RELEASE + ";" - + Locale.getDefault().toString() + "; " + android.os.Build.DEVICE + "/" - + android.os.Build.ID + ")" + - service.getPackageName(); - } - - /** - * Returns the default user agent - */ - private String userAgent() { - return mUserAgent; - } - - /** - * State for the entire run() method. - */ - private static class State { - public String mFilename; - public FileOutputStream mStream; - public boolean mCountRetry = false; - public int mRetryAfter = 0; - public int mRedirectCount = 0; - public String mNewUri; - public boolean mGotData = false; - public String mRequestUri; - - public State(DownloadInfo info, DownloaderService service) { - mRedirectCount = info.mRedirectCount; - mRequestUri = info.mUri; - mFilename = service.generateTempSaveFileName(info.mFileName); - } - } - - /** - * State within executeDownload() - */ - private static class InnerState { - public int mBytesSoFar = 0; - public int mBytesThisSession = 0; - public String mHeaderETag; - public boolean mContinuingDownload = false; - public String mHeaderContentLength; - public String mHeaderContentDisposition; - public String mHeaderContentLocation; - public int mBytesNotified = 0; - public long mTimeLastNotification = 0; - } - - /** - * Raised from methods called by run() to indicate that the current request - * should be stopped immediately. Note the message passed to this exception - * will be logged and therefore must be guaranteed not to contain any PII, - * meaning it generally can't include any information about the request URI, - * headers, or destination filename. - */ - private class StopRequest extends Throwable { - - private static final long serialVersionUID = 6338592678988347973L; - public int mFinalStatus; - - public StopRequest(int finalStatus, String message) { - super(message); - mFinalStatus = finalStatus; - } - - public StopRequest(int finalStatus, String message, Throwable throwable) { - super(message, throwable); - mFinalStatus = finalStatus; - } - } - - /** - * Raised from methods called by executeDownload() to indicate that the - * download should be retried immediately. - */ - private class RetryDownload extends Throwable { - - private static final long serialVersionUID = 6196036036517540229L; - } - - /** - * Executes the download in a separate thread - */ - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - State state = new State(mInfo, mService); - PowerManager.WakeLock wakeLock = null; - int finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; - - try { - PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - // -- GODOT start -- - //wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); - //wakeLock.acquire(); - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "org.godot.game:wakelock"); - wakeLock.acquire(20 * 60 * 1000L /*20 minutes*/); - // -- GODOT end -- - - if (Constants.LOGV) { - Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - - boolean finished = false; - while (!finished) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - // Set or unset proxy, which may have changed since last GET - // request. - // setDefaultProxy() supports null as proxy parameter. - URL url = new URL(state.mRequestUri); - HttpURLConnection request = (HttpURLConnection)url.openConnection(); - request.setRequestProperty("User-Agent", userAgent()); - try { - executeDownload(state, request); - finished = true; - } catch (RetryDownload exc) { - // fall through - } finally { - request.disconnect(); - request = null; - } - } - - if (Constants.LOGV) { - Log.v(Constants.TAG, "download completed for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - finalizeDestinationFile(state); - finalStatus = DownloaderService.STATUS_SUCCESS; - } catch (StopRequest error) { - // remove the cause before printing, in case it contains PII - Log.w(Constants.TAG, - "Aborting request for download " + mInfo.mFileName + ": " + error.getMessage()); - error.printStackTrace(); - finalStatus = error.mFinalStatus; - // fall through to finally block - } catch (Throwable ex) { // sometimes the socket code throws unchecked - // exceptions - Log.w(Constants.TAG, "Exception for " + mInfo.mFileName + ": " + ex); - finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; - // falls through to the code that reports an error - } finally { - if (wakeLock != null) { - wakeLock.release(); - wakeLock = null; - } - cleanupDestination(state, finalStatus); - notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter, - state.mRedirectCount, state.mGotData, state.mFilename); - } - } - - /** - * Fully execute a single download request - setup and send the request, - * handle the response, and transfer the data to the destination file. - */ - private void executeDownload(State state, HttpURLConnection request) - throws StopRequest, RetryDownload { - InnerState innerState = new InnerState(); - byte data[] = new byte[Constants.BUFFER_SIZE]; - - checkPausedOrCanceled(state); - - setupDestinationFile(state, innerState); - addRequestHeaders(innerState, request); - - // check just before sending the request to avoid using an invalid - // connection at all - checkConnectivity(state); - - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING); - int responseCode = sendRequest(state, request); - handleExceptionalStatus(state, innerState, request, responseCode); - - if (Constants.LOGV) { - Log.v(Constants.TAG, "received response for " + mInfo.mUri); - } - - processResponseHeaders(state, innerState, request); - InputStream entityStream = openResponseEntity(state, request); - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_DOWNLOADING); - transferData(state, innerState, data, entityStream); - } - - /** - * Check if current connectivity is valid for this request. - */ - private void checkConnectivity(State state) throws StopRequest { - switch (mService.getNetworkAvailabilityState(mDB)) { - case DownloaderService.NETWORK_OK: - return; - case DownloaderService.NETWORK_NO_CONNECTION: - throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, - "waiting for network to return"); - case DownloaderService.NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: - throw new StopRequest( - DownloaderService.STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION, - "waiting for wifi or for download over cellular to be authorized"); - case DownloaderService.NETWORK_CANNOT_USE_ROAMING: - throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, - "roaming is not allowed"); - case DownloaderService.NETWORK_UNUSABLE_DUE_TO_SIZE: - throw new StopRequest(DownloaderService.STATUS_QUEUED_FOR_WIFI, "waiting for wifi"); - } - } - - /** - * Transfer as much data as possible from the HTTP response to the - * destination file. - * - * @param data buffer to use to read data - * @param entityStream stream for reading the HTTP response entity - */ - private void transferData(State state, InnerState innerState, byte[] data, - InputStream entityStream) throws StopRequest { - for (;;) { - int bytesRead = readFromResponse(state, innerState, data, entityStream); - if (bytesRead == -1) { // success, end of stream already reached - handleEndOfStream(state, innerState); - return; - } - - state.mGotData = true; - writeDataToDestination(state, data, bytesRead); - innerState.mBytesSoFar += bytesRead; - innerState.mBytesThisSession += bytesRead; - reportProgress(state, innerState); - - checkPausedOrCanceled(state); - } - } - - /** - * Called after a successful completion to take any necessary action on the - * downloaded file. - */ - private void finalizeDestinationFile(State state) throws StopRequest { - syncDestination(state); - String tempFilename = state.mFilename; - String finalFilename = Helpers.generateSaveFileName(mService, mInfo.mFileName); - if (!state.mFilename.equals(finalFilename)) { - File startFile = new File(tempFilename); - File destFile = new File(finalFilename); - if (mInfo.mTotalBytes != -1 && mInfo.mCurrentBytes == mInfo.mTotalBytes) { - if (!startFile.renameTo(destFile)) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "unable to finalize destination file"); - } - } else { - throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, - "file delivered with incorrect size. probably due to network not browser configured"); - } - } - } - - /** - * Called just before the thread finishes, regardless of status, to take any - * necessary action on the downloaded file. - */ - private void cleanupDestination(State state, int finalStatus) { - closeDestination(state); - if (state.mFilename != null && DownloaderService.isStatusError(finalStatus)) { - new File(state.mFilename).delete(); - state.mFilename = null; - } - } - - /** - * Sync the destination file to storage. - */ - private void syncDestination(State state) { - FileOutputStream downloadedFileStream = null; - try { - downloadedFileStream = new FileOutputStream(state.mFilename, true); - downloadedFileStream.getFD().sync(); - } catch (FileNotFoundException ex) { - Log.w(Constants.TAG, "file " + state.mFilename + " not found: " + ex); - } catch (SyncFailedException ex) { - Log.w(Constants.TAG, "file " + state.mFilename + " sync failed: " + ex); - } catch (IOException ex) { - Log.w(Constants.TAG, "IOException trying to sync " + state.mFilename + ": " + ex); - } catch (RuntimeException ex) { - Log.w(Constants.TAG, "exception while syncing file: ", ex); - } finally { - if (downloadedFileStream != null) { - try { - downloadedFileStream.close(); - } catch (IOException ex) { - Log.w(Constants.TAG, "IOException while closing synced file: ", ex); - } catch (RuntimeException ex) { - Log.w(Constants.TAG, "exception while closing file: ", ex); - } - } - } - } - - /** - * Close the destination output stream. - */ - private void closeDestination(State state) { - try { - // close the file - if (state.mStream != null) { - state.mStream.close(); - state.mStream = null; - } - } catch (IOException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "exception when closing the file after download : " + ex); - } - // nothing can really be done if the file can't be closed - } - } - - /** - * Check if the download has been paused or canceled, stopping the request - * appropriately if it has been. - */ - private void checkPausedOrCanceled(State state) throws StopRequest { - if (mService.getControl() == DownloaderService.CONTROL_PAUSED) { - int status = mService.getStatus(); - switch (status) { - case DownloaderService.STATUS_PAUSED_BY_APP: - throw new StopRequest(mService.getStatus(), - "download paused"); - } - } - } - - /** - * Report download progress through the database if necessary. - */ - private void reportProgress(State state, InnerState innerState) { - long now = System.currentTimeMillis(); - if (innerState.mBytesSoFar - innerState.mBytesNotified - > Constants.MIN_PROGRESS_STEP - && now - innerState.mTimeLastNotification - > Constants.MIN_PROGRESS_TIME) { - // we store progress updates to the database here - mInfo.mCurrentBytes = innerState.mBytesSoFar; - mDB.updateDownloadCurrentBytes(mInfo); - - innerState.mBytesNotified = innerState.mBytesSoFar; - innerState.mTimeLastNotification = now; - - long totalBytesSoFar = innerState.mBytesThisSession + mService.mBytesSoFar; - - if (Constants.LOGVV) { - Log.v(Constants.TAG, "downloaded " + mInfo.mCurrentBytes + " out of " - + mInfo.mTotalBytes); - Log.v(Constants.TAG, " total " + totalBytesSoFar + " out of " - + mService.mTotalLength); - } - - mService.notifyUpdateBytes(totalBytesSoFar); - } - } - - /** - * Write a data buffer to the destination file. - * - * @param data buffer containing the data to write - * @param bytesRead how many bytes to write from the buffer - */ - private void writeDataToDestination(State state, byte[] data, int bytesRead) - throws StopRequest { - for (;;) { - try { - if (state.mStream == null) { - state.mStream = new FileOutputStream(state.mFilename, true); - } - state.mStream.write(data, 0, bytesRead); - // we close after every write --- this may be too inefficient - closeDestination(state); - return; - } catch (IOException ex) { - if (!Helpers.isExternalMediaMounted()) { - throw new StopRequest(DownloaderService.STATUS_DEVICE_NOT_FOUND_ERROR, - "external media not mounted while writing destination file"); - } - - long availableBytes = - Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename)); - if (availableBytes < bytesRead) { - throw new StopRequest(DownloaderService.STATUS_INSUFFICIENT_SPACE_ERROR, - "insufficient space while writing destination file", ex); - } - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while writing destination file: " + ex.toString(), ex); - } - } - } - - /** - * Called when we've reached the end of the HTTP response stream, to update - * the database and check for consistency. - */ - private void handleEndOfStream(State state, InnerState innerState) throws StopRequest { - mInfo.mCurrentBytes = innerState.mBytesSoFar; - // this should always be set from the market - // if ( innerState.mHeaderContentLength == null ) { - // mInfo.mTotalBytes = innerState.mBytesSoFar; - // } - mDB.updateDownload(mInfo); - - boolean lengthMismatched = (innerState.mHeaderContentLength != null) - && (innerState.mBytesSoFar != Integer.parseInt(innerState.mHeaderContentLength)); - if (lengthMismatched) { - if (cannotResume(innerState)) { - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - "mismatched content length"); - } else { - throw new StopRequest(getFinalStatusForHttpError(state), - "closed socket before end of file"); - } - } - } - - private boolean cannotResume(InnerState innerState) { - return innerState.mBytesSoFar > 0 && innerState.mHeaderETag == null; - } - - /** - * Read some data from the HTTP response stream, handling I/O errors. - * - * @param data buffer to use to read data - * @param entityStream stream for reading the HTTP response entity - * @return the number of bytes actually read or -1 if the end of the stream - * has been reached - */ - private int readFromResponse(State state, InnerState innerState, byte[] data, - InputStream entityStream) throws StopRequest { - try { - return entityStream.read(data); - } catch (IOException ex) { - logNetworkState(); - mInfo.mCurrentBytes = innerState.mBytesSoFar; - mDB.updateDownload(mInfo); - if (cannotResume(innerState)) { - String message = "while reading response: " + ex.toString() - + ", can't resume interrupted download with no ETag"; - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - message, ex); - } else { - throw new StopRequest(getFinalStatusForHttpError(state), - "while reading response: " + ex.toString(), ex); - } - } - } - - /** - * Open a stream for the HTTP response entity, handling I/O errors. - * - * @return an InputStream to read the response entity - */ - private InputStream openResponseEntity(State state, HttpURLConnection response) - throws StopRequest { - try { - return response.getInputStream(); - } catch (IOException ex) { - logNetworkState(); - throw new StopRequest(getFinalStatusForHttpError(state), - "while getting entity: " + ex.toString(), ex); - } - } - - private void logNetworkState() { - if (Constants.LOGX) { - Log.i(Constants.TAG, - "Net " - + (mService.getNetworkAvailabilityState(mDB) == DownloaderService.NETWORK_OK ? "Up" - : "Down")); - } - } - - /** - * Read HTTP response headers and take appropriate action, including setting - * up the destination file and updating the database. - */ - private void processResponseHeaders(State state, InnerState innerState, HttpURLConnection response) - throws StopRequest { - if (innerState.mContinuingDownload) { - // ignore response headers on resume requests - return; - } - - readResponseHeaders(state, innerState, response); - - try { - state.mFilename = mService.generateSaveFile(mInfo.mFileName, mInfo.mTotalBytes); - } catch (DownloaderService.GenerateSaveFileError exc) { - throw new StopRequest(exc.mStatus, exc.mMessage); - } - try { - state.mStream = new FileOutputStream(state.mFilename); - } catch (FileNotFoundException exc) { - // make sure the directory exists - File pathFile = new File(Helpers.getSaveFilePath(mService)); - try { - if (pathFile.mkdirs()) { - state.mStream = new FileOutputStream(state.mFilename); - } - } catch (Exception ex) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while opening destination file: " + exc.toString(), exc); - } - } - if (Constants.LOGV) { - Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + state.mFilename); - } - - updateDatabaseFromHeaders(state, innerState); - // check connectivity again now that we know the total size - checkConnectivity(state); - } - - /** - * Update necessary database fields based on values of HTTP response headers - * that have been read. - */ - private void updateDatabaseFromHeaders(State state, InnerState innerState) { - mInfo.mETag = innerState.mHeaderETag; - mDB.updateDownload(mInfo); - } - - /** - * Read headers from the HTTP response and store them into local state. - */ - private void readResponseHeaders(State state, InnerState innerState, HttpURLConnection response) - throws StopRequest { - String value = response.getHeaderField("Content-Disposition"); - if (value != null) { - innerState.mHeaderContentDisposition = value; - } - value = response.getHeaderField("Content-Location"); - if (value != null) { - innerState.mHeaderContentLocation = value; - } - value = response.getHeaderField("ETag"); - if (value != null) { - innerState.mHeaderETag = value; - } - String headerTransferEncoding = null; - value = response.getHeaderField("Transfer-Encoding"); - if (value != null) { - headerTransferEncoding = value; - } - String headerContentType = null; - value = response.getHeaderField("Content-Type"); - if (value != null) { - headerContentType = value; - if (!headerContentType.equals("application/vnd.android.obb")) { - throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, - "file delivered with incorrect Mime type"); - } - } - - if (headerTransferEncoding == null) { - long contentLength = response.getContentLength(); - if (value != null) { - // this is always set from Market - if (contentLength != -1 && contentLength != mInfo.mTotalBytes) { - // we're most likely on a bad wifi connection -- we should - // probably - // also look at the mime type --- but the size mismatch is - // enough - // to tell us that something is wrong here - Log.e(Constants.TAG, "Incorrect file size delivered."); - } else { - innerState.mHeaderContentLength = Long.toString(contentLength); - } - } - } else { - // Ignore content-length with transfer-encoding - 2616 4.4 3 - if (Constants.LOGVV) { - Log.v(Constants.TAG, - "ignoring content-length because of xfer-encoding"); - } - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Content-Disposition: " + - innerState.mHeaderContentDisposition); - Log.v(Constants.TAG, "Content-Length: " + innerState.mHeaderContentLength); - Log.v(Constants.TAG, "Content-Location: " + innerState.mHeaderContentLocation); - Log.v(Constants.TAG, "ETag: " + innerState.mHeaderETag); - Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding); - } - - boolean noSizeInfo = innerState.mHeaderContentLength == null - && (headerTransferEncoding == null - || !headerTransferEncoding.equalsIgnoreCase("chunked")); - if (noSizeInfo) { - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "can't know size of download, giving up"); - } - } - - /** - * Check the HTTP response status and handle anything unusual (e.g. not - * 200/206). - */ - private void handleExceptionalStatus(State state, InnerState innerState, HttpURLConnection connection, int responseCode) - throws StopRequest, RetryDownload { - if (responseCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) { - handleServiceUnavailable(state, connection); - } - int expectedStatus = innerState.mContinuingDownload ? 206 - : DownloaderService.STATUS_SUCCESS; - if (responseCode != expectedStatus) { - handleOtherStatus(state, innerState, responseCode); - } else { - // no longer redirected - state.mRedirectCount = 0; - } - } - - /** - * Handle a status that we don't know how to deal with properly. - */ - private void handleOtherStatus(State state, InnerState innerState, int statusCode) - throws StopRequest { - int finalStatus; - if (DownloaderService.isStatusError(statusCode)) { - finalStatus = statusCode; - } else if (statusCode >= 300 && statusCode < 400) { - finalStatus = DownloaderService.STATUS_UNHANDLED_REDIRECT; - } else if (innerState.mContinuingDownload && statusCode == DownloaderService.STATUS_SUCCESS) { - finalStatus = DownloaderService.STATUS_CANNOT_RESUME; - } else { - finalStatus = DownloaderService.STATUS_UNHANDLED_HTTP_CODE; - } - throw new StopRequest(finalStatus, "http error " + statusCode); - } - - /** - * Add headers for this download to the HTTP request to allow for resume. - */ - private void addRequestHeaders(InnerState innerState, HttpURLConnection request) { - if (innerState.mContinuingDownload) { - if (innerState.mHeaderETag != null) { - request.setRequestProperty("If-Match", innerState.mHeaderETag); - } - request.setRequestProperty("Range", "bytes=" + innerState.mBytesSoFar + "-"); - } - } - - /** - * Handle a 503 Service Unavailable status by processing the Retry-After - * header. - */ - private void handleServiceUnavailable(State state, HttpURLConnection connection) throws StopRequest { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP response code 503"); - } - state.mCountRetry = true; - String retryAfterValue = connection.getHeaderField("Retry-After"); - if (retryAfterValue != null) { - try { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Retry-After :" + retryAfterValue); - } - state.mRetryAfter = Integer.parseInt(retryAfterValue); - if (state.mRetryAfter < 0) { - state.mRetryAfter = 0; - } else { - if (state.mRetryAfter < Constants.MIN_RETRY_AFTER) { - state.mRetryAfter = Constants.MIN_RETRY_AFTER; - } else if (state.mRetryAfter > Constants.MAX_RETRY_AFTER) { - state.mRetryAfter = Constants.MAX_RETRY_AFTER; - } - state.mRetryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1); - state.mRetryAfter *= 1000; - } - } catch (NumberFormatException ex) { - // ignored - retryAfter stays 0 in this case. - } - } - throw new StopRequest(DownloaderService.STATUS_WAITING_TO_RETRY, - "got 503 Service Unavailable, will retry later"); - } - - /** - * Send the request to the server, handling any I/O exceptions. - */ - private int sendRequest(State state, HttpURLConnection request) - throws StopRequest { - try { - return request.getResponseCode(); - } catch (IllegalArgumentException ex) { - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "while trying to execute request: " + ex.toString(), ex); - } catch (IOException ex) { - logNetworkState(); - throw new StopRequest(getFinalStatusForHttpError(state), - "while trying to execute request: " + ex.toString(), ex); - } - } - - private int getFinalStatusForHttpError(State state) { - if (mService.getNetworkAvailabilityState(mDB) != DownloaderService.NETWORK_OK) { - return DownloaderService.STATUS_WAITING_FOR_NETWORK; - } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) { - state.mCountRetry = true; - return DownloaderService.STATUS_WAITING_TO_RETRY; - } else { - Log.w(Constants.TAG, "reached max retries for " + mInfo.mNumFailed); - return DownloaderService.STATUS_HTTP_DATA_ERROR; - } - } - - /** - * Prepare the destination file to receive data. If the file already exists, - * we'll set up appropriately for resumption. - */ - private void setupDestinationFile(State state, InnerState innerState) - throws StopRequest { - if (state.mFilename != null) { // only true if we've already run a - // thread for this download - if (!Helpers.isFilenameValid(state.mFilename)) { - // this should never happen - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "found invalid internal destination filename"); - } - // We're resuming a download that got interrupted - File f = new File(state.mFilename); - if (f.exists()) { - long fileLength = f.length(); - if (fileLength == 0) { - // The download hadn't actually started, we can restart from - // scratch - f.delete(); - state.mFilename = null; - } else if (mInfo.mETag == null) { - // This should've been caught upon failure - f.delete(); - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - "Trying to resume a download that can't be resumed"); - } else { - // All right, we'll be able to resume this download - try { - state.mStream = new FileOutputStream(state.mFilename, true); - } catch (FileNotFoundException exc) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while opening destination for resuming: " + exc.toString(), exc); - } - innerState.mBytesSoFar = (int) fileLength; - if (mInfo.mTotalBytes != -1) { - innerState.mHeaderContentLength = Long.toString(mInfo.mTotalBytes); - } - innerState.mHeaderETag = mInfo.mETag; - innerState.mContinuingDownload = true; - } - } - } - - if (state.mStream != null) { - closeDestination(state); - } - } - - /** - * Stores information about the completed download, and notifies the - * initiating application. - */ - private void notifyDownloadCompleted( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename) { - updateDownloadDatabase( - status, countRetry, retryAfter, redirectCount, gotData, filename); - if (DownloaderService.isStatusCompleted(status)) { - // TBD: send status update? - } - } - - private void updateDownloadDatabase( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename) { - mInfo.mStatus = status; - mInfo.mRetryAfter = retryAfter; - mInfo.mRedirectCount = redirectCount; - mInfo.mLastMod = System.currentTimeMillis(); - if (!countRetry) { - mInfo.mNumFailed = 0; - } else if (gotData) { - mInfo.mNumFailed = 1; - } else { - mInfo.mNumFailed++; - } - mDB.updateDownload(mInfo); - } - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java deleted file mode 100644 index 8d41a76900..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java +++ /dev/null @@ -1,1346 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.DownloadProgressInfo; -import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; -import com.google.android.vending.expansion.downloader.IDownloaderService; -import com.google.android.vending.expansion.downloader.IStub; -import com.google.android.vending.licensing.AESObfuscator; -import com.google.android.vending.licensing.APKExpansionPolicy; -import com.google.android.vending.licensing.LicenseChecker; -import com.google.android.vending.licensing.LicenseCheckerCallback; -import com.google.android.vending.licensing.Policy; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; -import android.os.Handler; -import android.os.IBinder; -import android.os.Messenger; -import android.os.SystemClock; -import android.provider.Settings.Secure; -import android.telephony.TelephonyManager; -import android.util.Log; - -// -- GODOT start -- -import android.annotation.SuppressLint; -// -- GODOT end -- - -import java.io.File; - -/** - * Performs the background downloads requested by applications that use the - * Downloads provider. This service does not run as a foreground task, so - * Android may kill it off at will, but it will try to restart itself if it can. - * Note that Android by default will kill off any process that has an open file - * handle on the shared (SD Card) partition if the partition is unmounted. - */ -public abstract class DownloaderService extends CustomIntentService implements IDownloaderService { - - public DownloaderService() { - super("LVLDownloadService"); - } - - private static final String LOG_TAG = "LVLDL"; - - // the following NETWORK_* constants are used to indicates specific reasons - // for disallowing a - // download from using a network, since specific causes can require special - // handling - - /** - * The network is usable for the given download. - */ - public static final int NETWORK_OK = 1; - - /** - * There is no network connectivity. - */ - public static final int NETWORK_NO_CONNECTION = 2; - - /** - * The download exceeds the maximum size for this network. - */ - public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3; - - /** - * The download exceeds the recommended maximum size for this network, the - * user must confirm for this download to proceed without WiFi. - */ - public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4; - - /** - * The current connection is roaming, and the download can't proceed over a - * roaming connection. - */ - public static final int NETWORK_CANNOT_USE_ROAMING = 5; - - /** - * The app requesting the download specific that it can't use the current - * network connection. - */ - public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6; - - /** - * For intents used to notify the user that a download exceeds a size - * threshold, if this extra is true, WiFi is required for this download - * size; otherwise, it is only recommended. - */ - public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired"; - public static final String EXTRA_FILE_NAME = "downloadId"; - - /** - * Used with DOWNLOAD_STATUS - */ - public static final String EXTRA_STATUS_STATE = "ESS"; - public static final String EXTRA_STATUS_TOTAL_SIZE = "ETS"; - public static final String EXTRA_STATUS_CURRENT_FILE_SIZE = "CFS"; - public static final String EXTRA_STATUS_TOTAL_PROGRESS = "TFP"; - public static final String EXTRA_STATUS_CURRENT_PROGRESS = "CFP"; - - public static final String ACTION_DOWNLOADS_CHANGED = "downloadsChanged"; - - /** - * Broadcast intent action sent by the download manager when a download - * completes. - */ - public final static String ACTION_DOWNLOAD_COMPLETE = "lvldownloader.intent.action.DOWNLOAD_COMPLETE"; - - /** - * Broadcast intent action sent by the download manager when download status - * changes. - */ - public final static String ACTION_DOWNLOAD_STATUS = "lvldownloader.intent.action.DOWNLOAD_STATUS"; - - /* - * Lists the states that the download manager can set on a download to - * notify applications of the download progress. The codes follow the HTTP - * families:<br> 1xx: informational<br> 2xx: success<br> 3xx: redirects (not - * used by the download manager)<br> 4xx: client errors<br> 5xx: server - * errors - */ - - /** - * Returns whether the status is informational (i.e. 1xx). - */ - public static boolean isStatusInformational(int status) { - return (status >= 100 && status < 200); - } - - /** - * Returns whether the status is a success (i.e. 2xx). - */ - public static boolean isStatusSuccess(int status) { - return (status >= 200 && status < 300); - } - - /** - * Returns whether the status is an error (i.e. 4xx or 5xx). - */ - public static boolean isStatusError(int status) { - return (status >= 400 && status < 600); - } - - /** - * Returns whether the status is a client error (i.e. 4xx). - */ - public static boolean isStatusClientError(int status) { - return (status >= 400 && status < 500); - } - - /** - * Returns whether the status is a server error (i.e. 5xx). - */ - public static boolean isStatusServerError(int status) { - return (status >= 500 && status < 600); - } - - /** - * Returns whether the download has completed (either with success or - * error). - */ - public static boolean isStatusCompleted(int status) { - return (status >= 200 && status < 300) - || (status >= 400 && status < 600); - } - - /** - * This download hasn't stated yet - */ - public static final int STATUS_PENDING = 190; - - /** - * This download has started - */ - public static final int STATUS_RUNNING = 192; - - /** - * This download has been paused by the owning app. - */ - public static final int STATUS_PAUSED_BY_APP = 193; - - /** - * This download encountered some network error and is waiting before - * retrying the request. - */ - public static final int STATUS_WAITING_TO_RETRY = 194; - - /** - * This download is waiting for network connectivity to proceed. - */ - public static final int STATUS_WAITING_FOR_NETWORK = 195; - - /** - * This download is waiting for a Wi-Fi connection to proceed or for - * permission to download over cellular. - */ - public static final int STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION = 196; - - /** - * This download is waiting for a Wi-Fi connection to proceed. - */ - public static final int STATUS_QUEUED_FOR_WIFI = 197; - - /** - * This download has successfully completed. Warning: there might be other - * status values that indicate success in the future. Use isSucccess() to - * capture the entire category. - * - * @hide - */ - public static final int STATUS_SUCCESS = 200; - - /** - * The requested URL is no longer available - */ - public static final int STATUS_FORBIDDEN = 403; - - /** - * The file was delivered incorrectly - */ - public static final int STATUS_FILE_DELIVERED_INCORRECTLY = 487; - - /** - * The requested destination file already exists. - */ - public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; - - /** - * Some possibly transient error occurred, but we can't resume the download. - */ - public static final int STATUS_CANNOT_RESUME = 489; - - /** - * This download was canceled - * - * @hide - */ - public static final int STATUS_CANCELED = 490; - - /** - * This download has completed with an error. Warning: there will be other - * status values that indicate errors in the future. Use isStatusError() to - * capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of a storage issue. - * Typically, that's because the filesystem is missing or full. Use the more - * specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} and - * {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. - * - * @hide - */ - public static final int STATUS_FILE_ERROR = 492; - - /** - * This download couldn't be completed because of an HTTP redirect response - * that the download manager couldn't handle. - * - * @hide - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed because of an unspecified unhandled - * HTTP code. - * - * @hide - */ - public static final int STATUS_UNHANDLED_HTTP_CODE = 494; - - /** - * This download couldn't be completed because of an error receiving or - * processing data at the HTTP level. - * - * @hide - */ - public static final int STATUS_HTTP_DATA_ERROR = 495; - - /** - * This download couldn't be completed because of an HttpException while - * setting up the request. - * - * @hide - */ - public static final int STATUS_HTTP_EXCEPTION = 496; - - /** - * This download couldn't be completed because there were too many - * redirects. - * - * @hide - */ - public static final int STATUS_TOO_MANY_REDIRECTS = 497; - - /** - * This download couldn't be completed due to insufficient storage space. - * Typically, this is because the SD card is full. - * - * @hide - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; - - /** - * This download couldn't be completed because no external storage device - * was found. Typically, this is because the SD card is not mounted. - * - * @hide - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - - /** - * This download is allowed to run. - * - * @hide - */ - public static final int CONTROL_RUN = 0; - - /** - * This download must pause at the first opportunity. - * - * @hide - */ - public static final int CONTROL_PAUSED = 1; - - /** - * This download is visible but only shows in the notifications while it's - * in progress. - * - * @hide - */ - public static final int VISIBILITY_VISIBLE = 0; - - /** - * This download is visible and shows in the notifications while in progress - * and after completion. - * - * @hide - */ - public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; - - /** - * This download doesn't show in the UI or in the notifications. - * - * @hide - */ - public static final int VISIBILITY_HIDDEN = 2; - - /** - * Bit flag for setAllowedNetworkTypes corresponding to - * {@link ConnectivityManager#TYPE_MOBILE}. - */ - public static final int NETWORK_MOBILE = 1 << 0; - - /** - * Bit flag for setAllowedNetworkTypes corresponding to - * {@link ConnectivityManager#TYPE_WIFI}. - */ - public static final int NETWORK_WIFI = 1 << 1; - - private final static String TEMP_EXT = ".tmp"; - - /** - * Service thread status - */ - private static boolean sIsRunning; - - @Override - public IBinder onBind(Intent paramIntent) { - Log.d(Constants.TAG, "Service Bound"); - return this.mServiceMessenger.getBinder(); - } - - /** - * Network state. - */ - private boolean mIsConnected; - private boolean mIsFailover; - private boolean mIsCellularConnection; - private boolean mIsRoaming; - private boolean mIsAtLeast3G; - private boolean mIsAtLeast4G; - private boolean mStateChanged; - - /** - * Download state - */ - private int mControl; - private int mStatus; - - public boolean isWiFi() { - return mIsConnected && !mIsCellularConnection; - } - - /** - * Bindings to important services - */ - private ConnectivityManager mConnectivityManager; - private WifiManager mWifiManager; - - /** - * Package we are downloading for (defaults to package of application) - */ - private PackageInfo mPackageInfo; - - /** - * Byte counts - */ - long mBytesSoFar; - long mTotalLength; - int mFileCount; - - /** - * Used for calculating time remaining and speed - */ - long mBytesAtSample; - long mMillisecondsAtSample; - float mAverageDownloadSpeed; - - /** - * Our binding to the network state broadcasts - */ - private BroadcastReceiver mConnReceiver; - final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this); - final private Messenger mServiceMessenger = mServiceStub.getMessenger(); - private Messenger mClientMessenger; - private DownloadNotification mNotification; - private PendingIntent mPendingIntent; - private PendingIntent mAlarmIntent; - - /** - * Updates the network type based upon the type and subtype returned from - * the connectivity manager. Subtype is only used for cellular signals. - * - * @param type - * @param subType - */ - private void updateNetworkType(int type, int subType) { - switch (type) { - case ConnectivityManager.TYPE_WIFI: - case ConnectivityManager.TYPE_ETHERNET: - case ConnectivityManager.TYPE_BLUETOOTH: - mIsCellularConnection = false; - mIsAtLeast3G = false; - mIsAtLeast4G = false; - break; - case ConnectivityManager.TYPE_WIMAX: - mIsCellularConnection = true; - mIsAtLeast3G = true; - mIsAtLeast4G = true; - break; - case ConnectivityManager.TYPE_MOBILE: - mIsCellularConnection = true; - switch (subType) { - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_GPRS: - case TelephonyManager.NETWORK_TYPE_IDEN: - mIsAtLeast3G = false; - mIsAtLeast4G = false; - break; - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_UMTS: - mIsAtLeast3G = true; - mIsAtLeast4G = false; - break; - case TelephonyManager.NETWORK_TYPE_LTE: // 4G - case TelephonyManager.NETWORK_TYPE_EHRPD: // 3G ++ interop - // with 4G - case TelephonyManager.NETWORK_TYPE_HSPAP: // 3G ++ but - // marketed as - // 4G - mIsAtLeast3G = true; - mIsAtLeast4G = true; - break; - default: - mIsCellularConnection = false; - mIsAtLeast3G = false; - mIsAtLeast4G = false; - } - } - } - - private void updateNetworkState(NetworkInfo info) { - boolean isConnected = mIsConnected; - boolean isFailover = mIsFailover; - boolean isCellularConnection = mIsCellularConnection; - boolean isRoaming = mIsRoaming; - boolean isAtLeast3G = mIsAtLeast3G; - if (null != info) { - mIsRoaming = info.isRoaming(); - mIsFailover = info.isFailover(); - mIsConnected = info.isConnected(); - updateNetworkType(info.getType(), info.getSubtype()); - } else { - mIsRoaming = false; - mIsFailover = false; - mIsConnected = false; - updateNetworkType(-1, -1); - } - mStateChanged = (mStateChanged || isConnected != mIsConnected - || isFailover != mIsFailover - || isCellularConnection != mIsCellularConnection - || isRoaming != mIsRoaming || isAtLeast3G != mIsAtLeast3G); - if (Constants.LOGVV) { - if (mStateChanged) { - Log.v(LOG_TAG, "Network state changed: "); - Log.v(LOG_TAG, "Starting State: " + - (isConnected ? "Connected " : "Not Connected ") + - (isCellularConnection ? "Cellular " : "WiFi ") + - (isRoaming ? "Roaming " : "Local ") + - (isAtLeast3G ? "3G+ " : "<3G ")); - Log.v(LOG_TAG, "Ending State: " + - (mIsConnected ? "Connected " : "Not Connected ") + - (mIsCellularConnection ? "Cellular " : "WiFi ") + - (mIsRoaming ? "Roaming " : "Local ") + - (mIsAtLeast3G ? "3G+ " : "<3G ")); - - if (isServiceRunning()) { - if (mIsRoaming) { - mStatus = STATUS_WAITING_FOR_NETWORK; - mControl = CONTROL_PAUSED; - } else if (mIsCellularConnection) { - DownloadsDB db = DownloadsDB.getDB(this); - int flags = db.getFlags(); - if (0 == (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { - mStatus = STATUS_QUEUED_FOR_WIFI; - mControl = CONTROL_PAUSED; - } - } - } - - } - } - } - - /** - * Polls the network state, setting the flags appropriately. - */ - void pollNetworkState() { - if (null == mConnectivityManager) { - mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - } - if (null == mWifiManager) { - mWifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); - } - if (mConnectivityManager == null) { - Log.w(Constants.TAG, - "couldn't get connectivity manager to poll network state"); - } else { - @SuppressLint("MissingPermission") - NetworkInfo activeInfo = mConnectivityManager - .getActiveNetworkInfo(); - updateNetworkState(activeInfo); - } - } - - public static final int NO_DOWNLOAD_REQUIRED = 0; - public static final int LVL_CHECK_REQUIRED = 1; - public static final int DOWNLOAD_REQUIRED = 2; - - public static final String EXTRA_PACKAGE_NAME = "EPN"; - public static final String EXTRA_PENDING_INTENT = "EPI"; - public static final String EXTRA_MESSAGE_HANDLER = "EMH"; - - /** - * Returns true if the LVL check is required - * - * @param db a downloads DB synchronized with the latest state - * @param pi the package info for the project - * @return returns true if the filenames need to be returned - */ - private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo pi) { - // we need to update the LVL check and get a successful status to - // proceed - if (db.mVersionCode != pi.versionCode) { - return true; - } - return false; - } - - /** - * Careful! Only use this internally. - * - * @return whether we think the service is running - */ - private static synchronized boolean isServiceRunning() { - return sIsRunning; - } - - private static synchronized void setServiceRunning(boolean isRunning) { - sIsRunning = isRunning; - } - - public static int startDownloadServiceIfRequired(Context context, - Intent intent, Class<?> serviceClass) throws NameNotFoundException { - final PendingIntent pendingIntent = (PendingIntent) intent - .getParcelableExtra(EXTRA_PENDING_INTENT); - return startDownloadServiceIfRequired(context, pendingIntent, - serviceClass); - } - - public static int startDownloadServiceIfRequired(Context context, - PendingIntent pendingIntent, Class<?> serviceClass) - throws NameNotFoundException - { - String packageName = context.getPackageName(); - String className = serviceClass.getName(); - - return startDownloadServiceIfRequired(context, pendingIntent, - packageName, className); - } - - /** - * 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 the - * metadata database updated 2) If the APK version does not match, checks - * the new LVL status to see if a new download is required 3) If the APK - * version does match, then checks to see if the download(s) have been - * completed 4) If the downloads have been completed, returns - * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the - * startup of an application to quickly ascertain if the application needs - * to wait to hear about any updated APK expansion files. Note that this - * does mean that the application MUST be run for the first time with a - * network connection, even if Market delivers all of the files. - * - * @param context - * @param pendingIntent - * @return true if the app should wait for more guidance from the - * downloader, false if the app can continue - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, - PendingIntent pendingIntent, String classPackage, String className) - throws NameNotFoundException { - // first: do we need to do an LVL update? - // we begin by getting our APK version from the package manager - final PackageInfo pi = context.getPackageManager().getPackageInfo( - context.getPackageName(), 0); - - int status = NO_DOWNLOAD_REQUIRED; - - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(context); - - // we need to update the LVL check and get a successful status to - // proceed - if (isLVLCheckRequired(db, pi)) { - status = LVL_CHECK_REQUIRED; - } - // we don't have to update LVL. do we still have a download to start? - if (db.mStatus == 0) { - DownloadInfo[] infos = db.getDownloads(); - if (null != infos) { - for (DownloadInfo info : infos) { - if (!Helpers.doesFileExist(context, info.mFileName, info.mTotalBytes, true)) { - status = DOWNLOAD_REQUIRED; - db.updateStatus(-1); - break; - } - } - } - } else { - status = DOWNLOAD_REQUIRED; - } - switch (status) { - case DOWNLOAD_REQUIRED: - case LVL_CHECK_REQUIRED: - Intent fileIntent = new Intent(); - fileIntent.setClassName(classPackage, className); - fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent); - context.startService(fileIntent); - break; - } - return status; - } - - @Override - public void requestAbortDownload() { - mControl = CONTROL_PAUSED; - mStatus = STATUS_CANCELED; - } - - @Override - public void requestPauseDownload() { - mControl = CONTROL_PAUSED; - mStatus = STATUS_PAUSED_BY_APP; - } - - @Override - public void setDownloadFlags(int flags) { - DownloadsDB.getDB(this).updateFlags(flags); - } - - @Override - public void requestContinueDownload() { - if (mControl == CONTROL_PAUSED) { - mControl = CONTROL_RUN; - } - Intent fileIntent = new Intent(this, this.getClass()); - fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - this.startService(fileIntent); - } - - public abstract String getPublicKey(); - - public abstract byte[] getSALT(); - - public abstract String getAlarmReceiverClassName(); - - private class LVLRunnable implements Runnable { - LVLRunnable(Context context, PendingIntent intent) { - mContext = context; - mPendingIntent = intent; - } - - final Context mContext; - - @Override - public void run() { - setServiceRunning(true); - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_FETCHING_URL); - String deviceId = Secure.getString(mContext.getContentResolver(), - Secure.ANDROID_ID); - - final APKExpansionPolicy aep = new APKExpansionPolicy(mContext, - new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId)); - - // reset our policy back to the start of the world to force a - // re-check - aep.resetPolicy(); - - // let's try and get the OBB file from LVL first - // Construct the LicenseChecker with a Policy. - final LicenseChecker checker = new LicenseChecker(mContext, aep, - getPublicKey() // Your public licensing key. - ); - checker.checkAccess(new LicenseCheckerCallback() { - - @Override - public void allow(int reason) { - try { - int count = aep.getExpansionURLCount(); - DownloadsDB db = DownloadsDB.getDB(mContext); - int status = 0; - if (count != 0) { - for (int i = 0; i < count; i++) { - String currentFileName = aep - .getExpansionFileName(i); - if (null != currentFileName) { - DownloadInfo di = new DownloadInfo(i, - currentFileName, mContext.getPackageName()); - - long fileSize = aep.getExpansionFileSize(i); - if (handleFileUpdated(db, i, currentFileName, - fileSize)) { - status |= -1; - di.resetDownload(); - di.mUri = aep.getExpansionURL(i); - di.mTotalBytes = fileSize; - di.mStatus = status; - db.updateDownload(di); - } else { - // we need to read the download - // information - // from - // the database - DownloadInfo dbdi = db - .getDownloadInfoByFileName(di.mFileName); - if (null == dbdi) { - // the file exists already and is - // the - // correct size - // was delivered by Market or - // through - // another mechanism - Log.d(LOG_TAG, "file " + di.mFileName - + " found. Not downloading."); - di.mStatus = STATUS_SUCCESS; - di.mTotalBytes = fileSize; - di.mCurrentBytes = fileSize; - di.mUri = aep.getExpansionURL(i); - db.updateDownload(di); - } else if (dbdi.mStatus != STATUS_SUCCESS) { - // we just update the URL - dbdi.mUri = aep.getExpansionURL(i); - db.updateDownload(dbdi); - status |= -1; - } - } - } - } - } - // first: do we need to do an LVL update? - // we begin by getting our APK version from the package - // manager - PackageInfo pi; - try { - pi = mContext.getPackageManager().getPackageInfo( - mContext.getPackageName(), 0); - db.updateMetadata(pi.versionCode, status); - Class<?> serviceClass = DownloaderService.this.getClass(); - switch (startDownloadServiceIfRequired(mContext, mPendingIntent, - serviceClass)) { - case NO_DOWNLOAD_REQUIRED: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); - break; - case LVL_CHECK_REQUIRED: - // DANGER WILL ROBINSON! - Log.e(LOG_TAG, "In LVL checking loop!"); - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); - throw new RuntimeException( - "Error with LVL checking and database integrity"); - case DOWNLOAD_REQUIRED: - // do nothing. the download will notify the - // application - // when things are done - break; - } - } catch (NameNotFoundException e1) { - e1.printStackTrace(); - throw new RuntimeException( - "Error with getting information from package name"); - } - } finally { - setServiceRunning(false); - } - } - - @Override - public void dontAllow(int reason) { - try - { - switch (reason) { - case Policy.NOT_LICENSED: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); - break; - case Policy.RETRY: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); - break; - } - } finally { - setServiceRunning(false); - } - - } - - @Override - public void applicationError(int errorCode) { - try { - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); - } finally { - setServiceRunning(false); - } - } - - }); - - } - - }; - - /** - * Updates the LVL information from the server. - * - * @param context - */ - public void updateLVL(final Context context) { - Context c = context.getApplicationContext(); - Handler h = new Handler(c.getMainLooper()); - h.post(new LVLRunnable(c, mPendingIntent)); - } - - /** - * The APK has been updated and a filename has been sent down from the - * Market call. If the file has the same name as the previous file, we do - * nothing as the file is guaranteed to be the same. If the file does not - * have the same name, we download it if it hasn't already been delivered by - * Market. - * - * @param index the index of the file from market (0 = main, 1 = patch) - * @param filename the name of the new file - * @param fileSize the size of the new file - * @return - */ - public boolean handleFileUpdated(DownloadsDB db, int index, - String filename, long fileSize) { - DownloadInfo di = db.getDownloadInfoByFileName(filename); - if (null != di) { - String oldFile = di.mFileName; - // cleanup - if (null != oldFile) { - if (filename.equals(oldFile)) { - return false; - } - - // remove partially downloaded file if it is there - String deleteFile = Helpers.generateSaveFileName(this, oldFile); - File f = new File(deleteFile); - if (f.exists()) - f.delete(); - } - } - return !Helpers.doesFileExist(this, filename, fileSize, true); - } - - private void scheduleAlarm(long wakeUp) { - AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - return; - } - - if (Constants.LOGV) { - Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); - } - - String className = getAlarmReceiverClassName(); - Intent intent = new Intent(Constants.ACTION_RETRY); - intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - intent.setClassName(this.getPackageName(), - className); - mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, - PendingIntent.FLAG_ONE_SHOT); - alarms.set( - AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + wakeUp, mAlarmIntent - ); - } - - private void cancelAlarms() { - if (null != mAlarmIntent) { - AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - return; - } - alarms.cancel(mAlarmIntent); - mAlarmIntent = null; - } - } - - /** - * We use this to track network state, such as when WiFi, Cellular, etc. is - * enabled when downloads are paused or in progress. - */ - private class InnerBroadcastReceiver extends BroadcastReceiver { - final Service mService; - - InnerBroadcastReceiver(Service service) { - mService = service; - } - - @Override - public void onReceive(Context context, Intent intent) { - pollNetworkState(); - if (mStateChanged - && !isServiceRunning()) { - Log.d(Constants.TAG, "InnerBroadcastReceiver Called"); - Intent fileIntent = new Intent(context, mService.getClass()); - fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - // send a new intent to the service - context.startService(fileIntent); - } - } - }; - - /** - * This is the main thread for the Downloader. This thread is responsible - * for queuing up downloads and other goodness. - */ - @Override - protected void onHandleIntent(Intent intent) { - setServiceRunning(true); - try { - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(this); - final PendingIntent pendingIntent = (PendingIntent) intent - .getParcelableExtra(EXTRA_PENDING_INTENT); - - if (null != pendingIntent) - { - mNotification.setClientIntent(pendingIntent); - mPendingIntent = pendingIntent; - } else if (null != mPendingIntent) { - mNotification.setClientIntent(mPendingIntent); - } else { - Log.e(LOG_TAG, "Downloader started in bad state without notification intent."); - return; - } - - // when the LVL check completes, a successful response will update - // the service - if (isLVLCheckRequired(db, mPackageInfo)) { - updateLVL(this); - return; - } - - // get each download - DownloadInfo[] infos = db.getDownloads(); - mBytesSoFar = 0; - mTotalLength = 0; - mFileCount = infos.length; - for (DownloadInfo info : infos) { - // We do an (simple) integrity check on each file, just to make - // sure - if (info.mStatus == STATUS_SUCCESS) { - // verify that the file matches the state - if (!Helpers.doesFileExist(this, info.mFileName, info.mTotalBytes, true)) { - info.mStatus = 0; - info.mCurrentBytes = 0; - } - } - // get aggregate data - mTotalLength += info.mTotalBytes; - mBytesSoFar += info.mCurrentBytes; - } - - // loop through all downloads and fetch them - pollNetworkState(); - if (null == mConnReceiver) { - - /** - * We use this to track network state, such as when WiFi, - * Cellular, etc. is enabled when downloads are paused or in - * progress. - */ - mConnReceiver = new InnerBroadcastReceiver(this); - IntentFilter intentFilter = new IntentFilter( - ConnectivityManager.CONNECTIVITY_ACTION); - intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - registerReceiver(mConnReceiver, intentFilter); - } - - for (DownloadInfo info : infos) { - long startingCount = info.mCurrentBytes; - - if (info.mStatus != STATUS_SUCCESS) { - DownloadThread dt = new DownloadThread(info, this, mNotification); - cancelAlarms(); - scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG); - dt.run(); - cancelAlarms(); - } - db.updateFromDb(info); - boolean setWakeWatchdog = false; - int notifyStatus; - switch (info.mStatus) { - case STATUS_FORBIDDEN: - // the URL is out of date - updateLVL(this); - return; - case STATUS_SUCCESS: - mBytesSoFar += info.mCurrentBytes - startingCount; - db.updateMetadata(mPackageInfo.versionCode, 0); - continue; - case STATUS_FILE_DELIVERED_INCORRECTLY: - // we may be on a network that is returning us a web - // page on redirect - notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE; - info.mCurrentBytes = 0; - db.updateDownload(info); - setWakeWatchdog = true; - break; - case STATUS_PAUSED_BY_APP: - notifyStatus = IDownloaderClient.STATE_PAUSED_BY_REQUEST; - break; - case STATUS_WAITING_FOR_NETWORK: - case STATUS_WAITING_TO_RETRY: - notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE; - setWakeWatchdog = true; - break; - case STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION: - case STATUS_QUEUED_FOR_WIFI: - // look for more detail here - if (null != mWifiManager) { - if (!mWifiManager.isWifiEnabled()) { - notifyStatus = IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION; - setWakeWatchdog = true; - break; - } - } - notifyStatus = IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION; - setWakeWatchdog = true; - break; - case STATUS_CANCELED: - notifyStatus = IDownloaderClient.STATE_FAILED_CANCELED; - setWakeWatchdog = true; - break; - - case STATUS_INSUFFICIENT_SPACE_ERROR: - notifyStatus = IDownloaderClient.STATE_FAILED_SDCARD_FULL; - setWakeWatchdog = true; - break; - - case STATUS_DEVICE_NOT_FOUND_ERROR: - notifyStatus = IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE; - setWakeWatchdog = true; - break; - - default: - notifyStatus = IDownloaderClient.STATE_FAILED; - break; - } - if (setWakeWatchdog) { - scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER); - } else { - cancelAlarms(); - } - // failure or pause state - mNotification.onDownloadStateChanged(notifyStatus); - return; - } - - // all downloads complete - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); - } finally { - setServiceRunning(false); - } - } - - @Override - public void onDestroy() { - if (null != mConnReceiver) { - unregisterReceiver(mConnReceiver); - mConnReceiver = null; - } - mServiceStub.disconnect(this); - super.onDestroy(); - } - - public int getNetworkAvailabilityState(DownloadsDB db) { - if (mIsConnected) { - if (!mIsCellularConnection) - return NETWORK_OK; - int flags = db.mFlags; - if (mIsRoaming) - return NETWORK_CANNOT_USE_ROAMING; - if (0 != (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { - return NETWORK_OK; - } else { - return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR; - } - } - return NETWORK_NO_CONNECTION; - } - - @Override - public void onCreate() { - super.onCreate(); - try { - mPackageInfo = getPackageManager().getPackageInfo( - getPackageName(), 0); - ApplicationInfo ai = getApplicationInfo(); - CharSequence applicationLabel = getPackageManager().getApplicationLabel(ai); - mNotification = new DownloadNotification(this, applicationLabel); - - } catch (NameNotFoundException e) { - e.printStackTrace(); - } - } - - /** - * Exception thrown from methods called by generateSaveFile() for any fatal - * error. - */ - public static class GenerateSaveFileError extends Exception { - private static final long serialVersionUID = 3465966015408936540L; - int mStatus; - String mMessage; - - public GenerateSaveFileError(int status, String message) { - mStatus = status; - mMessage = message; - } - } - - /** - * Returns the filename (where the file should be saved) from info about a - * download - */ - public String generateTempSaveFileName(String fileName) { - String path = Helpers.getSaveFilePath(this) - + File.separator + fileName + TEMP_EXT; - return path; - } - - /** - * Creates a filename (where the file should be saved) from info about a - * download. - */ - public String generateSaveFile(String filename, long filesize) - throws GenerateSaveFileError { - String path = generateTempSaveFileName(filename); - File expPath = new File(path); - if (!Helpers.isExternalMediaMounted()) { - Log.d(Constants.TAG, "External media not mounted: " + path); - throw new GenerateSaveFileError(STATUS_DEVICE_NOT_FOUND_ERROR, - "external media is not yet mounted"); - - } - if (expPath.exists()) { - Log.d(Constants.TAG, "File already exists: " + path); - throw new GenerateSaveFileError(STATUS_FILE_ALREADY_EXISTS_ERROR, - "requested destination file already exists"); - } - if (Helpers.getAvailableBytes(Helpers.getFilesystemRoot(path)) < filesize) { - throw new GenerateSaveFileError(STATUS_INSUFFICIENT_SPACE_ERROR, - "insufficient space on external storage"); - } - return path; - } - - /** - * @return a non-localized string appropriate for logging corresponding to - * one of the NETWORK_* constants. - */ - public String getLogMessageForNetworkError(int networkError) { - switch (networkError) { - case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE: - return "download size exceeds recommended limit for mobile network"; - - case NETWORK_UNUSABLE_DUE_TO_SIZE: - return "download size exceeds limit for mobile network"; - - case NETWORK_NO_CONNECTION: - return "no network connection available"; - - case NETWORK_CANNOT_USE_ROAMING: - return "download cannot use the current network connection because it is roaming"; - - case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: - return "download was requested to not use the current network type"; - - default: - return "unknown error with network connectivity"; - } - } - - public int getControl() { - return mControl; - } - - public int getStatus() { - return mStatus; - } - - /** - * Calculating a moving average for the speed so we don't get jumpy - * calculations for time etc. - */ - static private final float SMOOTHING_FACTOR = 0.005f; - - public void notifyUpdateBytes(long totalBytesSoFar) { - long timeRemaining; - long currentTime = SystemClock.uptimeMillis(); - if (0 != mMillisecondsAtSample) { - // we have a sample. - long timePassed = currentTime - mMillisecondsAtSample; - long bytesInSample = totalBytesSoFar - mBytesAtSample; - float currentSpeedSample = (float) bytesInSample / (float) timePassed; - if (0 != mAverageDownloadSpeed) { - mAverageDownloadSpeed = SMOOTHING_FACTOR * currentSpeedSample - + (1 - SMOOTHING_FACTOR) * mAverageDownloadSpeed; - } else { - mAverageDownloadSpeed = currentSpeedSample; - } - timeRemaining = (long) ((mTotalLength - totalBytesSoFar) / mAverageDownloadSpeed); - } else { - timeRemaining = -1; - } - mMillisecondsAtSample = currentTime; - mBytesAtSample = totalBytesSoFar; - mNotification.onDownloadProgress( - new DownloadProgressInfo(mTotalLength, - totalBytesSoFar, - timeRemaining, - mAverageDownloadSpeed) - ); - - } - - @Override - protected boolean shouldStop() { - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(this); - if (db.mStatus == 0) { - return true; - } - return false; - } - - @Override - public void requestDownloadStatus() { - mNotification.resendState(); - } - - @Override - public void onClientUpdated(Messenger clientMessenger) { - this.mClientMessenger = clientMessenger; - mNotification.setMessenger(mClientMessenger); - } - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java deleted file mode 100644 index c658b4cc43..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDoneException; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteStatement; -import android.provider.BaseColumns; -import android.util.Log; - -public class DownloadsDB { - private static final String DATABASE_NAME = "DownloadsDB"; - private static final int DATABASE_VERSION = 7; - public static final String LOG_TAG = DownloadsDB.class.getName(); - final SQLiteOpenHelper mHelper; - SQLiteStatement mGetDownloadByIndex; - SQLiteStatement mUpdateCurrentBytes; - private static DownloadsDB mDownloadsDB; - long mMetadataRowID = -1; - int mVersionCode = -1; - int mStatus = -1; - int mFlags; - - static public synchronized DownloadsDB getDB(Context paramContext) { - if (null == mDownloadsDB) { - return new DownloadsDB(paramContext); - } - return mDownloadsDB; - } - - private SQLiteStatement getDownloadByIndexStatement() { - if (null == mGetDownloadByIndex) { - mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement( - "SELECT " + BaseColumns._ID + " FROM " - + DownloadColumns.TABLE_NAME + " WHERE " - + DownloadColumns.INDEX + " = ?"); - } - return mGetDownloadByIndex; - } - - private SQLiteStatement getUpdateCurrentBytesStatement() { - if (null == mUpdateCurrentBytes) { - mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement( - "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES - + " = ?" + - " WHERE " + DownloadColumns.INDEX + " = ?"); - } - return mUpdateCurrentBytes; - } - - private DownloadsDB(Context paramContext) { - this.mHelper = new DownloadsContentDBHelper(paramContext); - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - // Query for the version code, the row ID of the metadata (for future - // updating) the status and the flags - Cursor cur = sqldb.rawQuery("SELECT " + - MetadataColumns.APKVERSION + "," + - BaseColumns._ID + "," + - MetadataColumns.DOWNLOAD_STATUS + "," + - MetadataColumns.FLAGS + - " FROM " - + MetadataColumns.TABLE_NAME + " LIMIT 1", null); - if (null != cur && cur.moveToFirst()) { - mVersionCode = cur.getInt(0); - mMetadataRowID = cur.getLong(1); - mStatus = cur.getInt(2); - mFlags = cur.getInt(3); - cur.close(); - } - mDownloadsDB = this; - } - - protected DownloadInfo getDownloadInfoByFileName(String fileName) { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor itemcur = null; - try { - itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, - DownloadColumns.FILENAME + " = ?", - new String[] { - fileName - }, null, null, null); - if (null != itemcur && itemcur.moveToFirst()) { - return getDownloadInfoFromCursor(itemcur); - } - } finally { - if (null != itemcur) - itemcur.close(); - } - return null; - } - - public long getIDForDownloadInfo(final DownloadInfo di) { - return getIDByIndex(di.mIndex); - } - - public long getIDByIndex(int index) { - SQLiteStatement downloadByIndex = getDownloadByIndexStatement(); - downloadByIndex.clearBindings(); - downloadByIndex.bindLong(1, index); - try { - return downloadByIndex.simpleQueryForLong(); - } catch (SQLiteDoneException e) { - return -1; - } - } - - public void updateDownloadCurrentBytes(final DownloadInfo di) { - SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement(); - downloadCurrentBytes.clearBindings(); - downloadCurrentBytes.bindLong(1, di.mCurrentBytes); - downloadCurrentBytes.bindLong(2, di.mIndex); - downloadCurrentBytes.execute(); - } - - public void close() { - this.mHelper.close(); - } - - protected static class DownloadsContentDBHelper extends SQLiteOpenHelper { - DownloadsContentDBHelper(Context paramContext) { - super(paramContext, DATABASE_NAME, null, DATABASE_VERSION); - } - - private String createTableQueryFromArray(String paramString, - String[][] paramArrayOfString) { - StringBuilder localStringBuilder = new StringBuilder(); - localStringBuilder.append("CREATE TABLE "); - localStringBuilder.append(paramString); - localStringBuilder.append(" ("); - int i = paramArrayOfString.length; - for (int j = 0;; j++) { - if (j >= i) { - localStringBuilder - .setLength(localStringBuilder.length() - 1); - localStringBuilder.append(");"); - return localStringBuilder.toString(); - } - String[] arrayOfString = paramArrayOfString[j]; - localStringBuilder.append(' '); - localStringBuilder.append(arrayOfString[0]); - localStringBuilder.append(' '); - localStringBuilder.append(arrayOfString[1]); - localStringBuilder.append(','); - } - } - - /** - * These two arrays must match and have the same order. For every Schema - * there must be a corresponding table name. - */ - static final private String[][][] sSchemas = { - DownloadColumns.SCHEMA, MetadataColumns.SCHEMA - }; - - static final private String[] sTables = { - DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME - }; - - /** - * Goes through all of the tables in sTables and drops each table if it - * exists. Altered to no longer make use of reflection. - */ - private void dropTables(SQLiteDatabase paramSQLiteDatabase) { - for (String table : sTables) { - try { - paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table); - } catch (Exception localException) { - localException.printStackTrace(); - } - } - } - - /** - * Goes through all of the tables in sTables and creates a database with - * the corresponding schema described in sSchemas. Altered to no longer - * make use of reflection. - */ - public void onCreate(SQLiteDatabase paramSQLiteDatabase) { - int numSchemas = sSchemas.length; - for (int i = 0; i < numSchemas; i++) { - try { - String[][] schema = (String[][]) sSchemas[i]; - paramSQLiteDatabase.execSQL(createTableQueryFromArray( - sTables[i], schema)); - } catch (Exception localException) { - while (true) - localException.printStackTrace(); - } - } - } - - public void onUpgrade(SQLiteDatabase paramSQLiteDatabase, - int paramInt1, int paramInt2) { - Log.w(DownloadsContentDBHelper.class.getName(), - "Upgrading database from version " + paramInt1 + " to " - + paramInt2 + ", which will destroy all old data"); - dropTables(paramSQLiteDatabase); - onCreate(paramSQLiteDatabase); - } - } - - public static class MetadataColumns implements BaseColumns { - public static final String APKVERSION = "APKVERSION"; - public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS"; - public static final String FLAGS = "DOWNLOADFLAGS"; - - public static final String[][] SCHEMA = { - { - BaseColumns._ID, "INTEGER PRIMARY KEY" - }, - { - APKVERSION, "INTEGER" - }, { - 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 final String INDEX = "FILEIDX"; - public static final String URI = "URI"; - public static final String FILENAME = "FN"; - public static final String ETAG = "ETAG"; - - public static final String TOTALBYTES = "TOTALBYTES"; - public static final String CURRENTBYTES = "CURRENTBYTES"; - public static final String LASTMOD = "LASTMOD"; - - public static final String STATUS = "STATUS"; - public static final String CONTROL = "CONTROL"; - public static final String NUM_FAILED = "FAILCOUNT"; - public static final String RETRY_AFTER = "RETRYAFTER"; - public static final String REDIRECT_COUNT = "REDIRECTCOUNT"; - - public static final String[][] SCHEMA = { - { - BaseColumns._ID, "INTEGER PRIMARY KEY" - }, - { - INDEX, "INTEGER UNIQUE" - }, { - URI, "TEXT" - }, - { - 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 = { - DownloadColumns.FILENAME, - DownloadColumns.URI, DownloadColumns.ETAG, - DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES, - DownloadColumns.LASTMOD, DownloadColumns.STATUS, - DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED, - DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT, - DownloadColumns.INDEX - }; - - private static final int FILENAME_IDX = 0; - private static final int URI_IDX = 1; - private static final int ETAG_IDX = 2; - private static final int TOTALBYTES_IDX = 3; - private static final int CURRENTBYTES_IDX = 4; - private static final int LASTMOD_IDX = 5; - private static final int STATUS_IDX = 6; - private static final int CONTROL_IDX = 7; - private static final int NUM_FAILED_IDX = 8; - private static final int RETRY_AFTER_IDX = 9; - private static final int REDIRECT_COUNT_IDX = 10; - private static final int INDEX_IDX = 11; - - /** - * This function will add a new file to the database if it does not exist. - * - * @param di DownloadInfo that we wish to store - * @return the row id of the record to be updated/inserted, or -1 - */ - public boolean updateDownload(DownloadInfo di) { - ContentValues cv = new ContentValues(); - cv.put(DownloadColumns.INDEX, di.mIndex); - cv.put(DownloadColumns.FILENAME, di.mFileName); - cv.put(DownloadColumns.URI, di.mUri); - cv.put(DownloadColumns.ETAG, di.mETag); - cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes); - cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes); - cv.put(DownloadColumns.LASTMOD, di.mLastMod); - cv.put(DownloadColumns.STATUS, di.mStatus); - cv.put(DownloadColumns.CONTROL, di.mControl); - cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed); - cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter); - cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount); - return updateDownload(di, cv); - } - - public boolean updateDownload(DownloadInfo di, ContentValues cv) { - long id = di == null ? -1 : getIDForDownloadInfo(di); - try { - final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); - if (id != -1) { - if (1 != sqldb.update(DownloadColumns.TABLE_NAME, - cv, DownloadColumns._ID + " = " + id, null)) { - return false; - } - } else { - return -1 != sqldb.insert(DownloadColumns.TABLE_NAME, - DownloadColumns.URI, cv); - } - } catch (android.database.sqlite.SQLiteException ex) { - ex.printStackTrace(); - } - return false; - } - - public int getLastCheckedVersionCode() { - return mVersionCode; - } - - public boolean isDownloadRequired() { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM " - + DownloadColumns.TABLE_NAME + " WHERE " - + DownloadColumns.STATUS + " <> 0", null); - try { - if (null != cur && cur.moveToFirst()) { - return 0 == cur.getInt(0); - } - } finally { - if (null != cur) - cur.close(); - } - return true; - } - - public int getFlags() { - return mFlags; - } - - public boolean updateFlags(int flags) { - if (mFlags != flags) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.FLAGS, flags); - if (updateMetadata(cv)) { - mFlags = flags; - return true; - } else { - return false; - } - } else { - return true; - } - }; - - public boolean updateStatus(int status) { - if (mStatus != status) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.DOWNLOAD_STATUS, status); - if (updateMetadata(cv)) { - mStatus = status; - return true; - } else { - return false; - } - } else { - return true; - } - }; - - public boolean updateMetadata(ContentValues cv) { - final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); - if (-1 == this.mMetadataRowID) { - long newID = sqldb.insert(MetadataColumns.TABLE_NAME, - MetadataColumns.APKVERSION, cv); - if (-1 == newID) - return false; - mMetadataRowID = newID; - } else { - if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv, - BaseColumns._ID + " = " + mMetadataRowID, null)) - return false; - } - return true; - } - - public boolean updateMetadata(int apkVersion, int downloadStatus) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.APKVERSION, apkVersion); - cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus); - if (updateMetadata(cv)) { - mVersionCode = apkVersion; - mStatus = downloadStatus; - return true; - } else { - return false; - } - }; - - public boolean updateFromDb(DownloadInfo di) { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = null; - try { - cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, - DownloadColumns.FILENAME + "= ?", - new String[] { - di.mFileName - }, null, null, null); - if (null != cur && cur.moveToFirst()) { - setDownloadInfoFromCursor(di, cur); - return true; - } - return false; - } finally { - if (null != cur) { - cur.close(); - } - } - } - - public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) { - di.mUri = cur.getString(URI_IDX); - di.mETag = cur.getString(ETAG_IDX); - di.mTotalBytes = cur.getLong(TOTALBYTES_IDX); - di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX); - di.mLastMod = cur.getLong(LASTMOD_IDX); - di.mStatus = cur.getInt(STATUS_IDX); - di.mControl = cur.getInt(CONTROL_IDX); - di.mNumFailed = cur.getInt(NUM_FAILED_IDX); - di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX); - di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX); - } - - public DownloadInfo getDownloadInfoFromCursor(Cursor cur) { - DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX), - cur.getString(FILENAME_IDX), this.getClass().getPackage() - .getName()); - 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(); - } - } - } - -} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java deleted file mode 100644 index 3f440e9893..0000000000 --- a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.text.format.Time; - -import java.util.Calendar; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Helper for parsing an HTTP date. - */ -public final class HttpDateTime { - - /* - * 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, - * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format - * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon - * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS - * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon - * (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. - */ - 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}:[0-9][0-9]:[0-9][0-9])"; - - private static final String HTTP_DATE_ANSIC_REGEXP = - "[ ]([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})"; - - /** - * The compiled version of the HTTP-date regular expressions. - */ - private static final Pattern HTTP_DATE_RFC_PATTERN = - Pattern.compile(HTTP_DATE_RFC_REGEXP); - private static final Pattern HTTP_DATE_ANSIC_PATTERN = - Pattern.compile(HTTP_DATE_ANSIC_REGEXP); - - private static class TimeOfDay { - TimeOfDay(int h, int m, int s) { - this.hour = h; - this.minute = m; - this.second = s; - } - - int hour; - int minute; - int second; - } - - public static long parse(String timeString) - throws IllegalArgumentException { - - int date = 1; - int month = Calendar.JANUARY; - int year = 1970; - TimeOfDay timeOfDay; - - Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); - if (rfcMatcher.find()) { - date = getDate(rfcMatcher.group(1)); - month = getMonth(rfcMatcher.group(2)); - year = getYear(rfcMatcher.group(3)); - timeOfDay = getTime(rfcMatcher.group(4)); - } else { - Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); - if (ansicMatcher.find()) { - month = getMonth(ansicMatcher.group(1)); - date = getDate(ansicMatcher.group(2)); - timeOfDay = getTime(ansicMatcher.group(3)); - year = getYear(ansicMatcher.group(4)); - } else { - throw new IllegalArgumentException(); - } - } - - // FIXME: Y2038 BUG! - if (year >= 2038) { - year = 2038; - month = Calendar.JANUARY; - date = 1; - } - - Time time = new Time(Time.TIMEZONE_UTC); - time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, - month, year); - return time.toMillis(false /* use isDst */); - } - - private static int getDate(String dateString) { - if (dateString.length() == 2) { - return (dateString.charAt(0) - '0') * 10 - + (dateString.charAt(1) - '0'); - } else { - return (dateString.charAt(0) - '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 - * + 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 - */ - private static int getMonth(String monthString) { - int hash = Character.toLowerCase(monthString.charAt(0)) + - Character.toLowerCase(monthString.charAt(1)) + - Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; - switch (hash) { - case 22: - return Calendar.JANUARY; - case 10: - return Calendar.FEBRUARY; - case 29: - return Calendar.MARCH; - case 32: - return Calendar.APRIL; - case 36: - return Calendar.MAY; - case 42: - return Calendar.JUNE; - case 40: - return Calendar.JULY; - case 26: - return Calendar.AUGUST; - case 37: - return Calendar.SEPTEMBER; - case 35: - return Calendar.OCTOBER; - case 48: - return Calendar.NOVEMBER; - case 9: - return Calendar.DECEMBER; - default: - throw new IllegalArgumentException(); - } - } - - private static int getYear(String yearString) { - if (yearString.length() == 2) { - int year = (yearString.charAt(0) - '0') * 10 - + (yearString.charAt(1) - '0'); - if (year >= 70) { - return year + 1900; - } else { - return year + 2000; - } - } else if (yearString.length() == 3) { - // According to RFC 2822, three digit years should be added to 1900. - int year = (yearString.charAt(0) - '0') * 100 - + (yearString.charAt(1) - '0') * 10 - + (yearString.charAt(2) - '0'); - return year + 1900; - } 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) { - // HH might be H - int i = 0; - int hour = timeString.charAt(i++) - '0'; - if (timeString.charAt(i) != ':') - hour = hour * 10 + (timeString.charAt(i++) - '0'); - // Skip ':' - i++; - - int minute = (timeString.charAt(i++) - '0') * 10 - + (timeString.charAt(i++) - '0'); - // Skip ':' - i++; - - int second = (timeString.charAt(i++) - '0') * 10 - + (timeString.charAt(i++) - '0'); - - return new TimeOfDay(hour, minute, second); - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java deleted file mode 100644 index d6ccb0c5e4..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import com.google.android.vending.licensing.util.Base64; -import com.google.android.vending.licensing.util.Base64DecoderException; - -import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; -import java.security.spec.KeySpec; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * An Obfuscator that uses AES to encrypt data. - */ -public class AESObfuscator implements Obfuscator { - private static final String UTF8 = "UTF-8"; - private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC"; - private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; - private static final byte[] IV = - { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 }; - private static final String header = "com.google.android.vending.licensing.AESObfuscator-1|"; - - private Cipher mEncryptor; - private Cipher mDecryptor; - - /** - * @param salt an array of random bytes to use for each (un)obfuscation - * @param applicationId application identifier, e.g. the package name - * @param deviceId device identifier. Use as many sources as possible to - * create this unique identifier. - */ - public AESObfuscator(byte[] salt, String applicationId, String deviceId) { - try { - SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); - KeySpec keySpec = - new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); - SecretKey tmp = factory.generateSecret(keySpec); - SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); - mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM); - mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV)); - mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM); - mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV)); - } catch (GeneralSecurityException e) { - // This can't happen on a compatible Android device. - throw new RuntimeException("Invalid environment", e); - } - } - - public String obfuscate(String original, String key) { - if (original == null) { - return null; - } - try { - // Header is appended as an integrity check - return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8))); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Invalid environment", e); - } catch (GeneralSecurityException e) { - throw new RuntimeException("Invalid environment", e); - } - } - - public String unobfuscate(String obfuscated, String key) throws ValidationException { - if (obfuscated == null) { - return null; - } - try { - String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8); - // Check for presence of header. This serves as a final integrity check, for cases - // where the block size is correct during decryption. - int headerIndex = result.indexOf(header+key); - if (headerIndex != 0) { - throw new ValidationException("Header not found (invalid data or key)" + ":" + - obfuscated); - } - return result.substring(header.length()+key.length(), result.length()); - } catch (Base64DecoderException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (IllegalBlockSizeException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (BadPaddingException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Invalid environment", e); - } - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java deleted file mode 100644 index 37fad8926a..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java +++ /dev/null @@ -1,414 +0,0 @@ - -package com.google.android.vending.licensing; - -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - -import com.google.android.vending.licensing.util.URIQueryDecoder; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - -/** - * Default policy. All policy decisions are based off of response data received - * from the licensing service. Specifically, the licensing server sends the - * following information: response validity period, error retry period, - * error retry count and a URL for restoring app access in unlicensed cases. - * <p> - * These values will vary based on the the way the application is configured in - * the Google Play publishing console, such as whether the application is - * marked as free or is within its refund period, as well as how often an - * application is checking with the licensing service. - * <p> - * Developers who need more fine grained control over their application's - * licensing policy should implement a custom Policy. - */ -public class APKExpansionPolicy implements Policy { - - private static final String TAG = "APKExpansionPolicy"; - private static final String PREFS_FILE = "com.google.android.vending.licensing.APKExpansionPolicy"; - private static final String PREF_LAST_RESPONSE = "lastResponse"; - private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; - private static final String PREF_RETRY_UNTIL = "retryUntil"; - private static final String PREF_MAX_RETRIES = "maxRetries"; - private static final String PREF_RETRY_COUNT = "retryCount"; - private static final String PREF_LICENSING_URL = "licensingUrl"; - private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; - private static final String DEFAULT_RETRY_UNTIL = "0"; - private static final String DEFAULT_MAX_RETRIES = "0"; - private static final String DEFAULT_RETRY_COUNT = "0"; - - private static final long MILLIS_PER_MINUTE = 60 * 1000; - - private long mValidityTimestamp; - private long mRetryUntil; - private long mMaxRetries; - private long mRetryCount; - private long mLastResponseTime = 0; - private int mLastResponse; - private String mLicensingUrl; - private PreferenceObfuscator mPreferences; - private Vector<String> mExpansionURLs = new Vector<String>(); - private Vector<String> mExpansionFileNames = new Vector<String>(); - private Vector<Long> mExpansionFileSizes = new Vector<Long>(); - - /** - * The design of the protocol supports n files. Currently the market can - * only deliver two files. To accommodate this, we have these two constants, - * but the order is the only relevant thing here. - */ - public static final int MAIN_FILE_URL_INDEX = 0; - public static final int PATCH_FILE_URL_INDEX = 1; - - /** - * @param context The context for the current application - * @param obfuscator An obfuscator to be used with preferences. - */ - public APKExpansionPolicy(Context context, Obfuscator obfuscator) { - // Import old values - SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); - mPreferences = new PreferenceObfuscator(sp, obfuscator); - mLastResponse = Integer.parseInt( - mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); - mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, - DEFAULT_VALIDITY_TIMESTAMP)); - mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); - mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); - mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); - mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null); - } - - /** - * We call this to guarantee that we fetch a fresh policy from the server. - * This is to be used if the URL is invalid. - */ - public void resetPolicy() { - mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)); - setRetryUntil(DEFAULT_RETRY_UNTIL); - setMaxRetries(DEFAULT_MAX_RETRIES); - setRetryCount(Long.parseLong(DEFAULT_RETRY_COUNT)); - setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); - mPreferences.commit(); - } - - /** - * Process a new response from the license server. - * <p> - * This data will be used for computing future policy decisions. The - * following parameters are processed: - * <ul> - * <li>VT: the timestamp that the client should consider the response valid - * until - * <li>GT: the timestamp that the client should ignore retry errors until - * <li>GR: the number of retry errors that the client should ignore - * <li>LU: a deep link URL that can enable access for unlicensed apps (e.g. - * buy app on the Play Store) - * </ul> - * - * @param response the result from validating the server response - * @param rawData the raw server response data - */ - public void processServerResponse(int response, - com.google.android.vending.licensing.ResponseData rawData) { - - // Update retry counter - if (response != Policy.RETRY) { - setRetryCount(0); - } else { - setRetryCount(mRetryCount + 1); - } - - // Update server policy data - Map<String, String> extras = decodeExtras(rawData); - if (response == Policy.LICENSED) { - mLastResponse = response; - // Reset the licensing URL since it is only applicable for NOT_LICENSED responses. - setLicensingUrl(null); - setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE)); - Set<String> keys = extras.keySet(); - for (String key : keys) { - if (key.equals("VT")) { - setValidityTimestamp(extras.get(key)); - } else if (key.equals("GT")) { - setRetryUntil(extras.get(key)); - } else if (key.equals("GR")) { - setMaxRetries(extras.get(key)); - } else if (key.startsWith("FILE_URL")) { - int index = Integer.parseInt(key.substring("FILE_URL".length())) - 1; - setExpansionURL(index, extras.get(key)); - } else if (key.startsWith("FILE_NAME")) { - int index = Integer.parseInt(key.substring("FILE_NAME".length())) - 1; - setExpansionFileName(index, extras.get(key)); - } else if (key.startsWith("FILE_SIZE")) { - int index = Integer.parseInt(key.substring("FILE_SIZE".length())) - 1; - setExpansionFileSize(index, Long.parseLong(extras.get(key))); - } - } - } else if (response == Policy.NOT_LICENSED) { - // Clear out stale retry params - setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); - setRetryUntil(DEFAULT_RETRY_UNTIL); - setMaxRetries(DEFAULT_MAX_RETRIES); - // Update the licensing URL - setLicensingUrl(extras.get("LU")); - } - - setLastResponse(response); - mPreferences.commit(); - } - - /** - * Set the last license response received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param l the response - */ - private void setLastResponse(int l) { - mLastResponseTime = System.currentTimeMillis(); - mLastResponse = l; - mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); - } - - /** - * Set the current retry count and add to preferences. You must manually - * call PreferenceObfuscator.commit() to commit these changes to disk. - * - * @param c the new retry count - */ - private void setRetryCount(long c) { - mRetryCount = c; - mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); - } - - public long getRetryCount() { - return mRetryCount; - } - - /** - * Set the last validity timestamp (VT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param validityTimestamp the VT string received - */ - private void setValidityTimestamp(String validityTimestamp) { - Long lValidityTimestamp; - try { - lValidityTimestamp = Long.parseLong(validityTimestamp); - } catch (NumberFormatException e) { - // No response or not parseable, expire in one minute. - Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); - lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; - validityTimestamp = Long.toString(lValidityTimestamp); - } - - mValidityTimestamp = lValidityTimestamp; - mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); - } - - public long getValidityTimestamp() { - return mValidityTimestamp; - } - - /** - * Set the retry until timestamp (GT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param retryUntil the GT string received - */ - private void setRetryUntil(String retryUntil) { - Long lRetryUntil; - try { - lRetryUntil = Long.parseLong(retryUntil); - } catch (NumberFormatException e) { - // No response or not parseable, expire immediately - Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); - retryUntil = "0"; - lRetryUntil = 0l; - } - - mRetryUntil = lRetryUntil; - mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); - } - - public long getRetryUntil() { - return mRetryUntil; - } - - /** - * Set the max retries value (GR) as received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param maxRetries the GR string received - */ - private void setMaxRetries(String maxRetries) { - Long lMaxRetries; - try { - lMaxRetries = Long.parseLong(maxRetries); - } catch (NumberFormatException e) { - // No response or not parseable, expire immediately - Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); - maxRetries = "0"; - lMaxRetries = 0l; - } - - mMaxRetries = lMaxRetries; - mPreferences.putString(PREF_MAX_RETRIES, maxRetries); - } - - public long getMaxRetries() { - return mMaxRetries; - } - - /** - * Set the licensing URL that displays a Play Store UI for the user to regain app access. - * - * @param url the LU string received - */ - private void setLicensingUrl(String url) { - mLicensingUrl = url; - mPreferences.putString(PREF_LICENSING_URL, url); - } - - public String getLicensingUrl() { - return mLicensingUrl; - } - - /** - * Gets the count of expansion URLs. Since expansionURLs are not committed - * to preferences, this will return zero if there has been no LVL fetch - * in the current session. - * - * @return the number of expansion URLs. (0,1,2) - */ - public int getExpansionURLCount() { - return mExpansionURLs.size(); - } - - /** - * Gets the expansion URL. Since these URLs are not committed to - * preferences, this will always return null if there has not been an LVL - * fetch in the current session. - * - * @param index the index of the URL to fetch. This value will be either - * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX - */ - public String getExpansionURL(int index) { - if (index < mExpansionURLs.size()) { - return mExpansionURLs.elementAt(index); - } - return null; - } - - /** - * Sets the expansion URL. Expansion URL's are not committed to preferences, - * but are instead intended to be stored when the license response is - * processed by the front-end. - * - * @param index the index of the expansion URL. This value will be either - * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX - * @param URL the URL to set - */ - public void setExpansionURL(int index, String URL) { - if (index >= mExpansionURLs.size()) { - mExpansionURLs.setSize(index + 1); - } - mExpansionURLs.set(index, URL); - } - - public String getExpansionFileName(int index) { - if (index < mExpansionFileNames.size()) { - return mExpansionFileNames.elementAt(index); - } - return null; - } - - public void setExpansionFileName(int index, String name) { - if (index >= mExpansionFileNames.size()) { - mExpansionFileNames.setSize(index + 1); - } - mExpansionFileNames.set(index, name); - } - - public long getExpansionFileSize(int index) { - if (index < mExpansionFileSizes.size()) { - return mExpansionFileSizes.elementAt(index); - } - return -1; - } - - public void setExpansionFileSize(int index, long size) { - if (index >= mExpansionFileSizes.size()) { - mExpansionFileSizes.setSize(index + 1); - } - mExpansionFileSizes.set(index, size); - } - - /** - * {@inheritDoc} This implementation allows access if either:<br> - * <ol> - * <li>a LICENSED response was received within the validity period - * <li>a RETRY response was received in the last minute, and we are under - * the RETRY count or in the RETRY period. - * </ol> - */ - public boolean allowAccess() { - long ts = System.currentTimeMillis(); - if (mLastResponse == Policy.LICENSED) { - // Check if the LICENSED response occurred within the validity - // timeout. - if (ts <= mValidityTimestamp) { - // Cached LICENSED response is still valid. - return true; - } - } else if (mLastResponse == Policy.RETRY && - ts < mLastResponseTime + MILLIS_PER_MINUTE) { - // Only allow access if we are within the retry period or we haven't - // used up our - // max retries. - return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); - } - return false; - } - - private Map<String, String> decodeExtras( - com.google.android.vending.licensing.ResponseData rawData) { - Map<String, String> results = new HashMap<String, String>(); - if (rawData == null) { - return results; - } - - try { - URI rawExtras = new URI("?" + rawData.extra); - URIQueryDecoder.DecodeQuery(rawExtras, results); - } catch (URISyntaxException e) { - Log.w(TAG, "Invalid syntax error while decoding extras data from server."); - } - return results; - } - -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java b/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java deleted file mode 100644 index e5c5e2d7ca..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Allows the developer to limit the number of devices using a single license. - * <p> - * The LICENSED response from the server contains a user identifier unique to - * the <application, user> pair. The developer can send this identifier - * to their own server along with some device identifier (a random number - * generated and stored once per application installation, - * {@link android.telephony.TelephonyManager#getDeviceId getDeviceId}, - * {@link android.provider.Settings.Secure#ANDROID_ID ANDROID_ID}, etc). - * The more sources used to identify the device, the harder it will be for an - * attacker to spoof. - * <p> - * The server can look at the <application, user, device id> tuple and - * restrict a user's application license to run on at most 10 different devices - * in a week (for example). We recommend not being too restrictive because a - * user might legitimately have multiple devices or be in the process of - * changing phones. This will catch egregious violations of multiple people - * sharing one license. - */ -public interface DeviceLimiter { - - /** - * Checks if this device is allowed to use the given user's license. - * - * @param userId the user whose license the server responded with - * @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs - */ - int isDeviceAllowed(String userId); -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java deleted file mode 100644 index 15017b3425..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.RemoteException; -import android.provider.Settings.Secure; -import android.util.Log; - -import com.android.vending.licensing.ILicenseResultListener; -import com.android.vending.licensing.ILicensingService; -import com.google.android.vending.licensing.util.Base64; -import com.google.android.vending.licensing.util.Base64DecoderException; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Queue; -import java.util.Set; - -/** - * Client library for Google Play license verifications. - * <p> - * The LicenseChecker is configured via a {@link Policy} which contains the logic to determine - * whether a user should have access to the application. For example, the Policy can define a - * threshold for allowable number of server or client failures before the library reports the user - * as not having access. - * <p> - * Must also provide the Base64-encoded RSA public key associated with your developer account. The - * public key is obtainable from the publisher site. - */ -public class LicenseChecker implements ServiceConnection { - private static final String TAG = "LicenseChecker"; - - private static final String KEY_FACTORY_ALGORITHM = "RSA"; - - // Timeout value (in milliseconds) for calls to service. - private static final int TIMEOUT_MS = 10 * 1000; - - private static final SecureRandom RANDOM = new SecureRandom(); - private static final boolean DEBUG_LICENSE_ERROR = false; - - private ILicensingService mService; - - private PublicKey mPublicKey; - private final Context mContext; - private final Policy mPolicy; - /** - * A handler for running tasks on a background thread. We don't want license processing to block - * the UI thread. - */ - private Handler mHandler; - private final String mPackageName; - private final String mVersionCode; - private final Set<LicenseValidator> mChecksInProgress = new HashSet<LicenseValidator>(); - private final Queue<LicenseValidator> mPendingChecks = new LinkedList<LicenseValidator>(); - - /** - * @param context a Context - * @param policy implementation of Policy - * @param encodedPublicKey Base64-encoded RSA public key - * @throws IllegalArgumentException if encodedPublicKey is invalid - */ - public LicenseChecker(Context context, Policy policy, String encodedPublicKey) { - mContext = context; - mPolicy = policy; - mPublicKey = generatePublicKey(encodedPublicKey); - mPackageName = mContext.getPackageName(); - mVersionCode = getVersionCode(context, mPackageName); - HandlerThread handlerThread = new HandlerThread("background thread"); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - } - - /** - * Generates a PublicKey instance from a string containing the Base64-encoded public key. - * - * @param encodedPublicKey Base64-encoded public key - * @throws IllegalArgumentException if encodedPublicKey is invalid - */ - private static PublicKey generatePublicKey(String encodedPublicKey) { - try { - byte[] decodedKey = Base64.decode(encodedPublicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); - - return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); - } catch (NoSuchAlgorithmException e) { - // This won't happen in an Android-compatible environment. - throw new RuntimeException(e); - } catch (Base64DecoderException e) { - Log.e(TAG, "Could not decode from Base64."); - throw new IllegalArgumentException(e); - } catch (InvalidKeySpecException e) { - Log.e(TAG, "Invalid key specification."); - throw new IllegalArgumentException(e); - } - } - - /** - * Checks if the user should have access to the app. Binds the service if necessary. - * <p> - * NOTE: This call uses a trivially obfuscated string (base64-encoded). For best security, we - * recommend obfuscating the string that is passed into bindService using another method of your - * own devising. - * <p> - * source string: "com.android.vending.licensing.ILicensingService" - * <p> - * - * @param callback - */ - public synchronized void checkAccess(LicenseCheckerCallback callback) { - // If we have a valid recent LICENSED response, we can skip asking - // Market. - if (mPolicy.allowAccess()) { - Log.i(TAG, "Using cached license response"); - callback.allow(Policy.LICENSED); - } else { - LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(), - callback, generateNonce(), mPackageName, mVersionCode); - - if (mService == null) { - Log.i(TAG, "Binding to licensing service."); - try { - boolean bindResult = mContext - .bindService( - new Intent( - new String( - // Base64 encoded - - // com.android.vending.licensing.ILicensingService - // Consider encoding this in another way in your - // code to improve security - Base64.decode( - "Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))) - // As of Android 5.0, implicit - // Service Intents are no longer - // allowed because it's not - // possible for the user to - // participate in disambiguating - // them. This does mean we break - // compatibility with Android - // Cupcake devices with this - // release, since setPackage was - // added in Donut. - .setPackage( - new String( - // Base64 - // encoded - - // com.android.vending - Base64.decode( - "Y29tLmFuZHJvaWQudmVuZGluZw=="))), - this, // ServiceConnection. - Context.BIND_AUTO_CREATE); - if (bindResult) { - mPendingChecks.offer(validator); - } else { - Log.e(TAG, "Could not bind to service."); - handleServiceConnectionError(validator); - } - } catch (SecurityException e) { - callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION); - } catch (Base64DecoderException e) { - e.printStackTrace(); - } - } else { - mPendingChecks.offer(validator); - runChecks(); - } - } - } - - /** - * Triggers the last deep link licensing URL returned from the server, which redirects users to a - * page which enables them to gain access to the app. If no such URL is returned by the server, it - * will go to the details page of the app in the Play Store. - */ - public void followLastLicensingUrl(Context context) { - String licensingUrl = mPolicy.getLicensingUrl(); - if (licensingUrl == null) { - licensingUrl = "https://play.google.com/store/apps/details?id=" + context.getPackageName(); - } - Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(licensingUrl)); - context.startActivity(marketIntent); - } - - private void runChecks() { - LicenseValidator validator; - while ((validator = mPendingChecks.poll()) != null) { - try { - Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName()); - mService.checkLicense( - validator.getNonce(), validator.getPackageName(), - new ResultListener(validator)); - mChecksInProgress.add(validator); - } catch (RemoteException e) { - Log.w(TAG, "RemoteException in checkLicense call.", e); - handleServiceConnectionError(validator); - } - } - } - - private synchronized void finishCheck(LicenseValidator validator) { - mChecksInProgress.remove(validator); - if (mChecksInProgress.isEmpty()) { - cleanupService(); - } - } - - private class ResultListener extends ILicenseResultListener.Stub { - private final LicenseValidator mValidator; - private Runnable mOnTimeout; - - public ResultListener(LicenseValidator validator) { - mValidator = validator; - mOnTimeout = new Runnable() { - public void run() { - Log.i(TAG, "Check timed out."); - handleServiceConnectionError(mValidator); - finishCheck(mValidator); - } - }; - startTimeout(); - } - - private static final int ERROR_CONTACTING_SERVER = 0x101; - private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; - private static final int ERROR_NON_MATCHING_UID = 0x103; - - // Runs in IPC thread pool. Post it to the Handler, so we can guarantee - // either this or the timeout runs. - public void verifyLicense(final int responseCode, final String signedData, - final String signature) { - mHandler.post(new Runnable() { - public void run() { - Log.i(TAG, "Received response."); - // Make sure it hasn't already timed out. - if (mChecksInProgress.contains(mValidator)) { - clearTimeout(); - mValidator.verify(mPublicKey, responseCode, signedData, signature); - finishCheck(mValidator); - } - if (DEBUG_LICENSE_ERROR) { - boolean logResponse; - String stringError = null; - switch (responseCode) { - case ERROR_CONTACTING_SERVER: - logResponse = true; - stringError = "ERROR_CONTACTING_SERVER"; - break; - case ERROR_INVALID_PACKAGE_NAME: - logResponse = true; - stringError = "ERROR_INVALID_PACKAGE_NAME"; - break; - case ERROR_NON_MATCHING_UID: - logResponse = true; - stringError = "ERROR_NON_MATCHING_UID"; - break; - default: - logResponse = false; - } - - if (logResponse) { - String android_id = Secure.getString(mContext.getContentResolver(), - Secure.ANDROID_ID); - Date date = new Date(); - Log.d(TAG, "Server Failure: " + stringError); - Log.d(TAG, "Android ID: " + android_id); - Log.d(TAG, "Time: " + date.toGMTString()); - } - } - - } - }); - } - - private void startTimeout() { - Log.i(TAG, "Start monitoring timeout."); - mHandler.postDelayed(mOnTimeout, TIMEOUT_MS); - } - - private void clearTimeout() { - Log.i(TAG, "Clearing timeout."); - mHandler.removeCallbacks(mOnTimeout); - } - } - - public synchronized void onServiceConnected(ComponentName name, IBinder service) { - mService = ILicensingService.Stub.asInterface(service); - runChecks(); - } - - public synchronized void onServiceDisconnected(ComponentName name) { - // Called when the connection with the service has been - // unexpectedly disconnected. That is, Market crashed. - // If there are any checks in progress, the timeouts will handle them. - Log.w(TAG, "Service unexpectedly disconnected."); - mService = null; - } - - /** - * Generates policy response for service connection errors, as a result of disconnections or - * timeouts. - */ - private synchronized void handleServiceConnectionError(LicenseValidator validator) { - mPolicy.processServerResponse(Policy.RETRY, null); - - if (mPolicy.allowAccess()) { - validator.getCallback().allow(Policy.RETRY); - } else { - validator.getCallback().dontAllow(Policy.RETRY); - } - } - - /** Unbinds service if necessary and removes reference to it. */ - private void cleanupService() { - if (mService != null) { - try { - mContext.unbindService(this); - } catch (IllegalArgumentException e) { - // Somehow we've already been unbound. This is a non-fatal - // error. - Log.e(TAG, "Unable to unbind from licensing service (already unbound)"); - } - mService = null; - } - } - - /** - * Inform the library that the context is about to be destroyed, so that any open connections - * can be cleaned up. - * <p> - * Failure to call this method can result in a crash under certain circumstances, such as during - * screen rotation if an Activity requests the license check or when the user exits the - * application. - */ - public synchronized void onDestroy() { - cleanupService(); - mHandler.getLooper().quit(); - } - - /** Generates a nonce (number used once). */ - private int generateNonce() { - return RANDOM.nextInt(); - } - - /** - * Get version code for the application package name. - * - * @param context - * @param packageName application package name - * @return the version code or empty string if package not found - */ - private static String getVersionCode(Context context, String packageName) { - try { - return String.valueOf( - context.getPackageManager().getPackageInfo(packageName, 0).versionCode); - } catch (NameNotFoundException e) { - Log.e(TAG, "Package not found. could not get version code."); - return ""; - } - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java deleted file mode 100644 index 8b869ddaaf..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Callback for the license checker library. - * <p> - * Upon checking with the Market server and conferring with the {@link Policy}, - * the library calls the appropriate callback method to communicate the result. - * <p> - * <b>The callback does not occur in the original checking thread.</b> Your - * application should post to the appropriate handling thread or lock - * accordingly. - * <p> - * The reason that is passed back with allow/dontAllow is the base status handed - * to the policy for allowed/disallowing the license. Policy.RETRY will call - * allow or dontAllow depending on other statistics associated with the policy, - * while in most cases Policy.NOT_LICENSED will call dontAllow and - * Policy.LICENSED will Allow. - */ -public interface LicenseCheckerCallback { - - /** - * Allow use. App should proceed as normal. - * - * @param reason Policy.LICENSED or Policy.RETRY typically. (although in - * theory the policy can return Policy.NOT_LICENSED here as well) - */ - public void allow(int reason); - - /** - * Don't allow use. App should inform user and take appropriate action. - * - * @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory - * the policy can return Policy.LICENSED here as well --- - * perhaps the call to the LVL took too long, for example) - */ - public void dontAllow(int reason); - - /** Application error codes. */ - public static final int ERROR_INVALID_PACKAGE_NAME = 1; - public static final int ERROR_NON_MATCHING_UID = 2; - public static final int ERROR_NOT_MARKET_MANAGED = 3; - public static final int ERROR_CHECK_IN_PROGRESS = 4; - public static final int ERROR_INVALID_PUBLIC_KEY = 5; - public static final int ERROR_MISSING_PERMISSION = 6; - - /** - * Error in application code. Caller did not call or set up license checker - * correctly. Should be considered fatal. - */ - public void applicationError(int errorCode); -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java deleted file mode 100644 index 11a00786d0..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import com.google.android.vending.licensing.util.Base64; -import com.google.android.vending.licensing.util.Base64DecoderException; - -import android.text.TextUtils; -import android.util.Log; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; - -/** - * Contains data related to a licensing request and methods to verify - * and process the response. - */ -class LicenseValidator { - private static final String TAG = "LicenseValidator"; - - // Server response codes. - private static final int LICENSED = 0x0; - private static final int NOT_LICENSED = 0x1; - private static final int LICENSED_OLD_KEY = 0x2; - private static final int ERROR_NOT_MARKET_MANAGED = 0x3; - private static final int ERROR_SERVER_FAILURE = 0x4; - private static final int ERROR_OVER_QUOTA = 0x5; - - private static final int ERROR_CONTACTING_SERVER = 0x101; - private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; - private static final int ERROR_NON_MATCHING_UID = 0x103; - - private final Policy mPolicy; - private final LicenseCheckerCallback mCallback; - private final int mNonce; - private final String mPackageName; - private final String mVersionCode; - private final DeviceLimiter mDeviceLimiter; - - LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback, - int nonce, String packageName, String versionCode) { - mPolicy = policy; - mDeviceLimiter = deviceLimiter; - mCallback = callback; - mNonce = nonce; - mPackageName = packageName; - mVersionCode = versionCode; - } - - public LicenseCheckerCallback getCallback() { - return mCallback; - } - - public int getNonce() { - return mNonce; - } - - public String getPackageName() { - return mPackageName; - } - - private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; - - /** - * Verifies the response from server and calls appropriate callback method. - * - * @param publicKey public key associated with the developer account - * @param responseCode server response code - * @param signedData signed data from server - * @param signature server signature - */ - public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) { - String userId = null; - // Skip signature check for unsuccessful requests - ResponseData data = null; - if (responseCode == LICENSED || responseCode == NOT_LICENSED || - responseCode == LICENSED_OLD_KEY) { - // Verify signature. - try { - if (TextUtils.isEmpty(signedData)) { - Log.e(TAG, "Signature verification failed: signedData is empty. " + - "(Device not signed-in to any Google accounts?)"); - handleInvalidResponse(); - return; - } - - Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); - sig.initVerify(publicKey); - sig.update(signedData.getBytes()); - - if (!sig.verify(Base64.decode(signature))) { - Log.e(TAG, "Signature verification failed."); - handleInvalidResponse(); - return; - } - } catch (NoSuchAlgorithmException e) { - // This can't happen on an Android compatible device. - throw new RuntimeException(e); - } catch (InvalidKeyException e) { - handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY); - return; - } catch (SignatureException e) { - throw new RuntimeException(e); - } catch (Base64DecoderException e) { - Log.e(TAG, "Could not Base64-decode signature."); - handleInvalidResponse(); - return; - } - - // Parse and validate response. - try { - data = ResponseData.parse(signedData); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Could not parse response."); - handleInvalidResponse(); - return; - } - - if (data.responseCode != responseCode) { - Log.e(TAG, "Response codes don't match."); - handleInvalidResponse(); - return; - } - - if (data.nonce != mNonce) { - Log.e(TAG, "Nonce doesn't match."); - handleInvalidResponse(); - return; - } - - if (!data.packageName.equals(mPackageName)) { - Log.e(TAG, "Package name doesn't match."); - handleInvalidResponse(); - return; - } - - if (!data.versionCode.equals(mVersionCode)) { - Log.e(TAG, "Version codes don't match."); - handleInvalidResponse(); - return; - } - - // Application-specific user identifier. - userId = data.userId; - if (TextUtils.isEmpty(userId)) { - Log.e(TAG, "User identifier is empty."); - handleInvalidResponse(); - return; - } - } - - switch (responseCode) { - case LICENSED: - case LICENSED_OLD_KEY: - int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId); - handleResponse(limiterResponse, data); - break; - case NOT_LICENSED: - handleResponse(Policy.NOT_LICENSED, data); - break; - case ERROR_CONTACTING_SERVER: - Log.w(TAG, "Error contacting licensing server."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_SERVER_FAILURE: - Log.w(TAG, "An error has occurred on the licensing server."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_OVER_QUOTA: - Log.w(TAG, "Licensing server is refusing to talk to this device, over quota."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_INVALID_PACKAGE_NAME: - handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME); - break; - case ERROR_NON_MATCHING_UID: - handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID); - break; - case ERROR_NOT_MARKET_MANAGED: - handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED); - break; - default: - Log.e(TAG, "Unknown response code for license check."); - handleInvalidResponse(); - } - } - - /** - * Confers with policy and calls appropriate callback method. - * - * @param response - * @param rawData - */ - private void handleResponse(int response, ResponseData rawData) { - // Update policy data and increment retry counter (if needed) - mPolicy.processServerResponse(response, rawData); - - // Given everything we know, including cached data, ask the policy if we should grant - // access. - if (mPolicy.allowAccess()) { - mCallback.allow(response); - } else { - mCallback.dontAllow(response); - } - } - - private void handleApplicationError(int code) { - mCallback.applicationError(code); - } - - private void handleInvalidResponse() { - mCallback.dontAllow(Policy.NOT_LICENSED); - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java b/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java deleted file mode 100644 index d87af3153f..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * A DeviceLimiter that doesn't limit the number of devices that can use a - * given user's license. - * <p> - * Unless you have reason to believe that your application is being pirated - * by multiple users using the same license (signing in to Market as the same - * user), we recommend you use this implementation. - */ -public class NullDeviceLimiter implements DeviceLimiter { - - public int isDeviceAllowed(String userId) { - return Policy.LICENSED; - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java deleted file mode 100644 index 008c150a8e..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Interface used as part of a {@link Policy} to allow application authors to obfuscate - * licensing data that will be stored into a SharedPreferences file. - * <p> - * Any transformation scheme must be reversable. Implementing classes may optionally implement an - * integrity check to further prevent modification to preference data. Implementing classes - * should use device-specific information as a key in the obfuscation algorithm to prevent - * obfuscated preferences from being shared among devices. - */ -public interface Obfuscator { - - /** - * Obfuscate a string that is being stored into shared preferences. - * - * @param original The data that is to be obfuscated. - * @param key The key for the data that is to be obfuscated. - * @return A transformed version of the original data. - */ - String obfuscate(String original, String key); - - /** - * Undo the transformation applied to data by the obfuscate() method. - * - * @param obfuscated The data that is to be un-obfuscated. - * @param key The key for the data that is to be un-obfuscated. - * @return The original data transformed by the obfuscate() method. - * @throws ValidationException Optionally thrown if a data integrity check fails. - */ - String unobfuscate(String obfuscated, String key) throws ValidationException; -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/Policy.java b/platform/android/java/src/com/google/android/vending/licensing/Policy.java deleted file mode 100644 index b672a078b7..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/Policy.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Policy used by {@link LicenseChecker} to determine whether a user should have - * access to the application. - */ -public interface Policy { - - /** - * Change these values to make it more difficult for tools to automatically - * strip LVL protection from your APK. - */ - - /** - * LICENSED means that the server returned back a valid license response - */ - public static final int LICENSED = 0x0100; - /** - * NOT_LICENSED means that the server returned back a valid license response - * that indicated that the user definitively is not licensed - */ - public static final int NOT_LICENSED = 0x0231; - /** - * RETRY means that the license response was unable to be determined --- - * perhaps as a result of faulty networking - */ - public static final int RETRY = 0x0123; - - /** - * Provide results from contact with the license server. Retry counts are - * incremented if the current value of response is RETRY. Results will be - * used for any future policy decisions. - * - * @param response the result from validating the server response - * @param rawData the raw server response data, can be null for RETRY - */ - void processServerResponse(int response, ResponseData rawData); - - /** - * Check if the user should be allowed access to the application. - */ - boolean allowAccess(); - - /** - * Gets the licensing URL returned by the server that can enable access for unlicensed apps (e.g. - * buy app on the Play Store). - */ - String getLicensingUrl(); -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java deleted file mode 100644 index feb579af04..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import android.content.SharedPreferences; -import android.util.Log; - -/** - * An wrapper for SharedPreferences that transparently performs data obfuscation. - */ -public class PreferenceObfuscator { - - private static final String TAG = "PreferenceObfuscator"; - - private final SharedPreferences mPreferences; - private final Obfuscator mObfuscator; - private SharedPreferences.Editor mEditor; - - /** - * Constructor. - * - * @param sp A SharedPreferences instance provided by the system. - * @param o The Obfuscator to use when reading or writing data. - */ - public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) { - mPreferences = sp; - mObfuscator = o; - mEditor = null; - } - - public void putString(String key, String value) { - if (mEditor == null) { - mEditor = mPreferences.edit(); - // -- GODOT start -- - mEditor.apply(); - // -- GODOT end -- - } - String obfuscatedValue = mObfuscator.obfuscate(value, key); - mEditor.putString(key, obfuscatedValue); - } - - public String getString(String key, String defValue) { - String result; - String value = mPreferences.getString(key, null); - if (value != null) { - try { - result = mObfuscator.unobfuscate(value, key); - } catch (ValidationException e) { - // Unable to unobfuscate, data corrupt or tampered - Log.w(TAG, "Validation error while reading preference: " + key); - result = defValue; - } - } else { - // Preference not found - result = defValue; - } - return result; - } - - public void commit() { - if (mEditor != null) { - mEditor.commit(); - mEditor = null; - } - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java b/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java deleted file mode 100644 index 3b5d557e76..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import android.text.TextUtils; - -import java.util.regex.Pattern; - -/** - * ResponseData from licensing server. - */ -public class ResponseData { - - public int responseCode; - public int nonce; - public String packageName; - public String versionCode; - public String userId; - public long timestamp; - /** Response-specific data. */ - public String extra; - - /** - * Parses response string into ResponseData. - * - * @param responseData response data string - * @throws IllegalArgumentException upon parsing error - * @return ResponseData object - */ - public static ResponseData parse(String responseData) { - // Must parse out main response data and response-specific data. - int index = responseData.indexOf(':'); - String mainData, extraData; - if (-1 == index) { - mainData = responseData; - extraData = ""; - } else { - mainData = responseData.substring(0, index); - extraData = index >= responseData.length() ? "" : responseData.substring(index + 1); - } - - String[] fields = TextUtils.split(mainData, Pattern.quote("|")); - if (fields.length < 6) { - throw new IllegalArgumentException("Wrong number of fields."); - } - - ResponseData data = new ResponseData(); - data.extra = extraData; - data.responseCode = Integer.parseInt(fields[0]); - data.nonce = Integer.parseInt(fields[1]); - data.packageName = fields[2]; - data.versionCode = fields[3]; - // Application-specific user identifier. - data.userId = fields[4]; - data.timestamp = Long.parseLong(fields[5]); - - return data; - } - - @Override - public String toString() { - return TextUtils.join("|", new Object[] { - responseCode, nonce, packageName, versionCode, - userId, timestamp - }); - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java deleted file mode 100644 index e2f0bfdca8..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - -import com.google.android.vending.licensing.util.URIQueryDecoder; - -/** - * Default policy. All policy decisions are based off of response data received - * from the licensing service. Specifically, the licensing server sends the - * following information: response validity period, error retry period, - * error retry count and a URL for restoring app access in unlicensed cases. - * <p> - * These values will vary based on the the way the application is configured in - * the Google Play publishing console, such as whether the application is - * marked as free or is within its refund period, as well as how often an - * application is checking with the licensing service. - * <p> - * Developers who need more fine grained control over their application's - * licensing policy should implement a custom Policy. - */ -public class ServerManagedPolicy implements Policy { - - private static final String TAG = "ServerManagedPolicy"; - private static final String PREFS_FILE = "com.google.android.vending.licensing.ServerManagedPolicy"; - private static final String PREF_LAST_RESPONSE = "lastResponse"; - private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; - private static final String PREF_RETRY_UNTIL = "retryUntil"; - private static final String PREF_MAX_RETRIES = "maxRetries"; - private static final String PREF_RETRY_COUNT = "retryCount"; - private static final String PREF_LICENSING_URL = "licensingUrl"; - private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; - private static final String DEFAULT_RETRY_UNTIL = "0"; - private static final String DEFAULT_MAX_RETRIES = "0"; - private static final String DEFAULT_RETRY_COUNT = "0"; - - private static final long MILLIS_PER_MINUTE = 60 * 1000; - - private long mValidityTimestamp; - private long mRetryUntil; - private long mMaxRetries; - private long mRetryCount; - private long mLastResponseTime = 0; - private int mLastResponse; - private String mLicensingUrl; - private PreferenceObfuscator mPreferences; - - /** - * @param context The context for the current application - * @param obfuscator An obfuscator to be used with preferences. - */ - public ServerManagedPolicy(Context context, Obfuscator obfuscator) { - // Import old values - SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); - mPreferences = new PreferenceObfuscator(sp, obfuscator); - mLastResponse = Integer.parseInt( - mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); - mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, - DEFAULT_VALIDITY_TIMESTAMP)); - mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); - mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); - mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); - mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null); - } - - /** - * Process a new response from the license server. - * <p> - * This data will be used for computing future policy decisions. The - * following parameters are processed: - * <ul> - * <li>VT: the timestamp that the client should consider the response valid - * until - * <li>GT: the timestamp that the client should ignore retry errors until - * <li>GR: the number of retry errors that the client should ignore - * <li>LU: a deep link URL that can enable access for unlicensed apps (e.g. - * buy app on the Play Store) - * </ul> - * - * @param response the result from validating the server response - * @param rawData the raw server response data - */ - public void processServerResponse(int response, ResponseData rawData) { - - // Update retry counter - if (response != Policy.RETRY) { - setRetryCount(0); - } else { - setRetryCount(mRetryCount + 1); - } - - // Update server policy data - Map<String, String> extras = decodeExtras(rawData); - if (response == Policy.LICENSED) { - mLastResponse = response; - // Reset the licensing URL since it is only applicable for NOT_LICENSED responses. - setLicensingUrl(null); - setValidityTimestamp(extras.get("VT")); - setRetryUntil(extras.get("GT")); - setMaxRetries(extras.get("GR")); - } else if (response == Policy.NOT_LICENSED) { - // Clear out stale retry params - setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); - setRetryUntil(DEFAULT_RETRY_UNTIL); - setMaxRetries(DEFAULT_MAX_RETRIES); - // Update the licensing URL - setLicensingUrl(extras.get("LU")); - } - - setLastResponse(response); - mPreferences.commit(); - } - - /** - * Set the last license response received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param l the response - */ - private void setLastResponse(int l) { - mLastResponseTime = System.currentTimeMillis(); - mLastResponse = l; - mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); - } - - /** - * Set the current retry count and add to preferences. You must manually - * call PreferenceObfuscator.commit() to commit these changes to disk. - * - * @param c the new retry count - */ - private void setRetryCount(long c) { - mRetryCount = c; - mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); - } - - public long getRetryCount() { - return mRetryCount; - } - - /** - * Set the last validity timestamp (VT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param validityTimestamp the VT string received - */ - private void setValidityTimestamp(String validityTimestamp) { - Long lValidityTimestamp; - try { - lValidityTimestamp = Long.parseLong(validityTimestamp); - } catch (NumberFormatException e) { - // No response or not parsable, expire in one minute. - Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); - lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; - validityTimestamp = Long.toString(lValidityTimestamp); - } - - mValidityTimestamp = lValidityTimestamp; - mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); - } - - public long getValidityTimestamp() { - return mValidityTimestamp; - } - - /** - * Set the retry until timestamp (GT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param retryUntil the GT string received - */ - private void setRetryUntil(String retryUntil) { - Long lRetryUntil; - try { - lRetryUntil = Long.parseLong(retryUntil); - } catch (NumberFormatException e) { - // No response or not parsable, expire immediately - Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); - retryUntil = "0"; - lRetryUntil = 0l; - } - - mRetryUntil = lRetryUntil; - mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); - } - - public long getRetryUntil() { - return mRetryUntil; - } - - /** - * Set the max retries value (GR) as received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param maxRetries the GR string received - */ - private void setMaxRetries(String maxRetries) { - Long lMaxRetries; - try { - lMaxRetries = Long.parseLong(maxRetries); - } catch (NumberFormatException e) { - // No response or not parsable, expire immediately - Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); - maxRetries = "0"; - lMaxRetries = 0l; - } - - mMaxRetries = lMaxRetries; - mPreferences.putString(PREF_MAX_RETRIES, maxRetries); - } - - public long getMaxRetries() { - return mMaxRetries; - } - - /** - * Set the license URL value (LU) as received from the server and add to preferences. You must - * manually call PreferenceObfuscator.commit() to commit these changes to disk. - * - * @param url the LU string received - */ - private void setLicensingUrl(String url) { - mLicensingUrl = url; - mPreferences.putString(PREF_LICENSING_URL, url); - } - - public String getLicensingUrl() { - return mLicensingUrl; - } - - /** - * {@inheritDoc} - * - * This implementation allows access if either:<br> - * <ol> - * <li>a LICENSED response was received within the validity period - * <li>a RETRY response was received in the last minute, and we are under - * the RETRY count or in the RETRY period. - * </ol> - */ - public boolean allowAccess() { - long ts = System.currentTimeMillis(); - if (mLastResponse == Policy.LICENSED) { - // Check if the LICENSED response occurred within the validity timeout. - if (ts <= mValidityTimestamp) { - // Cached LICENSED response is still valid. - return true; - } - } else if (mLastResponse == Policy.RETRY && - ts < mLastResponseTime + MILLIS_PER_MINUTE) { - // Only allow access if we are within the retry period or we haven't used up our - // max retries. - return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); - } - return false; - } - - private Map<String, String> decodeExtras( - com.google.android.vending.licensing.ResponseData rawData) { - Map<String, String> results = new HashMap<String, String>(); - if (rawData == null) { - return results; - } - - try { - URI rawExtras = new URI("?" + rawData.extra); - URIQueryDecoder.DecodeQuery(rawExtras, results); - } catch (URISyntaxException e) { - Log.w(TAG, "Invalid syntax error while decoding extras data from server."); - } - return results; - } - -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java deleted file mode 100644 index c2d55c37f1..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import android.util.Log; -import com.google.android.vending.licensing.util.URIQueryDecoder; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; - -/** - * Non-caching policy. All requests will be sent to the licensing service, - * and no local caching is performed. - * <p> - * Using a non-caching policy ensures that there is no local preference data - * for malicious users to tamper with. As a side effect, applications - * will not be permitted to run while offline. Developers should carefully - * weigh the risks of using this Policy over one which implements caching, - * such as ServerManagedPolicy. - * <p> - * Access to the application is only allowed if a LICENSED response is. - * received. All other responses (including RETRY) will deny access. - */ -public class StrictPolicy implements Policy { - - private static final String TAG = "StrictPolicy"; - - private int mLastResponse; - private String mLicensingUrl; - - public StrictPolicy() { - // Set default policy. This will force the application to check the policy on launch. - mLastResponse = Policy.RETRY; - mLicensingUrl = null; - } - - /** - * Process a new response from the license server. Since we aren't - * performing any caching, this equates to reading the LicenseResponse. - * Any cache-related ResponseData is ignored, but the licensing URL - * extra is still extracted in cases where the app is unlicensed. - * - * @param response the result from validating the server response - * @param rawData the raw server response data - */ - public void processServerResponse(int response, ResponseData rawData) { - mLastResponse = response; - - if (response == Policy.NOT_LICENSED) { - Map<String, String> extras = decodeExtras(rawData); - mLicensingUrl = extras.get("LU"); - } - } - - /** - * {@inheritDoc} - * - * This implementation allows access if and only if a LICENSED response - * was received the last time the server was contacted. - */ - public boolean allowAccess() { - return (mLastResponse == Policy.LICENSED); - } - - public String getLicensingUrl() { - return mLicensingUrl; - } - - private Map<String, String> decodeExtras( - com.google.android.vending.licensing.ResponseData rawData) { - Map<String, String> results = new HashMap<String, String>(); - if (rawData == null) { - return results; - } - - try { - URI rawExtras = new URI("?" + rawData.extra); - URIQueryDecoder.DecodeQuery(rawExtras, results); - } catch (URISyntaxException e) { - Log.w(TAG, "Invalid syntax error while decoding extras data from server."); - } - return results; - } - -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java b/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java deleted file mode 100644 index ee4df47c68..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Indicates that an error occurred while validating the integrity of data managed by an - * {@link Obfuscator}.} - */ -public class ValidationException extends Exception { - public ValidationException() { - super(); - } - - public ValidationException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java b/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java deleted file mode 100644 index a8bf65f9ca..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java +++ /dev/null @@ -1,578 +0,0 @@ -// Portions copyright 2002, Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.android.vending.licensing.util; - -// This code was converted from code at http://iharder.sourceforge.net/base64/ -// Lots of extraneous features were removed. -/* The original code said: - * <p> - * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit - * <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a> - * periodically to check for updates or to contribute improvements. - * </p> - * - * @author Robert Harder - * @author rharder@usa.net - * @version 1.3 - */ - -// -- GODOT start -- -import com.godot.game.BuildConfig; -// -- GODOT end -- - -/** - * Base64 converter class. This code is not a full-blown MIME encoder; - * it simply converts binary data to base64 data and back. - * - * <p>Note {@link CharBase64} is a GWT-compatible implementation of this - * class. - */ -public class Base64 { - /** Specify encoding (value is {@code true}). */ - public final static boolean ENCODE = true; - - /** Specify decoding (value is {@code false}). */ - public final static boolean DECODE = false; - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte) '='; - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte) '\n'; - - /** - * The 64 valid Base64 values. - */ - private final static byte[] ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '+', (byte) '/'}; - - /** - * The 64 valid web safe Base64 values. - */ - private final static byte[] WEBSAFE_ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '-', (byte) '_'}; - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - /** The web safe decodabet */ - private final static byte[] WEBSAFE_DECODABET = - {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 - 62, // Dash '-' sign at decimal 45 - -9, -9, // Decimal 46-47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91-94 - 63, // Underscore '_' at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - // Indicates white space in encoding - private final static byte WHITE_SPACE_ENC = -5; - // Indicates equals sign in encoding - private final static byte EQUALS_SIGN_ENC = -1; - - /** Defeats instantiation. */ - private Base64() { - } - - /* ******** E N C O D I N G M E T H O D S ******** */ - - /** - * Encodes up to three bytes of the array <var>source</var> - * and writes the resulting four Base64 bytes to <var>destination</var>. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * <var>srcOffset</var> and <var>destOffset</var>. - * This method does not check to make sure your arrays - * are large enough to accommodate <var>srcOffset</var> + 3 for - * the <var>source</var> array or <var>destOffset</var> + 4 for - * the <var>destination</var> array. - * The actual number of significant bytes in your array is - * given by <var>numSigBytes</var>. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param alphabet is the encoding alphabet - * @return the <var>destination</var> array - * @since 1.3 - */ - private static byte[] encode3to4(byte[] source, int srcOffset, - int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index alphabet - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = - (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; - return destination; - case 2: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - case 1: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - default: - return destination; - } // end switch - } // end encode3to4 - - /** - * Encodes a byte array into Base64 notation. - * Equivalent to calling - * {@code encodeBytes(source, 0, source.length)} - * - * @param source The data to convert - * @since 1.4 - */ - public static String encode(byte[] source) { - return encode(source, 0, source.length, ALPHABET, true); - } - - /** - * Encodes a byte array into web safe Base64 notation. - * - * @param source The data to convert - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - */ - public static String encodeWebSafe(byte[] source, boolean doPadding) { - return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param alphabet is the encoding alphabet - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - * @since 1.4 - */ - public static String encode(byte[] source, int off, int len, byte[] alphabet, - boolean doPadding) { - byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); - int outLen = outBuff.length; - - // If doPadding is false, set length to truncate '=' - // padding characters - while (doPadding == false && outLen > 0) { - if (outBuff[outLen - 1] != '=') { - break; - } - outLen -= 1; - } - - return new String(outBuff, 0, outLen); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param alphabet is the encoding alphabet - * @param maxLineLength maximum length of one line. - * @return the BASE64-encoded byte array - */ - public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, - int maxLineLength) { - int lenDiv3 = (len + 2) / 3; // ceil(len / 3) - int len43 = lenDiv3 * 4; - byte[] outBuff = new byte[len43 // Main 4:3 - + (len43 / maxLineLength)]; // New lines - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - - // The following block of code is the same as - // encode3to4( source, d + off, 3, outBuff, e, alphabet ); - // but inlined for faster encoding (~20% improvement) - int inBuff = - ((source[d + off] << 24) >>> 8) - | ((source[d + 1 + off] << 24) >>> 16) - | ((source[d + 2 + off] << 24) >>> 24); - outBuff[e] = alphabet[(inBuff >>> 18)]; - outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; - - lineLength += 4; - if (lineLength == maxLineLength) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // end for: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e, alphabet); - - lineLength += 4; - if (lineLength == maxLineLength) { - // Add a last newline - outBuff[e + 4] = NEW_LINE; - e++; - } - e += 4; - } - - // -- GODOT start -- - //assert (e == outBuff.length); - if (BuildConfig.DEBUG && e != outBuff.length) - throw new RuntimeException(); - // -- GODOT end -- - return outBuff; - } - - - /* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array <var>source</var> - * and writes the resulting bytes (up to three of them) - * to <var>destination</var>. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * <var>srcOffset</var> and <var>destOffset</var>. - * This method does not check to make sure your arrays - * are large enough to accommodate <var>srcOffset</var> + 4 for - * the <var>source</var> array or <var>destOffset</var> + 3 for - * the <var>destination</var> array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param decodabet the decodabet for decoding Base64 content - * @return the number of decoded bytes converted - * @since 1.3 - */ - private static int decode4to3(byte[] source, int srcOffset, - byte[] destination, int destOffset, byte[] decodabet) { - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Example: DkL= - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } else { - // Example: DkLE - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) - | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - return 3; - } - } // end decodeToBytes - - - /** - * Decodes data from Base64 notation. - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - * @since 1.4 - */ - public static byte[] decode(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decode(bytes, 0, bytes.length); - } - - /** - * Decodes data from web safe Base64 notation. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decodeWebSafe(bytes, 0, bytes.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source) throws Base64DecoderException { - return decode(source, 0, source.length); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded data. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(byte[] source) - throws Base64DecoderException { - return decodeWebSafe(source, 0, source.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, DECODABET); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded byte array. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - */ - public static byte[] decodeWebSafe(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, WEBSAFE_DECODABET); - } - - /** - * Decodes Base64 content using the supplied decodabet and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param decodabet the decodabet for decoding Base64 content - * @return decoded data - */ - public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) - throws Base64DecoderException { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for (i = 0; i < len; i++) { - sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits - sbiDecode = decodabet[sbiCrop]; - - if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better - if (sbiDecode >= EQUALS_SIGN_ENC) { - // An equals sign (for padding) must not occur at position 0 or 1 - // and must be the last byte[s] in the encoded value - if (sbiCrop == EQUALS_SIGN) { - int bytesLeft = len - i; - byte lastByte = (byte) (source[len - 1 + off] & 0x7f); - if (b4Posn == 0 || b4Posn == 1) { - throw new Base64DecoderException( - "invalid padding byte '=' at byte offset " + i); - } else if ((b4Posn == 3 && bytesLeft > 2) - || (b4Posn == 4 && bytesLeft > 1)) { - throw new Base64DecoderException( - "padding byte '=' falsely signals end of encoded value " - + "at offset " + i); - } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { - throw new Base64DecoderException( - "encoded value has invalid trailing byte"); - } - break; - } - - b4[b4Posn++] = sbiCrop; - if (b4Posn == 4) { - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - b4Posn = 0; - } - } - } else { - throw new Base64DecoderException("Bad Base64 input character at " + i - + ": " + source[i + off] + "(decimal)"); - } - } - - // Because web safe encoding allows non padding base64 encodes, we - // need to pad the rest of the b4 buffer with equal signs when - // b4Posn != 0. There can be at most 2 equal signs at the end of - // four characters, so the b4 buffer must have two or three - // characters. This also catches the case where the input is - // padded with EQUALS_SIGN - if (b4Posn != 0) { - if (b4Posn == 1) { - throw new Base64DecoderException("single trailing character at offset " - + (len - 1)); - } - b4[b4Posn++] = EQUALS_SIGN; - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - } - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java b/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java deleted file mode 100644 index 1aef1b54b8..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2002, Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.android.vending.licensing.util; - -/** - * Exception thrown when encountering an invalid Base64 input character. - * - * @author nelson - */ -public class Base64DecoderException extends Exception { - public Base64DecoderException() { - super(); - } - - public Base64DecoderException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java b/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java deleted file mode 100644 index 5155bf5ac3..0000000000 --- a/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing.util; - -import android.util.Log; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.util.Map; -import java.util.Scanner; - -public class URIQueryDecoder { - private static final String TAG = "URIQueryDecoder"; - - /** - * Decodes the query portion of the passed-in URI. - * - * @param encodedURI the URI containing the query to decode - * @param results a map containing all query parameters. Query parameters that do not have a - * value will map to a null string - */ - static public void DecodeQuery(URI encodedURI, Map<String, String> results) { - Scanner scanner = new Scanner(encodedURI.getRawQuery()); - scanner.useDelimiter("&"); - try { - while (scanner.hasNext()) { - String param = scanner.next(); - String[] valuePair = param.split("="); - String name, value; - if (valuePair.length == 1) { - value = null; - } else if (valuePair.length == 2) { - value = URLDecoder.decode(valuePair[1], "UTF-8"); - } else { - throw new IllegalArgumentException("query parameter invalid"); - } - name = URLDecoder.decode(valuePair[0], "UTF-8"); - results.put(name, value); - } - } catch (UnsupportedEncodingException e) { - // This should never happen. - Log.e(TAG, "UTF-8 Not Recognized as a charset. Device configuration Error."); - } - } -} |