| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,280 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
|
|
||
| /** | ||
| * 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 { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger("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. | ||
| */ | ||
| final Messenger mMessenger = new Messenger(new Handler() { | ||
| @Override | ||
| public void handleMessage(Message msg) { | ||
| switch (msg.what) { | ||
| case MSG_ONDOWNLOADPROGRESS: | ||
| Bundle bun = msg.getData(); | ||
| if ( null != mContext ) { | ||
| bun.setClassLoader(mContext.getClassLoader()); | ||
| DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData() | ||
| .getParcelable(PARAM_PROGRESS); | ||
| mItf.onDownloadProgress(dpi); | ||
| } | ||
| break; | ||
| case MSG_ONDOWNLOADSTATE_CHANGED: | ||
| mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE)); | ||
| break; | ||
| case MSG_ONSERVICECONNECTED: | ||
| mItf.onServiceConnected( | ||
| (Messenger) msg.getData().getParcelable(PARAM_MESSENGER)); | ||
| break; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| 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.debug("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); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
|
|
||
|
|
||
| /** | ||
| * 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; | ||
| final Messenger mMessenger = new Messenger(new Handler() { | ||
| @Override | ||
| public void handleMessage(Message msg) { | ||
| switch (msg.what) { | ||
| case MSG_REQUEST_ABORT_DOWNLOAD: | ||
| mItf.requestAbortDownload(); | ||
| break; | ||
| case MSG_REQUEST_CONTINUE_DOWNLOAD: | ||
| mItf.requestContinueDownload(); | ||
| break; | ||
| case MSG_REQUEST_PAUSE_DOWNLOAD: | ||
| mItf.requestPauseDownload(); | ||
| break; | ||
| case MSG_SET_DOWNLOAD_FLAGS: | ||
| mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS)); | ||
| break; | ||
| case MSG_REQUEST_DOWNLOAD_STATE: | ||
| mItf.requestDownloadStatus(); | ||
| break; | ||
| case MSG_REQUEST_CLIENT_UPDATE: | ||
| mItf.onClientUpdated((Messenger) msg.getData().getParcelable( | ||
| PARAM_MESSENGER)); | ||
| break; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,311 @@ | ||
| /* | ||
| * 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.Build; | ||
| import android.os.Environment; | ||
| import android.os.StatFs; | ||
| import android.os.SystemClock; | ||
|
|
||
| import com.android.vending.expansion.downloader.R; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| 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 { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger("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.debug("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.warn("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.error("Notification called when total is zero"); | ||
| } | ||
| return ""; | ||
| } | ||
| return String.format("%.2f", | ||
| (float) overallProgress / (1024.0f * 1024.0f)) | ||
| + "MB /" + | ||
| String.format("%.2f", (float) overallTotal / | ||
| (1024.0f * 1024.0f)) + "MB"; | ||
| } | ||
|
|
||
| /** | ||
| * 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.error("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.error("Notification called when total is zero"); | ||
| } | ||
| return ""; | ||
| } | ||
| return Long.toString(overallProgress * 100 / overallTotal) + "%"; | ||
| } | ||
|
|
||
| public static String getSpeedString(float bytesPerMillisecond) { | ||
| return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| static public String getSaveFilePath(Context c) { | ||
| File root = Environment.getExternalStorageDirectory(); | ||
| String path = Build.VERSION.SDK_INT >= 23 ? Constants.EXP_PATH_API23 : Constants.EXP_PATH; | ||
| return root.toString() + path + c.getPackageName(); | ||
| } | ||
|
|
||
| /** | ||
| * 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 Market --- 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; | ||
| } | ||
|
|
||
| /** | ||
| * 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; | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| /* | ||
| * 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); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /* | ||
| * 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); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * 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); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * Contains useful helper functions, typically tied to the application context. | ||
| */ | ||
| class SystemFacade { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger("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.warn("couldn't get connectivity manager"); | ||
| return null; | ||
| } | ||
|
|
||
| NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); | ||
| if (activeInfo == null) { | ||
| if (Constants.LOGVV) { | ||
| LOG.info("network is not available"); | ||
| } | ||
| return null; | ||
| } | ||
| return activeInfo.getType(); | ||
| } | ||
|
|
||
| public boolean isNetworkRoaming() { | ||
| ConnectivityManager connectivity = | ||
| (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); | ||
| if (connectivity == null) { | ||
| LOG.warn("couldn't get connectivity manager"); | ||
| return false; | ||
| } | ||
|
|
||
| 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.warn("couldn't get telephony manager"); | ||
| return false; | ||
| } | ||
| boolean isRoaming = isMobile && tm.isNetworkRoaming(); | ||
| if (Constants.LOGVV && isRoaming) { | ||
| LOG.info("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(); | ||
| } | ||
| } |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /* | ||
| * 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 org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * 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 static final Logger LOG = LoggerFactory.getLogger("CancellableIntentService"); | ||
|
|
||
| private String mName; | ||
| private boolean mRedelivery; | ||
| private volatile ServiceHandler mServiceHandler; | ||
| private volatile Looper mServiceLooper; | ||
| 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.debug("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.debug("stopSelf"); | ||
| CustomIntentService.this.stopSelf(paramMessage.arg1); | ||
| LOG.debug("afterStopSelf"); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| /** | ||
| * Uses the class-loader model to utilize the updated notification builders in | ||
| * Honeycomb while maintaining a compatible version for older devices. | ||
| */ | ||
| public class CustomNotificationFactory { | ||
| static public DownloadNotification.ICustomNotification createCustomNotification() { | ||
| if (android.os.Build.VERSION.SDK_INT > 13) | ||
| return new V14CustomNotification(); | ||
| else | ||
| throw new RuntimeException(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /* | ||
| * 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.Helpers; | ||
|
|
||
| /** | ||
| * 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; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| /* | ||
| * 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.Notification; | ||
| import android.app.NotificationManager; | ||
| import android.app.PendingIntent; | ||
| import android.content.Context; | ||
| import android.os.Messenger; | ||
|
|
||
| import com.android.vending.expansion.downloader.R; | ||
| 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; | ||
|
|
||
| /** | ||
| * 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 String mCurrentTitle; | ||
|
|
||
| private IDownloaderClient mClientProxy; | ||
| final ICustomNotification mCustomNotification; | ||
| private Notification.Builder mNotificationBuilder; | ||
| private Notification.Builder mCurrentNotificationBuilder; | ||
| private CharSequence mLabel; | ||
| private String mCurrentText; | ||
| private PendingIntent mContentIntent; | ||
| private DownloadProgressInfo mProgressInfo; | ||
|
|
||
| static final String LOGTAG = "DownloadNotification"; | ||
| static final int NOTIFICATION_ID = LOGTAG.hashCode(); | ||
|
|
||
| public PendingIntent getClientIntent() { | ||
| return mContentIntent; | ||
| } | ||
|
|
||
| public void setClientIntent(PendingIntent mClientIntent) { | ||
| this.mContentIntent = mClientIntent; | ||
| } | ||
|
|
||
| public void resendState() { | ||
| if (null != mClientProxy) { | ||
| mClientProxy.onDownloadStateChanged(mState); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void onDownloadStateChanged(int newState) { | ||
| boolean completed = false; | ||
| 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: | ||
| completed = true; | ||
| 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; | ||
| } | ||
| if (completed) { | ||
| mNotificationManager.cancel(NOTIFICATION_ID); | ||
| } else { | ||
| mCurrentText = mContext.getString(stringDownloadID); | ||
| mCurrentTitle = mLabel.toString(); | ||
| mCurrentNotificationBuilder.setTicker(mLabel + ": " + mCurrentText); | ||
| mCurrentNotificationBuilder.setSmallIcon(iconResource); | ||
| mCurrentNotificationBuilder.setContentTitle(mCurrentTitle); | ||
| mCurrentNotificationBuilder.setContentText(mCurrentText); | ||
| mCurrentNotificationBuilder.setContentIntent(mContentIntent); | ||
| mCurrentNotificationBuilder.setOngoing(ongoingEvent); | ||
| mCurrentNotificationBuilder.setAutoCancel(!ongoingEvent); | ||
| mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotificationBuilder.build()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void onDownloadProgress(DownloadProgressInfo progress) { | ||
| mProgressInfo = progress; | ||
| if (null != mClientProxy) { | ||
| mClientProxy.onDownloadProgress(progress); | ||
| } | ||
| if (progress.mOverallTotal <= 0) { | ||
| // we just show the text | ||
| mNotificationBuilder.setTicker(mCurrentTitle); | ||
| mNotificationBuilder.setSmallIcon(android.R.drawable.stat_sys_download); | ||
| mNotificationBuilder.setContentTitle(mCurrentTitle); | ||
| mNotificationBuilder.setContentText(mCurrentText); | ||
| mNotificationBuilder.setContentIntent(mContentIntent); | ||
| mCurrentNotificationBuilder = mNotificationBuilder; | ||
| } else { | ||
| mCustomNotification.setCurrentBytes(progress.mOverallProgress); | ||
| mCustomNotification.setTotalBytes(progress.mOverallTotal); | ||
| mCustomNotification.setIcon(android.R.drawable.stat_sys_download); | ||
| mCustomNotification.setPendingIntent(mContentIntent); | ||
| mCustomNotification.setTicker(mLabel + ": " + mCurrentText); | ||
| mCustomNotification.setTitle(mLabel); | ||
| mCustomNotification.setTimeRemaining(progress.mTimeRemaining); | ||
| mCurrentNotificationBuilder = mCustomNotification.updateNotification(mContext); | ||
| } | ||
| mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotificationBuilder.build()); | ||
| } | ||
|
|
||
| public interface ICustomNotification { | ||
| void setTitle(CharSequence title); | ||
|
|
||
| void setTicker(CharSequence ticker); | ||
|
|
||
| void setPendingIntent(PendingIntent mContentIntent); | ||
|
|
||
| void setTotalBytes(long totalBytes); | ||
|
|
||
| void setCurrentBytes(long currentBytes); | ||
|
|
||
| void setIcon(int iconResource); | ||
|
|
||
| void setTimeRemaining(long timeRemaining); | ||
|
|
||
| Notification.Builder updateNotification(Context c); | ||
| } | ||
|
|
||
| /** | ||
| * 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); | ||
| mCustomNotification = CustomNotificationFactory | ||
| .createCustomNotification(); | ||
| mNotificationBuilder = new Notification.Builder(ctx); | ||
| mCurrentNotificationBuilder = mNotificationBuilder; | ||
|
|
||
| } | ||
|
|
||
| @Override | ||
| public void onServiceConnected(Messenger m) { | ||
| } | ||
|
|
||
| } |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| /* | ||
| * 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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| /* | ||
| * 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.Notification; | ||
| import android.app.PendingIntent; | ||
| import android.content.Context; | ||
|
|
||
| import com.android.vending.expansion.downloader.R; | ||
| import com.google.android.vending.expansion.downloader.Helpers; | ||
|
|
||
| public class V14CustomNotification implements DownloadNotification.ICustomNotification { | ||
|
|
||
| CharSequence mTitle; | ||
| CharSequence mTicker; | ||
| int mIcon; | ||
| long mTotalKB = -1; | ||
| long mCurrentKB = -1; | ||
| long mTimeRemaining; | ||
| PendingIntent mPendingIntent; | ||
|
|
||
| @Override | ||
| public void setIcon(int icon) { | ||
| mIcon = icon; | ||
| } | ||
|
|
||
| @Override | ||
| public void setTitle(CharSequence title) { | ||
| mTitle = title; | ||
| } | ||
|
|
||
| @Override | ||
| public void setTotalBytes(long totalBytes) { | ||
| mTotalKB = totalBytes; | ||
| } | ||
|
|
||
| @Override | ||
| public void setCurrentBytes(long currentBytes) { | ||
| mCurrentKB = currentBytes; | ||
| } | ||
|
|
||
| void setProgress(Notification.Builder builder) { | ||
|
|
||
| } | ||
|
|
||
| @Override | ||
| public Notification.Builder updateNotification(Context c) { | ||
| Notification.Builder builder = new Notification.Builder(c); | ||
| builder.setContentTitle(mTitle); | ||
| if (mTotalKB > 0 && -1 != mCurrentKB) { | ||
| builder.setProgress((int) (mTotalKB >> 8), (int) (mCurrentKB >> 8), false); | ||
| } else { | ||
| builder.setProgress(0, 0, true); | ||
| } | ||
| builder.setContentText(Helpers.getDownloadProgressString(mCurrentKB, mTotalKB)); | ||
| builder.setContentInfo(c.getString(R.string.time_remaining_notification, | ||
| Helpers.getTimeRemaining(mTimeRemaining))); | ||
| if (mIcon != 0) { | ||
| builder.setSmallIcon(mIcon); | ||
| } else { | ||
| int iconResource = android.R.drawable.stat_sys_download; | ||
| builder.setSmallIcon(iconResource); | ||
| } | ||
| builder.setOngoing(true); | ||
| builder.setTicker(mTicker); | ||
| builder.setContentIntent(mPendingIntent); | ||
| builder.setOnlyAlertOnce(true); | ||
|
|
||
| return builder; | ||
| } | ||
|
|
||
| @Override | ||
| public void setPendingIntent(PendingIntent contentIntent) { | ||
| mPendingIntent = contentIntent; | ||
| } | ||
|
|
||
| @Override | ||
| public void setTicker(CharSequence ticker) { | ||
| mTicker = ticker; | ||
| } | ||
|
|
||
| @Override | ||
| public void setTimeRemaining(long timeRemaining) { | ||
| mTimeRemaining = timeRemaining; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <!-- | ||
| /* | ||
| ** Copyright 2008, 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. | ||
| */ | ||
| --> | ||
|
|
||
| <LinearLayout android:layout_width="match_parent" | ||
| android:layout_height="match_parent" | ||
| android:baselineAligned="false" | ||
| android:orientation="horizontal" android:id="@+id/notificationLayout" xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
|
||
| <RelativeLayout | ||
| android:layout_width="35dp" | ||
| android:layout_height="fill_parent" | ||
| android:paddingTop="10dp" | ||
| android:paddingBottom="8dp" > | ||
|
|
||
| <ImageView | ||
| android:id="@+id/appIcon" | ||
| android:layout_width="fill_parent" | ||
| android:layout_height="25dp" | ||
| android:scaleType="centerInside" | ||
| android:layout_alignParentLeft="true" | ||
| android:layout_alignParentTop="true" | ||
| android:src="@android:drawable/stat_sys_download" /> | ||
|
|
||
| <TextView | ||
| android:id="@+id/progress_text" | ||
| style="@style/NotificationText" | ||
| android:layout_width="fill_parent" | ||
| android:layout_height="wrap_content" | ||
| android:layout_alignParentLeft="true" | ||
| android:layout_alignParentBottom="true" | ||
| android:layout_gravity="center_horizontal" | ||
| android:singleLine="true" | ||
| android:gravity="center" /> | ||
| </RelativeLayout> | ||
|
|
||
| <RelativeLayout | ||
| android:layout_width="0dip" | ||
| android:layout_height="match_parent" | ||
| android:layout_weight="1.0" | ||
| android:clickable="true" | ||
| android:focusable="true" | ||
| android:paddingTop="10dp" | ||
| android:paddingRight="8dp" | ||
| android:paddingBottom="8dp" > | ||
|
|
||
| <TextView | ||
| android:id="@+id/title" | ||
| style="@style/NotificationTitle" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_alignParentLeft="true" | ||
| android:singleLine="true"/> | ||
|
|
||
| <TextView | ||
| android:id="@+id/time_remaining" | ||
| style="@style/NotificationText" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_alignParentRight="true" | ||
| android:singleLine="true"/> | ||
| <!-- Only one of progress_bar and paused_text will be visible. --> | ||
|
|
||
| <FrameLayout | ||
| android:id="@+id/progress_bar_frame" | ||
| android:layout_width="fill_parent" | ||
| android:layout_height="wrap_content" | ||
| android:layout_alignParentBottom="true" > | ||
|
|
||
| <ProgressBar | ||
| android:id="@+id/progress_bar" | ||
| style="?android:attr/progressBarStyleHorizontal" | ||
| android:layout_width="fill_parent" | ||
| android:layout_height="wrap_content" | ||
| android:paddingRight="25dp" /> | ||
|
|
||
| <TextView | ||
| android:id="@+id/description" | ||
| style="@style/NotificationTextShadow" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_gravity="center" | ||
| android:paddingRight="25dp" | ||
| android:singleLine="true" /> | ||
| </FrameLayout> | ||
|
|
||
| </RelativeLayout> | ||
|
|
||
| </LinearLayout> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
| <style name="NotificationTextSecondary" parent="NotificationText"> | ||
| <item name="android:textSize">12sp</item> | ||
| </style> | ||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
| <style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" /> | ||
| <style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" /> | ||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
|
|
||
| <!-- When a download completes, a notification is displayed, and this | ||
| string is used to indicate that the download successfully completed. | ||
| Note that such a download could have been initiated by a variety of | ||
| applications, including (but not limited to) the browser, an email | ||
| application, a content marketplace. --> | ||
| <string name="notification_download_complete">Download complete</string> | ||
|
|
||
| <!-- When a download completes, a notification is displayed, and this | ||
| string is used to indicate that the download failed. | ||
| Note that such a download could have been initiated by a variety of | ||
| applications, including (but not limited to) the browser, an email | ||
| application, a content marketplace. --> | ||
| <string name="notification_download_failed">Download unsuccessful</string> | ||
|
|
||
|
|
||
| <string name="state_unknown">Starting..."</string> | ||
| <string name="state_idle">Waiting for download to start</string> | ||
| <string name="state_fetching_url">Looking for resources to download</string> | ||
| <string name="state_connecting">Connecting to the download server</string> | ||
| <string name="state_downloading">Downloading resources</string> | ||
| <string name="state_completed">Download finished</string> | ||
| <string name="state_paused_network_unavailable">Download paused because no network is available</string> | ||
| <string name="state_paused_network_setup_failure">Download paused. Test a website in browser</string> | ||
| <string name="state_paused_by_request">Download paused</string> | ||
| <string name="state_paused_wifi_unavailable">Download paused because wifi is unavailable</string> | ||
| <string name="state_paused_wifi_disabled">Download paused because wifi is disabled</string> | ||
| <string name="state_paused_roaming">Download paused because you are roaming</string> | ||
| <string name="state_paused_sdcard_unavailable">Download paused because the external storage is unavailable</string> | ||
| <string name="state_failed_unlicensed">Download failed because you may not have purchased this app</string> | ||
| <string name="state_failed_fetching_url">Download failed because the resources could not be found</string> | ||
| <string name="state_failed_sdcard_full">Download failed because the external storage is full</string> | ||
| <string name="state_failed_cancelled">Download cancelled</string> | ||
| <string name="state_failed">Download failed</string> | ||
|
|
||
| <string name="kilobytes_per_second">%1$s KB/s</string> | ||
| <string name="time_remaining">Time remaining: %1$s</string> | ||
| <string name="time_remaining_notification">%1$s left</string> | ||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
|
|
||
| <style name="NotificationText"> | ||
| <item name="android:textColor">?android:attr/textColorPrimary</item> | ||
| </style> | ||
|
|
||
| <style name="NotificationTextShadow" parent="NotificationText"> | ||
| <item name="android:textColor">?android:attr/textColorPrimary</item> | ||
| <item name="android:shadowColor">@android:color/background_dark</item> | ||
| <item name="android:shadowDx">1.0</item> | ||
| <item name="android:shadowDy">1.0</item> | ||
| <item name="android:shadowRadius">1</item> | ||
| </style> | ||
|
|
||
| <style name="NotificationTitle"> | ||
| <item name="android:textColor">?android:attr/textColorPrimary</item> | ||
| <item name="android:textStyle">bold</item> | ||
| </style> | ||
|
|
||
| <style name="ButtonBackground"> | ||
| <item name="android:background">@android:color/background_dark</item> | ||
| </style> | ||
|
|
||
| </resources> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| Apache License | ||
| Version 2.0, January 2004 | ||
| http://www.apache.org/licenses/ | ||
|
|
||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
|
|
||
| 1. Definitions. | ||
|
|
||
| "License" shall mean the terms and conditions for use, reproduction, | ||
| and distribution as defined by Sections 1 through 9 of this document. | ||
|
|
||
| "Licensor" shall mean the copyright owner or entity authorized by | ||
| the copyright owner that is granting the License. | ||
|
|
||
| "Legal Entity" shall mean the union of the acting entity and all | ||
| other entities that control, are controlled by, or are under common | ||
| control with that entity. For the purposes of this definition, | ||
| "control" means (i) the power, direct or indirect, to cause the | ||
| direction or management of such entity, whether by contract or | ||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
| outstanding shares, or (iii) beneficial ownership of such entity. | ||
|
|
||
| "You" (or "Your") shall mean an individual or Legal Entity | ||
| exercising permissions granted by this License. | ||
|
|
||
| "Source" form shall mean the preferred form for making modifications, | ||
| including but not limited to software source code, documentation | ||
| source, and configuration files. | ||
|
|
||
| "Object" form shall mean any form resulting from mechanical | ||
| transformation or translation of a Source form, including but | ||
| not limited to compiled object code, generated documentation, | ||
| and conversions to other media types. | ||
|
|
||
| "Work" shall mean the work of authorship, whether in Source or | ||
| Object form, made available under the License, as indicated by a | ||
| copyright notice that is included in or attached to the work | ||
| (an example is provided in the Appendix below). | ||
|
|
||
| "Derivative Works" shall mean any work, whether in Source or Object | ||
| form, that is based on (or derived from) the Work and for which the | ||
| editorial revisions, annotations, elaborations, or other modifications | ||
| represent, as a whole, an original work of authorship. For the purposes | ||
| of this License, Derivative Works shall not include works that remain | ||
| separable from, or merely link (or bind by name) to the interfaces of, | ||
| the Work and Derivative Works thereof. | ||
|
|
||
| "Contribution" shall mean any work of authorship, including | ||
| the original version of the Work and any modifications or additions | ||
| to that Work or Derivative Works thereof, that is intentionally | ||
| submitted to Licensor for inclusion in the Work by the copyright owner | ||
| or by an individual or Legal Entity authorized to submit on behalf of | ||
| the copyright owner. For the purposes of this definition, "submitted" | ||
| means any form of electronic, verbal, or written communication sent | ||
| to the Licensor or its representatives, including but not limited to | ||
| communication on electronic mailing lists, source code control systems, | ||
| and issue tracking systems that are managed by, or on behalf of, the | ||
| Licensor for the purpose of discussing and improving the Work, but | ||
| excluding communication that is conspicuously marked or otherwise | ||
| designated in writing by the copyright owner as "Not a Contribution." | ||
|
|
||
| "Contributor" shall mean Licensor and any individual or Legal Entity | ||
| on behalf of whom a Contribution has been received by Licensor and | ||
| subsequently incorporated within the Work. | ||
|
|
||
| 2. Grant of Copyright License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| copyright license to reproduce, prepare Derivative Works of, | ||
| publicly display, publicly perform, sublicense, and distribute the | ||
| Work and such Derivative Works in Source or Object form. | ||
|
|
||
| 3. Grant of Patent License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| (except as stated in this section) patent license to make, have made, | ||
| use, offer to sell, sell, import, and otherwise transfer the Work, | ||
| where such license applies only to those patent claims licensable | ||
| by such Contributor that are necessarily infringed by their | ||
| Contribution(s) alone or by combination of their Contribution(s) | ||
| with the Work to which such Contribution(s) was submitted. If You | ||
| institute patent litigation against any entity (including a | ||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
| or a Contribution incorporated within the Work constitutes direct | ||
| or contributory patent infringement, then any patent licenses | ||
| granted to You under this License for that Work shall terminate | ||
| as of the date such litigation is filed. | ||
|
|
||
| 4. Redistribution. You may reproduce and distribute copies of the | ||
| Work or Derivative Works thereof in any medium, with or without | ||
| modifications, and in Source or Object form, provided that You | ||
| meet the following conditions: | ||
|
|
||
| (a) You must give any other recipients of the Work or | ||
| Derivative Works a copy of this License; and | ||
|
|
||
| (b) You must cause any modified files to carry prominent notices | ||
| stating that You changed the files; and | ||
|
|
||
| (c) You must retain, in the Source form of any Derivative Works | ||
| that You distribute, all copyright, patent, trademark, and | ||
| attribution notices from the Source form of the Work, | ||
| excluding those notices that do not pertain to any part of | ||
| the Derivative Works; and | ||
|
|
||
| (d) If the Work includes a "NOTICE" text file as part of its | ||
| distribution, then any Derivative Works that You distribute must | ||
| include a readable copy of the attribution notices contained | ||
| within such NOTICE file, excluding those notices that do not | ||
| pertain to any part of the Derivative Works, in at least one | ||
| of the following places: within a NOTICE text file distributed | ||
| as part of the Derivative Works; within the Source form or | ||
| documentation, if provided along with the Derivative Works; or, | ||
| within a display generated by the Derivative Works, if and | ||
| wherever such third-party notices normally appear. The contents | ||
| of the NOTICE file are for informational purposes only and | ||
| do not modify the License. You may add Your own attribution | ||
| notices within Derivative Works that You distribute, alongside | ||
| or as an addendum to the NOTICE text from the Work, provided | ||
| that such additional attribution notices cannot be construed | ||
| as modifying the License. | ||
|
|
||
| You may add Your own copyright statement to Your modifications and | ||
| may provide additional or different license terms and conditions | ||
| for use, reproduction, or distribution of Your modifications, or | ||
| for any such Derivative Works as a whole, provided Your use, | ||
| reproduction, and distribution of the Work otherwise complies with | ||
| the conditions stated in this License. | ||
|
|
||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | ||
| any Contribution intentionally submitted for inclusion in the Work | ||
| by You to the Licensor shall be under the terms and conditions of | ||
| this License, without any additional terms or conditions. | ||
| Notwithstanding the above, nothing herein shall supersede or modify | ||
| the terms of any separate license agreement you may have executed | ||
| with Licensor regarding such Contributions. | ||
|
|
||
| 6. Trademarks. This License does not grant permission to use the trade | ||
| names, trademarks, service marks, or product names of the Licensor, | ||
| except as required for reasonable and customary use in describing the | ||
| origin of the Work and reproducing the content of the NOTICE file. | ||
|
|
||
| 7. Disclaimer of Warranty. Unless required by applicable law or | ||
| agreed to in writing, Licensor provides the Work (and each | ||
| Contributor provides its Contributions) on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
| implied, including, without limitation, any warranties or conditions | ||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
| PARTICULAR PURPOSE. You are solely responsible for determining the | ||
| appropriateness of using or redistributing the Work and assume any | ||
| risks associated with Your exercise of permissions under this License. | ||
|
|
||
| 8. Limitation of Liability. In no event and under no legal theory, | ||
| whether in tort (including negligence), contract, or otherwise, | ||
| unless required by applicable law (such as deliberate and grossly | ||
| negligent acts) or agreed to in writing, shall any Contributor be | ||
| liable to You for damages, including any direct, indirect, special, | ||
| incidental, or consequential damages of any character arising as a | ||
| result of this License or out of the use or inability to use the | ||
| Work (including but not limited to damages for loss of goodwill, | ||
| work stoppage, computer failure or malfunction, or any and all | ||
| other commercial damages or losses), even if such Contributor | ||
| has been advised of the possibility of such damages. | ||
|
|
||
| 9. Accepting Warranty or Additional Liability. While redistributing | ||
| the Work or Derivative Works thereof, You may choose to offer, | ||
| and charge a fee for, acceptance of support, warranty, indemnity, | ||
| or other liability obligations and/or rights consistent with this | ||
| License. However, in accepting such obligations, You may act only | ||
| on Your own behalf and on Your sole responsibility, not on behalf | ||
| of any other Contributor, and only if You agree to indemnify, | ||
| defend, and hold each Contributor harmless for any liability | ||
| incurred by, or claims asserted against, such Contributor by reason | ||
| of your accepting any such warranty or additional liability. | ||
|
|
||
| END OF TERMS AND CONDITIONS | ||
|
|
||
| APPENDIX: How to apply the Apache License to your work. | ||
|
|
||
| To apply the Apache License to your work, attach the following | ||
| boilerplate notice, with the fields enclosed by brackets "[]" | ||
| replaced with your own identifying information. (Don't include | ||
| the brackets!) The text should be enclosed in the appropriate | ||
| comment syntax for the file format. We also recommend that a | ||
| file or class name and description of purpose be included on the | ||
| same "printed page" as the copyright notice for easier | ||
| identification within third-party archives. | ||
|
|
||
| Copyright [yyyy] [name of copyright owner] | ||
|
|
||
| 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| apply plugin: 'com.android.library' | ||
|
|
||
| android { | ||
| compileSdkVersion 29 | ||
|
|
||
| useLibrary 'org.apache.http.legacy' | ||
|
|
||
| defaultConfig { | ||
| minSdkVersion 19 | ||
| targetSdkVersion 29 | ||
| versionCode 2 | ||
| versionName '1.7.0' | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| compile 'org.slf4j:slf4j-api:1.7.7' | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <!-- 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. | ||
| --> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| package="com.google.android.vending.licensing" | ||
| android:versionCode="2" | ||
| android:versionName="1.5"> | ||
| <!-- Devices >= 3 have version of Android Market that supports licensing. --> | ||
| <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="15" /> | ||
| <!-- Required permission to check licensing. --> | ||
| <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> | ||
| </manifest> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /* | ||
| * 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.android.vending.licensing; | ||
|
|
||
| // Android library projects do not yet support AIDL, so this has been | ||
| // precompiled into the src directory. | ||
| oneway interface ILicenseResultListener { | ||
| void verifyLicense(int responseCode, String signedData, String signature); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /* | ||
| * 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.android.vending.licensing; | ||
|
|
||
|
|
||
| import com.android.vending.licensing.ILicenseResultListener; | ||
|
|
||
| // Android library projects do not yet support AIDL, so this has been | ||
| // precompiled into the src directory. | ||
| oneway interface ILicensingService { | ||
| void checkLicense(long nonce, String packageName, in ILicenseResultListener listener); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| /* | ||
| * 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.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); | ||
| } | ||
| } | ||
| } |