The IPC is implemented using an Android Messenger and a service Binder. The connect method 43 | * should be called whenever the client wants to bind to the service. It opens up a service connection 44 | * that ends up calling the onServiceConnected client API that passes the service messenger 45 | * in. If the client wants to be notified by the service, it is responsible for then passing its 46 | * messenger to the service in a separate call. 47 | * 48 | *
Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}. 49 | * 50 | *
When your application first starts, you should first check whether your app's expansion files are 51 | * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which 52 | * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method 53 | * returns a value indicating whether download is required or not. 54 | * 55 | *
If a download is required, {@link #startDownloadServiceIfRequired} begins the download through 56 | * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link 57 | * IStub} object that you need in order to receive calls through your {@link IDownloaderClient} 58 | * interface. 59 | */ 60 | public class DownloaderClientMarshaller { 61 | public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10; 62 | public static final int MSG_ONDOWNLOADPROGRESS = 11; 63 | public static final int MSG_ONSERVICECONNECTED = 12; 64 | 65 | public static final String PARAM_NEW_STATE = "newState"; 66 | public static final String PARAM_PROGRESS = "progress"; 67 | public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; 68 | 69 | public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED; 70 | public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED; 71 | public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED; 72 | 73 | private static class Proxy implements IDownloaderClient { 74 | private Messenger mServiceMessenger; 75 | 76 | @Override 77 | public void onDownloadStateChanged(int newState) { 78 | Bundle params = new Bundle(1); 79 | params.putInt(PARAM_NEW_STATE, newState); 80 | send(MSG_ONDOWNLOADSTATE_CHANGED, params); 81 | } 82 | 83 | @Override 84 | public void onDownloadProgress(DownloadProgressInfo progress) { 85 | Bundle params = new Bundle(1); 86 | params.putParcelable(PARAM_PROGRESS, progress); 87 | send(MSG_ONDOWNLOADPROGRESS, params); 88 | } 89 | 90 | private void send(int method, Bundle params) { 91 | Message m = Message.obtain(null, method); 92 | m.setData(params); 93 | try { 94 | mServiceMessenger.send(m); 95 | } catch (RemoteException e) { 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | public Proxy(Messenger msg) { 101 | mServiceMessenger = msg; 102 | } 103 | 104 | @Override 105 | public void onServiceConnected(Messenger m) { 106 | /** 107 | * This is never called through the proxy. 108 | */ 109 | } 110 | } 111 | 112 | private static class Stub implements IStub { 113 | private IDownloaderClient mItf = null; 114 | private Class> mDownloaderServiceClass; 115 | private boolean mBound; 116 | private Messenger mServiceMessenger; 117 | private Context mContext; 118 | /** 119 | * Target we publish for clients to send messages to IncomingHandler. 120 | */ 121 | final Messenger mMessenger = new Messenger(new Handler() { 122 | @Override 123 | public void handleMessage(Message msg) { 124 | switch (msg.what) { 125 | case MSG_ONDOWNLOADPROGRESS: 126 | Bundle bun = msg.getData(); 127 | if ( null != mContext ) { 128 | bun.setClassLoader(mContext.getClassLoader()); 129 | DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData() 130 | .getParcelable(PARAM_PROGRESS); 131 | mItf.onDownloadProgress(dpi); 132 | } 133 | break; 134 | case MSG_ONDOWNLOADSTATE_CHANGED: 135 | mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE)); 136 | break; 137 | case MSG_ONSERVICECONNECTED: 138 | mItf.onServiceConnected( 139 | (Messenger) msg.getData().getParcelable(PARAM_MESSENGER)); 140 | break; 141 | } 142 | } 143 | }); 144 | 145 | public Stub(IDownloaderClient itf, Class> downloaderService) { 146 | mItf = itf; 147 | mDownloaderServiceClass = downloaderService; 148 | } 149 | 150 | /** 151 | * Class for interacting with the main interface of the service. 152 | */ 153 | private ServiceConnection mConnection = new ServiceConnection() { 154 | public void onServiceConnected(ComponentName className, IBinder service) { 155 | // This is called when the connection with the service has been 156 | // established, giving us the object we can use to 157 | // interact with the service. We are communicating with the 158 | // service using a Messenger, so here we get a client-side 159 | // representation of that from the raw IBinder object. 160 | mServiceMessenger = new Messenger(service); 161 | mItf.onServiceConnected( 162 | mServiceMessenger); 163 | } 164 | 165 | public void onServiceDisconnected(ComponentName className) { 166 | // This is called when the connection with the service has been 167 | // unexpectedly disconnected -- that is, its process crashed. 168 | mServiceMessenger = null; 169 | } 170 | }; 171 | 172 | @Override 173 | public void connect(Context c) { 174 | mContext = c; 175 | Intent bindIntent = new Intent(c, mDownloaderServiceClass); 176 | bindIntent.putExtra(PARAM_MESSENGER, mMessenger); 177 | if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) { 178 | if ( Constants.LOGVV ) { 179 | Log.d(Constants.TAG, "Service Unbound"); 180 | } 181 | } else { 182 | mBound = true; 183 | } 184 | 185 | } 186 | 187 | @Override 188 | public void disconnect(Context c) { 189 | if (mBound) { 190 | c.unbindService(mConnection); 191 | mBound = false; 192 | } 193 | mContext = null; 194 | } 195 | 196 | @Override 197 | public Messenger getMessenger() { 198 | return mMessenger; 199 | } 200 | } 201 | 202 | /** 203 | * Returns a proxy that will marshal calls to IDownloaderClient methods 204 | * 205 | * @param msg 206 | * @return 207 | */ 208 | public static IDownloaderClient CreateProxy(Messenger msg) { 209 | return new Proxy(msg); 210 | } 211 | 212 | /** 213 | * Returns a stub object that, when connected, will listen for marshaled 214 | * {@link IDownloaderClient} methods and translate them into calls to the supplied 215 | * interface. 216 | * 217 | * @param itf An implementation of IDownloaderClient that will be called 218 | * when remote method calls are unmarshaled. 219 | * @param downloaderService The class for your implementation of {@link 220 | * impl.DownloaderService}. 221 | * @return The {@link IStub} that allows you to connect to the service such that 222 | * your {@link IDownloaderClient} receives status updates. 223 | */ 224 | public static IStub CreateStub(IDownloaderClient itf, Class> downloaderService) { 225 | return new Stub(itf, downloaderService); 226 | } 227 | 228 | /** 229 | * Starts the download if necessary. This function starts a flow that does ` 230 | * many things. 1) Checks to see if the APK version has been checked and 231 | * the metadata database updated 2) If the APK version does not match, 232 | * checks the new LVL status to see if a new download is required 3) If the 233 | * APK version does match, then checks to see if the download(s) have been 234 | * completed 4) If the downloads have been completed, returns 235 | * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the 236 | * startup of an application to quickly ascertain if the application needs 237 | * to wait to hear about any updated APK expansion files. Note that this does 238 | * mean that the application MUST be run for the first time with a network 239 | * connection, even if Market delivers all of the files. 240 | * 241 | * @param context Your application Context. 242 | * @param notificationClient A PendingIntent to start the Activity in your application 243 | * that shows the download progress and which will also start the application when download 244 | * completes. 245 | * @param serviceClass the class of your {@link imp.DownloaderService} implementation 246 | * @return whether the service was started and the reason for starting the service. 247 | * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link 248 | * #DOWNLOAD_REQUIRED}. 249 | * @throws NameNotFoundException 250 | */ 251 | public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, 252 | Class> serviceClass) 253 | throws NameNotFoundException { 254 | return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, 255 | serviceClass); 256 | } 257 | 258 | /** 259 | * This version assumes that the intent contains the pending intent as a parameter. This 260 | * is used for responding to alarms. 261 | *
The pending intent must be in an extra with the key {@link 262 | * impl.DownloaderService#EXTRA_PENDING_INTENT}. 263 | * 264 | * @param context 265 | * @param notificationClient 266 | * @param serviceClass the class of the service to start 267 | * @return 268 | * @throws NameNotFoundException 269 | */ 270 | public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, 271 | Class> serviceClass) 272 | throws NameNotFoundException { 273 | return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, 274 | serviceClass); 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService; 20 | 21 | import android.content.Context; 22 | import android.os.Bundle; 23 | import android.os.Handler; 24 | import android.os.Message; 25 | import android.os.Messenger; 26 | import android.os.RemoteException; 27 | 28 | 29 | 30 | /** 31 | * This class is used by the client activity to proxy requests to the Downloader 32 | * Service. 33 | * 34 | * Most importantly, you must call {@link #CreateProxy} during the {@link 35 | * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate 36 | * an {@link IDownloaderService} object that you can then use to issue commands to the {@link 37 | * DownloaderService} (such as to pause and resume downloads). 38 | */ 39 | public class DownloaderServiceMarshaller { 40 | 41 | public static final int MSG_REQUEST_ABORT_DOWNLOAD = 42 | 1; 43 | public static final int MSG_REQUEST_PAUSE_DOWNLOAD = 44 | 2; 45 | public static final int MSG_SET_DOWNLOAD_FLAGS = 46 | 3; 47 | public static final int MSG_REQUEST_CONTINUE_DOWNLOAD = 48 | 4; 49 | public static final int MSG_REQUEST_DOWNLOAD_STATE = 50 | 5; 51 | public static final int MSG_REQUEST_CLIENT_UPDATE = 52 | 6; 53 | 54 | public static final String PARAMS_FLAGS = "flags"; 55 | public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; 56 | 57 | private static class Proxy implements IDownloaderService { 58 | private Messenger mMsg; 59 | 60 | private void send(int method, Bundle params) { 61 | Message m = Message.obtain(null, method); 62 | m.setData(params); 63 | try { 64 | mMsg.send(m); 65 | } catch (RemoteException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | 70 | public Proxy(Messenger msg) { 71 | mMsg = msg; 72 | } 73 | 74 | @Override 75 | public void requestAbortDownload() { 76 | send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle()); 77 | } 78 | 79 | @Override 80 | public void requestPauseDownload() { 81 | send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle()); 82 | } 83 | 84 | @Override 85 | public void setDownloadFlags(int flags) { 86 | Bundle params = new Bundle(); 87 | params.putInt(PARAMS_FLAGS, flags); 88 | send(MSG_SET_DOWNLOAD_FLAGS, params); 89 | } 90 | 91 | @Override 92 | public void requestContinueDownload() { 93 | send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle()); 94 | } 95 | 96 | @Override 97 | public void requestDownloadStatus() { 98 | send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle()); 99 | } 100 | 101 | @Override 102 | public void onClientUpdated(Messenger clientMessenger) { 103 | Bundle bundle = new Bundle(1); 104 | bundle.putParcelable(PARAM_MESSENGER, clientMessenger); 105 | send(MSG_REQUEST_CLIENT_UPDATE, bundle); 106 | } 107 | } 108 | 109 | private static class Stub implements IStub { 110 | private IDownloaderService mItf = null; 111 | final Messenger mMessenger = new Messenger(new Handler() { 112 | @Override 113 | public void handleMessage(Message msg) { 114 | switch (msg.what) { 115 | case MSG_REQUEST_ABORT_DOWNLOAD: 116 | mItf.requestAbortDownload(); 117 | break; 118 | case MSG_REQUEST_CONTINUE_DOWNLOAD: 119 | mItf.requestContinueDownload(); 120 | break; 121 | case MSG_REQUEST_PAUSE_DOWNLOAD: 122 | mItf.requestPauseDownload(); 123 | break; 124 | case MSG_SET_DOWNLOAD_FLAGS: 125 | mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS)); 126 | break; 127 | case MSG_REQUEST_DOWNLOAD_STATE: 128 | mItf.requestDownloadStatus(); 129 | break; 130 | case MSG_REQUEST_CLIENT_UPDATE: 131 | mItf.onClientUpdated((Messenger) msg.getData().getParcelable( 132 | PARAM_MESSENGER)); 133 | break; 134 | } 135 | } 136 | }); 137 | 138 | public Stub(IDownloaderService itf) { 139 | mItf = itf; 140 | } 141 | 142 | @Override 143 | public Messenger getMessenger() { 144 | return mMessenger; 145 | } 146 | 147 | @Override 148 | public void connect(Context c) { 149 | 150 | } 151 | 152 | @Override 153 | public void disconnect(Context c) { 154 | 155 | } 156 | } 157 | 158 | /** 159 | * Returns a proxy that will marshall calls to IDownloaderService methods 160 | * 161 | * @param ctx 162 | * @return 163 | */ 164 | public static IDownloaderService CreateProxy(Messenger msg) { 165 | return new Proxy(msg); 166 | } 167 | 168 | /** 169 | * Returns a stub object that, when connected, will listen for marshalled 170 | * IDownloaderService methods and translate them into calls to the supplied 171 | * interface. 172 | * 173 | * @param itf An implementation of IDownloaderService that will be called 174 | * when remote method calls are unmarshalled. 175 | * @return 176 | */ 177 | public static IStub CreateStub(IDownloaderService itf) { 178 | return new Stub(itf); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/Helpers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import com.android.vending.expansion.downloader.R; 20 | 21 | import android.content.Context; 22 | import android.os.Environment; 23 | import android.os.StatFs; 24 | import android.os.SystemClock; 25 | import android.util.Log; 26 | 27 | import java.io.File; 28 | import java.text.SimpleDateFormat; 29 | import java.util.Date; 30 | import java.util.Locale; 31 | import java.util.Random; 32 | import java.util.TimeZone; 33 | import java.util.regex.Matcher; 34 | import java.util.regex.Pattern; 35 | 36 | /** 37 | * Some helper functions for the download manager 38 | */ 39 | public class Helpers { 40 | 41 | public static Random sRandom = new Random(SystemClock.uptimeMillis()); 42 | 43 | /** Regex used to parse content-disposition headers */ 44 | private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern 45 | .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); 46 | 47 | private Helpers() { 48 | } 49 | 50 | /* 51 | * Parse the Content-Disposition HTTP Header. The format of the header is 52 | * defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This 53 | * header provides a filename for content that is going to be downloaded to 54 | * the file system. We only support the attachment type. 55 | */ 56 | static String parseContentDisposition(String contentDisposition) { 57 | try { 58 | Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); 59 | if (m.find()) { 60 | return m.group(1); 61 | } 62 | } catch (IllegalStateException ex) { 63 | // This function is defined as returning null when it can't parse 64 | // the header 65 | } 66 | return null; 67 | } 68 | 69 | /** 70 | * @return the root of the filesystem containing the given path 71 | */ 72 | public static File getFilesystemRoot(String path) { 73 | File cache = Environment.getDownloadCacheDirectory(); 74 | if (path.startsWith(cache.getPath())) { 75 | return cache; 76 | } 77 | File external = Environment.getExternalStorageDirectory(); 78 | if (path.startsWith(external.getPath())) { 79 | return external; 80 | } 81 | throw new IllegalArgumentException( 82 | "Cannot determine filesystem root for " + path); 83 | } 84 | 85 | public static boolean isExternalMediaMounted() { 86 | if (!Environment.getExternalStorageState().equals( 87 | Environment.MEDIA_MOUNTED)) { 88 | // No SD card found. 89 | if ( Constants.LOGVV ) { 90 | Log.d(Constants.TAG, "no external storage"); 91 | } 92 | return false; 93 | } 94 | return true; 95 | } 96 | 97 | /** 98 | * @return the number of bytes available on the filesystem rooted at the 99 | * given File 100 | */ 101 | public static long getAvailableBytes(File root) { 102 | StatFs stat = new StatFs(root.getPath()); 103 | // put a bit of margin (in case creating the file grows the system by a 104 | // few blocks) 105 | long availableBlocks = (long) stat.getAvailableBlocks() - 4; 106 | return stat.getBlockSize() * availableBlocks; 107 | } 108 | 109 | /** 110 | * Checks whether the filename looks legitimate 111 | */ 112 | public static boolean isFilenameValid(String filename) { 113 | filename = filename.replaceFirst("/+", "/"); // normalize leading 114 | // slashes 115 | return filename.startsWith(Environment.getDownloadCacheDirectory().toString()) 116 | || filename.startsWith(Environment.getExternalStorageDirectory().toString()); 117 | } 118 | 119 | /* 120 | * Delete the given file from device 121 | */ 122 | /* package */static void deleteFile(String path) { 123 | try { 124 | File file = new File(path); 125 | file.delete(); 126 | } catch (Exception e) { 127 | Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e); 128 | } 129 | } 130 | 131 | /** 132 | * Showing progress in MB here. It would be nice to choose the unit (KB, MB, 133 | * GB) based on total file size, but given what we know about the expected 134 | * ranges of file sizes for APK expansion files, it's probably not necessary. 135 | * 136 | * @param overallProgress 137 | * @param overallTotal 138 | * @return 139 | */ 140 | 141 | static public String getDownloadProgressString(long overallProgress, long overallTotal) { 142 | if (overallTotal == 0) { 143 | if ( Constants.LOGVV ) { 144 | Log.e(Constants.TAG, "Notification called when total is zero"); 145 | } 146 | return ""; 147 | } 148 | return String.format("%.2f", 149 | (float) overallProgress / (1024.0f * 1024.0f)) 150 | + "MB /" + 151 | String.format("%.2f", (float) overallTotal / 152 | (1024.0f * 1024.0f)) + "MB"; 153 | } 154 | 155 | /** 156 | * Adds a percentile to getDownloadProgressString. 157 | * 158 | * @param overallProgress 159 | * @param overallTotal 160 | * @return 161 | */ 162 | static public String getDownloadProgressStringNotification(long overallProgress, 163 | long overallTotal) { 164 | if (overallTotal == 0) { 165 | if ( Constants.LOGVV ) { 166 | Log.e(Constants.TAG, "Notification called when total is zero"); 167 | } 168 | return ""; 169 | } 170 | return getDownloadProgressString(overallProgress, overallTotal) + " (" + 171 | getDownloadProgressPercent(overallProgress, overallTotal) + ")"; 172 | } 173 | 174 | public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { 175 | if (overallTotal == 0) { 176 | if ( Constants.LOGVV ) { 177 | Log.e(Constants.TAG, "Notification called when total is zero"); 178 | } 179 | return ""; 180 | } 181 | return Long.toString(overallProgress * 100 / overallTotal) + "%"; 182 | } 183 | 184 | public static String getSpeedString(float bytesPerMillisecond) { 185 | return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); 186 | } 187 | 188 | public static String getTimeRemaining(long durationInMilliseconds) { 189 | SimpleDateFormat sdf; 190 | if (durationInMilliseconds > 1000 * 60 * 60) { 191 | sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); 192 | } else { 193 | sdf = new SimpleDateFormat("mm:ss", Locale.getDefault()); 194 | } 195 | return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset())); 196 | } 197 | 198 | /** 199 | * Returns the file name (without full path) for an Expansion APK file from 200 | * the given context. 201 | * 202 | * @param c the context 203 | * @param mainFile true for main file, false for patch file 204 | * @param versionCode the version of the file 205 | * @return String the file name of the expansion file 206 | */ 207 | public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) { 208 | return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb"; 209 | } 210 | 211 | /** 212 | * Returns the filename (where the file should be saved) from info about a 213 | * download 214 | */ 215 | static public String generateSaveFileName(Context c, String fileName) { 216 | String path = getSaveFilePath(c) 217 | + File.separator + fileName; 218 | return path; 219 | } 220 | 221 | static public String getSaveFilePath(Context c) { 222 | File root = Environment.getExternalStorageDirectory(); 223 | String path = root.toString() + Constants.EXP_PATH + c.getPackageName(); 224 | return path; 225 | } 226 | 227 | /** 228 | * Helper function to ascertain the existence of a file and return 229 | * true/false appropriately 230 | * 231 | * @param c the app/activity/service context 232 | * @param fileName the name (sans path) of the file to query 233 | * @param fileSize the size that the file must match 234 | * @param deleteFileOnMismatch if the file sizes do not match, delete the 235 | * file 236 | * @return true if it does exist, false otherwise 237 | */ 238 | static public boolean doesFileExist(Context c, String fileName, long fileSize, 239 | boolean deleteFileOnMismatch) { 240 | // the file may have been delivered by Market --- let's make sure 241 | // it's the size we expect 242 | File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); 243 | if (fileForNewFile.exists()) { 244 | if (fileForNewFile.length() == fileSize) { 245 | return true; 246 | } 247 | if (deleteFileOnMismatch) { 248 | // delete the file --- we won't be able to resume 249 | // because we cannot confirm the integrity of the file 250 | fileForNewFile.delete(); 251 | } 252 | } 253 | return false; 254 | } 255 | 256 | /** 257 | * Converts download states that are returned by the {@link 258 | * IDownloaderClient#onDownloadStateChanged} callback into usable strings. 259 | * This is useful if using the state strings built into the library to display user messages. 260 | * @param state One of the STATE_* constants from {@link IDownloaderClient}. 261 | * @return string resource ID for the corresponding string. 262 | */ 263 | static public int getDownloaderStringResourceIDFromState(int state) { 264 | switch (state) { 265 | case IDownloaderClient.STATE_IDLE: 266 | return R.string.state_idle; 267 | case IDownloaderClient.STATE_FETCHING_URL: 268 | return R.string.state_fetching_url; 269 | case IDownloaderClient.STATE_CONNECTING: 270 | return R.string.state_connecting; 271 | case IDownloaderClient.STATE_DOWNLOADING: 272 | return R.string.state_downloading; 273 | case IDownloaderClient.STATE_COMPLETED: 274 | return R.string.state_completed; 275 | case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE: 276 | return R.string.state_paused_network_unavailable; 277 | case IDownloaderClient.STATE_PAUSED_BY_REQUEST: 278 | return R.string.state_paused_by_request; 279 | case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: 280 | return R.string.state_paused_wifi_disabled; 281 | case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: 282 | return R.string.state_paused_wifi_unavailable; 283 | case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED: 284 | return R.string.state_paused_wifi_disabled; 285 | case IDownloaderClient.STATE_PAUSED_NEED_WIFI: 286 | return R.string.state_paused_wifi_unavailable; 287 | case IDownloaderClient.STATE_PAUSED_ROAMING: 288 | return R.string.state_paused_roaming; 289 | case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE: 290 | return R.string.state_paused_network_setup_failure; 291 | case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: 292 | return R.string.state_paused_sdcard_unavailable; 293 | case IDownloaderClient.STATE_FAILED_UNLICENSED: 294 | return R.string.state_failed_unlicensed; 295 | case IDownloaderClient.STATE_FAILED_FETCHING_URL: 296 | return R.string.state_failed_fetching_url; 297 | case IDownloaderClient.STATE_FAILED_SDCARD_FULL: 298 | return R.string.state_failed_sdcard_full; 299 | case IDownloaderClient.STATE_FAILED_CANCELED: 300 | return R.string.state_failed_cancelled; 301 | default: 302 | return R.string.state_unknown; 303 | } 304 | } 305 | 306 | } 307 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.os.Messenger; 20 | 21 | /** 22 | * This interface should be implemented by the client activity for the 23 | * downloader. It is used to pass status from the service to the client. 24 | */ 25 | public interface IDownloaderClient { 26 | static final int STATE_IDLE = 1; 27 | static final int STATE_FETCHING_URL = 2; 28 | static final int STATE_CONNECTING = 3; 29 | static final int STATE_DOWNLOADING = 4; 30 | static final int STATE_COMPLETED = 5; 31 | 32 | static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6; 33 | static final int STATE_PAUSED_BY_REQUEST = 7; 34 | 35 | /** 36 | * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and 37 | * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and 38 | * cellular permission will restart the service. Wi-Fi disabled means that 39 | * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the 40 | * other case Wi-Fi is enabled but not available. 41 | */ 42 | static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8; 43 | static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9; 44 | 45 | /** 46 | * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that 47 | * Wi-Fi is unavailable and cellular permission will NOT restart the 48 | * service. Wi-Fi disabled means that the Wi-Fi manager is returning that 49 | * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not 50 | * available. 51 | *
52 | * The service does not return these values. We recommend that app 53 | * developers with very large payloads do not allow these payloads to be 54 | * downloaded over cellular connections. 55 | */ 56 | static final int STATE_PAUSED_WIFI_DISABLED = 10; 57 | static final int STATE_PAUSED_NEED_WIFI = 11; 58 | 59 | static final int STATE_PAUSED_ROAMING = 12; 60 | 61 | /** 62 | * Scary case. We were on a network that redirected us to another website 63 | * that delivered us the wrong file. 64 | */ 65 | static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13; 66 | 67 | static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; 68 | 69 | static final int STATE_FAILED_UNLICENSED = 15; 70 | static final int STATE_FAILED_FETCHING_URL = 16; 71 | static final int STATE_FAILED_SDCARD_FULL = 17; 72 | static final int STATE_FAILED_CANCELED = 18; 73 | 74 | static final int STATE_FAILED = 19; 75 | 76 | /** 77 | * Called internally by the stub when the service is bound to the client. 78 | *
79 | * Critical implementation detail. In onServiceConnected we create the 80 | * remote service and marshaler. This is how we pass the client information 81 | * back to the service so the client can be properly notified of changes. We 82 | * must do this every time we reconnect to the service. 83 | *
84 | * That is, when you receive this callback, you should call 85 | * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member 86 | * instance of {@link IDownloaderService}, then call 87 | * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved 88 | * from your {@link IStub} proxy object. 89 | * 90 | * @param m the service Messenger. This Messenger is used to call the 91 | * service API from the client. 92 | */ 93 | void onServiceConnected(Messenger m); 94 | 95 | /** 96 | * Called when the download state changes. Depending on the state, there may 97 | * be user requests. The service is free to change the download state in the 98 | * middle of a user request, so the client should be able to handle this. 99 | *
100 | * The Downloader Library includes a collection of string resources that 101 | * correspond to each of the states, which you can use to provide users a 102 | * useful message based on the state provided in this callback. To fetch the 103 | * appropriate string for a state, call 104 | * {@link Helpers#getDownloaderStringResourceIDFromState}. 105 | *
106 | * What this means to the developer: The application has gotten a message 107 | * that the download has paused due to lack of WiFi. The developer should 108 | * then show UI asking the user if they want to enable downloading over 109 | * cellular connections with appropriate warnings. If the application 110 | * suddenly starts downloading, the application should revert to showing the 111 | * progress again, rather than leaving up the download over cellular UI up. 112 | * 113 | * @param newState one of the STATE_* values defined in IDownloaderClient 114 | */ 115 | void onDownloadStateChanged(int newState); 116 | 117 | /** 118 | * Shows the download progress. This is intended to be used to fill out a 119 | * client UI. This progress should only be shown in a few states such as 120 | * STATE_DOWNLOADING. 121 | * 122 | * @param progress the DownloadProgressInfo object containing the current 123 | * progress of all downloads. 124 | */ 125 | void onDownloadProgress(DownloadProgressInfo progress); 126 | } 127 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService; 20 | import android.os.Messenger; 21 | 22 | /** 23 | * This interface is implemented by the DownloaderService and by the 24 | * DownloaderServiceMarshaller. It contains functions to control the service. 25 | * When a client binds to the service, it must call the onClientUpdated 26 | * function. 27 | *
28 | * You can acquire a proxy that implements this interface for your service by 29 | * calling {@link DownloaderServiceMarshaller#CreateProxy} during the 30 | * {@link IDownloaderClient#onServiceConnected} callback. At which point, you 31 | * should immediately call {@link #onClientUpdated}. 32 | */ 33 | public interface IDownloaderService { 34 | /** 35 | * Set this flag in response to the 36 | * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then 37 | * call RequestContinueDownload to resume a download 38 | */ 39 | public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1; 40 | 41 | /** 42 | * Request that the service abort the current download. The service should 43 | * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}. 44 | */ 45 | void requestAbortDownload(); 46 | 47 | /** 48 | * Request that the service pause the current download. The service should 49 | * respond by changing the state to 50 | * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. 51 | */ 52 | void requestPauseDownload(); 53 | 54 | /** 55 | * Request that the service continue a paused download, when in any paused 56 | * or failed state, including 57 | * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. 58 | */ 59 | void requestContinueDownload(); 60 | 61 | /** 62 | * Set the flags for this download (e.g. 63 | * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). 64 | * 65 | * @param flags 66 | */ 67 | void setDownloadFlags(int flags); 68 | 69 | /** 70 | * Requests that the download status be sent to the client. 71 | */ 72 | void requestDownloadStatus(); 73 | 74 | /** 75 | * Call this when you get {@link 76 | * IDownloaderClient.onServiceConnected(Messenger m)} from the 77 | * DownloaderClient to register the client with the service. It will 78 | * automatically send the current status to the client. 79 | * 80 | * @param clientMessenger 81 | */ 82 | void onClientUpdated(Messenger clientMessenger); 83 | } 84 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/IStub.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.content.Context; 20 | import android.os.Messenger; 21 | 22 | /** 23 | * This is the interface that is used to connect/disconnect from the downloader 24 | * service. 25 | *
26 | * You should get a proxy object that implements this interface by calling 27 | * {@link DownloaderClientMarshaller#CreateStub} in your activity when the 28 | * downloader service starts. Then, call {@link #connect} during your activity's 29 | * onResume() and call {@link #disconnect} during onStop(). 30 | *
31 | * Then during the {@link IDownloaderClient#onServiceConnected} callback, you 32 | * should call {@link #getMessenger} to pass the stub's Messenger object to 33 | * {@link IDownloaderService#onClientUpdated}. 34 | */ 35 | public interface IStub { 36 | Messenger getMessenger(); 37 | 38 | void connect(Context c); 39 | 40 | void disconnect(Context c); 41 | } 42 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/SystemFacade.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.app.Notification; 20 | import android.app.NotificationManager; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.pm.PackageManager.NameNotFoundException; 24 | import android.net.ConnectivityManager; 25 | import android.net.NetworkInfo; 26 | import android.telephony.TelephonyManager; 27 | import android.util.Log; 28 | 29 | /** 30 | * Contains useful helper functions, typically tied to the application context. 31 | */ 32 | class SystemFacade { 33 | private Context mContext; 34 | private NotificationManager mNotificationManager; 35 | 36 | public SystemFacade(Context context) { 37 | mContext = context; 38 | mNotificationManager = (NotificationManager) 39 | mContext.getSystemService(Context.NOTIFICATION_SERVICE); 40 | } 41 | 42 | public long currentTimeMillis() { 43 | return System.currentTimeMillis(); 44 | } 45 | 46 | public Integer getActiveNetworkType() { 47 | ConnectivityManager connectivity = 48 | (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 49 | if (connectivity == null) { 50 | Log.w(Constants.TAG, "couldn't get connectivity manager"); 51 | return null; 52 | } 53 | 54 | NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); 55 | if (activeInfo == null) { 56 | if (Constants.LOGVV) { 57 | Log.v(Constants.TAG, "network is not available"); 58 | } 59 | return null; 60 | } 61 | return activeInfo.getType(); 62 | } 63 | 64 | public boolean isNetworkRoaming() { 65 | ConnectivityManager connectivity = 66 | (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 67 | if (connectivity == null) { 68 | Log.w(Constants.TAG, "couldn't get connectivity manager"); 69 | return false; 70 | } 71 | 72 | NetworkInfo info = connectivity.getActiveNetworkInfo(); 73 | boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); 74 | TelephonyManager tm = (TelephonyManager) mContext 75 | .getSystemService(Context.TELEPHONY_SERVICE); 76 | if (null == tm) { 77 | Log.w(Constants.TAG, "couldn't get telephony manager"); 78 | return false; 79 | } 80 | boolean isRoaming = isMobile && tm.isNetworkRoaming(); 81 | if (Constants.LOGVV && isRoaming) { 82 | Log.v(Constants.TAG, "network is roaming"); 83 | } 84 | return isRoaming; 85 | } 86 | 87 | public Long getMaxBytesOverMobile() { 88 | return (long) Integer.MAX_VALUE; 89 | } 90 | 91 | public Long getRecommendedMaxBytesOverMobile() { 92 | return 2097152L; 93 | } 94 | 95 | public void sendBroadcast(Intent intent) { 96 | mContext.sendBroadcast(intent); 97 | } 98 | 99 | public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException { 100 | return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid; 101 | } 102 | 103 | public void postNotification(long id, Notification notification) { 104 | /** 105 | * TODO: The system notification manager takes ints, not longs, as IDs, 106 | * but the download manager uses IDs take straight from the database, 107 | * which are longs. This will have to be dealt with at some point. 108 | */ 109 | mNotificationManager.notify((int) id, notification); 110 | } 111 | 112 | public void cancelNotification(long id) { 113 | mNotificationManager.cancel((int) id); 114 | } 115 | 116 | public void cancelAllNotifications() { 117 | mNotificationManager.cancelAll(); 118 | } 119 | 120 | public void startThread(Thread thread) { 121 | thread.start(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import android.app.Service; 20 | import android.content.Intent; 21 | import android.os.Handler; 22 | import android.os.HandlerThread; 23 | import android.os.IBinder; 24 | import android.os.Looper; 25 | import android.os.Message; 26 | import android.util.Log; 27 | 28 | /** 29 | * This service differs from IntentService in a few minor ways/ It will not 30 | * auto-stop itself after the intent is handled unless the target returns "true" 31 | * in should stop. Since the goal of this service is to handle a single kind of 32 | * intent, it does not queue up batches of intents of the same type. 33 | */ 34 | public abstract class CustomIntentService extends Service { 35 | private String mName; 36 | private boolean mRedelivery; 37 | private volatile ServiceHandler mServiceHandler; 38 | private volatile Looper mServiceLooper; 39 | private static final String LOG_TAG = "CancellableIntentService"; 40 | private static final int WHAT_MESSAGE = -10; 41 | 42 | public CustomIntentService(String paramString) { 43 | this.mName = paramString; 44 | } 45 | 46 | @Override 47 | public IBinder onBind(Intent paramIntent) { 48 | return null; 49 | } 50 | 51 | @Override 52 | public void onCreate() { 53 | super.onCreate(); 54 | HandlerThread localHandlerThread = new HandlerThread("IntentService[" 55 | + this.mName + "]"); 56 | localHandlerThread.start(); 57 | this.mServiceLooper = localHandlerThread.getLooper(); 58 | this.mServiceHandler = new ServiceHandler(this.mServiceLooper); 59 | } 60 | 61 | @Override 62 | public void onDestroy() { 63 | Thread localThread = this.mServiceLooper.getThread(); 64 | if ((localThread != null) && (localThread.isAlive())) { 65 | localThread.interrupt(); 66 | } 67 | this.mServiceLooper.quit(); 68 | Log.d(LOG_TAG, "onDestroy"); 69 | } 70 | 71 | protected abstract void onHandleIntent(Intent paramIntent); 72 | 73 | protected abstract boolean shouldStop(); 74 | 75 | @Override 76 | public void onStart(Intent paramIntent, int startId) { 77 | if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) { 78 | Message localMessage = this.mServiceHandler.obtainMessage(); 79 | localMessage.arg1 = startId; 80 | localMessage.obj = paramIntent; 81 | localMessage.what = WHAT_MESSAGE; 82 | this.mServiceHandler.sendMessage(localMessage); 83 | } 84 | } 85 | 86 | @Override 87 | public int onStartCommand(Intent paramIntent, int flags, int startId) { 88 | onStart(paramIntent, startId); 89 | return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; 90 | } 91 | 92 | public void setIntentRedelivery(boolean enabled) { 93 | this.mRedelivery = enabled; 94 | } 95 | 96 | private final class ServiceHandler extends Handler { 97 | public ServiceHandler(Looper looper) { 98 | super(looper); 99 | } 100 | 101 | @Override 102 | public void handleMessage(Message paramMessage) { 103 | CustomIntentService.this 104 | .onHandleIntent((Intent) paramMessage.obj); 105 | if (shouldStop()) { 106 | Log.d(LOG_TAG, "stopSelf"); 107 | CustomIntentService.this.stopSelf(paramMessage.arg1); 108 | Log.d(LOG_TAG, "afterStopSelf"); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | /** 20 | * Uses the class-loader model to utilize the updated notification builders in 21 | * Honeycomb while maintaining a compatible version for older devices. 22 | */ 23 | public class CustomNotificationFactory { 24 | static public DownloadNotification.ICustomNotification createCustomNotification() { 25 | if (android.os.Build.VERSION.SDK_INT > 13) 26 | return new V14CustomNotification(); 27 | else 28 | return new V3CustomNotification(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import com.google.android.vending.expansion.downloader.Constants; 20 | import com.google.android.vending.expansion.downloader.Helpers; 21 | 22 | import android.util.Log; 23 | 24 | /** 25 | * Representation of information about an individual download from the database. 26 | */ 27 | public class DownloadInfo { 28 | public String mUri; 29 | public final int mIndex; 30 | public final String mFileName; 31 | public String mETag; 32 | public long mTotalBytes; 33 | public long mCurrentBytes; 34 | public long mLastMod; 35 | public int mStatus; 36 | public int mControl; 37 | public int mNumFailed; 38 | public int mRetryAfter; 39 | public int mRedirectCount; 40 | 41 | boolean mInitialized; 42 | 43 | public int mFuzz; 44 | 45 | public DownloadInfo(int index, String fileName, String pkg) { 46 | mFuzz = Helpers.sRandom.nextInt(1001); 47 | mFileName = fileName; 48 | mIndex = index; 49 | } 50 | 51 | public void resetDownload() { 52 | mCurrentBytes = 0; 53 | mETag = ""; 54 | mLastMod = 0; 55 | mStatus = 0; 56 | mControl = 0; 57 | mNumFailed = 0; 58 | mRetryAfter = 0; 59 | mRedirectCount = 0; 60 | } 61 | 62 | /** 63 | * Returns the time when a download should be restarted. 64 | */ 65 | public long restartTime(long now) { 66 | if (mNumFailed == 0) { 67 | return now; 68 | } 69 | if (mRetryAfter > 0) { 70 | return mLastMod + mRetryAfter; 71 | } 72 | return mLastMod + 73 | Constants.RETRY_FIRST_DELAY * 74 | (1000 + mFuzz) * (1 << (mNumFailed - 1)); 75 | } 76 | 77 | public void logVerboseInfo() { 78 | Log.v(Constants.TAG, "Service adding new entry"); 79 | Log.v(Constants.TAG, "FILENAME: " + mFileName); 80 | Log.v(Constants.TAG, "URI : " + mUri); 81 | Log.v(Constants.TAG, "FILENAME: " + mFileName); 82 | Log.v(Constants.TAG, "CONTROL : " + mControl); 83 | Log.v(Constants.TAG, "STATUS : " + mStatus); 84 | Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); 85 | Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); 86 | Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); 87 | Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); 88 | Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); 89 | Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); 90 | Log.v(Constants.TAG, "ETAG : " + mETag); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import com.android.vending.expansion.downloader.R; 20 | import com.google.android.vending.expansion.downloader.DownloadProgressInfo; 21 | import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; 22 | import com.google.android.vending.expansion.downloader.Helpers; 23 | import com.google.android.vending.expansion.downloader.IDownloaderClient; 24 | 25 | import android.app.Notification; 26 | import android.app.NotificationManager; 27 | import android.app.PendingIntent; 28 | import android.content.Context; 29 | import android.os.Messenger; 30 | 31 | /** 32 | * This class handles displaying the notification associated with the download 33 | * queue going on in the download manager. It handles multiple status types; 34 | * Some require user interaction and some do not. Some of the user interactions 35 | * may be transient. (for example: the user is queried to continue the download 36 | * on 3G when it started on WiFi, but then the phone locks onto WiFi again so 37 | * the prompt automatically goes away) 38 | *
39 | * The application interface for the downloader also needs to understand and 40 | * handle these transient states. 41 | */ 42 | public class DownloadNotification implements IDownloaderClient { 43 | 44 | private int mState; 45 | private final Context mContext; 46 | private final NotificationManager mNotificationManager; 47 | private String mCurrentTitle; 48 | 49 | private IDownloaderClient mClientProxy; 50 | final ICustomNotification mCustomNotification; 51 | private Notification.Builder mNotification; 52 | private Notification.Builder mCurrentNotification; 53 | private CharSequence mLabel; 54 | private String mCurrentText; 55 | private PendingIntent mContentIntent; 56 | private DownloadProgressInfo mProgressInfo; 57 | 58 | static final String LOGTAG = "DownloadNotification"; 59 | static final int NOTIFICATION_ID = LOGTAG.hashCode(); 60 | 61 | public PendingIntent getClientIntent() { 62 | return mContentIntent; 63 | } 64 | 65 | public void setClientIntent(PendingIntent mClientIntent) { 66 | this.mContentIntent = mClientIntent; 67 | } 68 | 69 | public void resendState() { 70 | if (null != mClientProxy) { 71 | mClientProxy.onDownloadStateChanged(mState); 72 | } 73 | } 74 | 75 | @Override 76 | public void onDownloadStateChanged(int newState) { 77 | if (null != mClientProxy) { 78 | mClientProxy.onDownloadStateChanged(newState); 79 | } 80 | if (newState != mState) { 81 | mState = newState; 82 | if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) { 83 | return; 84 | } 85 | int stringDownloadID; 86 | int iconResource; 87 | boolean ongoingEvent; 88 | 89 | // get the new title string and paused text 90 | switch (newState) { 91 | case 0: 92 | iconResource = android.R.drawable.stat_sys_warning; 93 | stringDownloadID = R.string.state_unknown; 94 | ongoingEvent = false; 95 | break; 96 | 97 | case IDownloaderClient.STATE_DOWNLOADING: 98 | iconResource = android.R.drawable.stat_sys_download; 99 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); 100 | ongoingEvent = true; 101 | break; 102 | 103 | case IDownloaderClient.STATE_FETCHING_URL: 104 | case IDownloaderClient.STATE_CONNECTING: 105 | iconResource = android.R.drawable.stat_sys_download_done; 106 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); 107 | ongoingEvent = true; 108 | break; 109 | 110 | case IDownloaderClient.STATE_COMPLETED: 111 | case IDownloaderClient.STATE_PAUSED_BY_REQUEST: 112 | iconResource = android.R.drawable.stat_sys_download_done; 113 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); 114 | ongoingEvent = false; 115 | break; 116 | 117 | case IDownloaderClient.STATE_FAILED: 118 | case IDownloaderClient.STATE_FAILED_CANCELED: 119 | case IDownloaderClient.STATE_FAILED_FETCHING_URL: 120 | case IDownloaderClient.STATE_FAILED_SDCARD_FULL: 121 | case IDownloaderClient.STATE_FAILED_UNLICENSED: 122 | iconResource = android.R.drawable.stat_sys_warning; 123 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); 124 | ongoingEvent = false; 125 | break; 126 | 127 | default: 128 | iconResource = android.R.drawable.stat_sys_warning; 129 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); 130 | ongoingEvent = true; 131 | break; 132 | } 133 | mCurrentText = mContext.getString(stringDownloadID); 134 | mCurrentTitle = mLabel.toString(); 135 | mCurrentNotification.setTicker(mLabel + ": " + mCurrentText); 136 | mCurrentNotification.setSmallIcon(iconResource); 137 | mCurrentNotification.setContentTitle(mCurrentTitle); 138 | mCurrentNotification.setContentText(mCurrentText); 139 | mCurrentNotification.setContentIntent(mContentIntent); 140 | if (ongoingEvent) { 141 | mCurrentNotification.setOngoing(true); 142 | } else { 143 | mCurrentNotification.setOngoing(false); 144 | mCurrentNotification.setAutoCancel(true); 145 | } 146 | mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification.build()); 147 | } 148 | } 149 | 150 | @Override 151 | public void onDownloadProgress(DownloadProgressInfo progress) { 152 | mProgressInfo = progress; 153 | if (null != mClientProxy) { 154 | mClientProxy.onDownloadProgress(progress); 155 | } 156 | if (progress.mOverallTotal <= 0) { 157 | // we just show the text 158 | mNotification.setTicker(mCurrentTitle); 159 | mNotification.setSmallIcon(android.R.drawable.stat_sys_download); 160 | mNotification.setContentTitle(mLabel); 161 | mNotification.setContentText(mCurrentText); 162 | mNotification.setContentIntent(mContentIntent); 163 | mCurrentNotification = mNotification; 164 | } else { 165 | mCustomNotification.setCurrentBytes(progress.mOverallProgress); 166 | mCustomNotification.setTotalBytes(progress.mOverallTotal); 167 | mCustomNotification.setIcon(android.R.drawable.stat_sys_download); 168 | mCustomNotification.setPendingIntent(mContentIntent); 169 | mCustomNotification.setTicker(mLabel + ": " + mCurrentText); 170 | mCustomNotification.setTitle(mLabel); 171 | mCustomNotification.setTimeRemaining(progress.mTimeRemaining); 172 | } 173 | mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification.build()); 174 | } 175 | 176 | public interface ICustomNotification { 177 | void setTitle(CharSequence title); 178 | 179 | void setTicker(CharSequence ticker); 180 | 181 | void setPendingIntent(PendingIntent mContentIntent); 182 | 183 | void setTotalBytes(long totalBytes); 184 | 185 | void setCurrentBytes(long currentBytes); 186 | 187 | void setIcon(int iconResource); 188 | 189 | void setTimeRemaining(long timeRemaining); 190 | 191 | Notification updateNotification(Context c); 192 | } 193 | 194 | /** 195 | * Called in response to onClientUpdated. Creates a new proxy and notifies 196 | * it of the current state. 197 | * 198 | * @param msg the client Messenger to notify 199 | */ 200 | public void setMessenger(Messenger msg) { 201 | mClientProxy = DownloaderClientMarshaller.CreateProxy(msg); 202 | if (null != mProgressInfo) { 203 | mClientProxy.onDownloadProgress(mProgressInfo); 204 | } 205 | if (mState != -1) { 206 | mClientProxy.onDownloadStateChanged(mState); 207 | } 208 | } 209 | 210 | /** 211 | * Constructor 212 | * 213 | * @param ctx The context to use to obtain access to the Notification 214 | * Service 215 | */ 216 | DownloadNotification(Context ctx, CharSequence applicationLabel) { 217 | mState = -1; 218 | mContext = ctx; 219 | mLabel = applicationLabel; 220 | mNotificationManager = (NotificationManager) 221 | mContext.getSystemService(Context.NOTIFICATION_SERVICE); 222 | mCustomNotification = CustomNotificationFactory 223 | .createCustomNotification(); 224 | mNotification = new Notification.Builder(mContext); 225 | mCurrentNotification = mNotification; 226 | 227 | } 228 | 229 | @Override 230 | public void onServiceConnected(Messenger m) { 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import android.text.format.Time; 20 | 21 | import java.util.Calendar; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * Helper for parsing an HTTP date. 27 | */ 28 | public final class HttpDateTime { 29 | 30 | /* 31 | * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT 32 | * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850, 33 | * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format 34 | * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon 35 | * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS 36 | * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon 37 | * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first 38 | * digit is zero. Mon can be the full name of the month. 39 | */ 40 | private static final String HTTP_DATE_RFC_REGEXP = 41 | "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" 42 | + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; 43 | 44 | private static final String HTTP_DATE_ANSIC_REGEXP = 45 | "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" 46 | + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; 47 | 48 | /** 49 | * The compiled version of the HTTP-date regular expressions. 50 | */ 51 | private static final Pattern HTTP_DATE_RFC_PATTERN = 52 | Pattern.compile(HTTP_DATE_RFC_REGEXP); 53 | private static final Pattern HTTP_DATE_ANSIC_PATTERN = 54 | Pattern.compile(HTTP_DATE_ANSIC_REGEXP); 55 | 56 | private static class TimeOfDay { 57 | TimeOfDay(int h, int m, int s) { 58 | this.hour = h; 59 | this.minute = m; 60 | this.second = s; 61 | } 62 | 63 | int hour; 64 | int minute; 65 | int second; 66 | } 67 | 68 | public static long parse(String timeString) 69 | throws IllegalArgumentException { 70 | 71 | int date = 1; 72 | int month = Calendar.JANUARY; 73 | int year = 1970; 74 | TimeOfDay timeOfDay; 75 | 76 | Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); 77 | if (rfcMatcher.find()) { 78 | date = getDate(rfcMatcher.group(1)); 79 | month = getMonth(rfcMatcher.group(2)); 80 | year = getYear(rfcMatcher.group(3)); 81 | timeOfDay = getTime(rfcMatcher.group(4)); 82 | } else { 83 | Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); 84 | if (ansicMatcher.find()) { 85 | month = getMonth(ansicMatcher.group(1)); 86 | date = getDate(ansicMatcher.group(2)); 87 | timeOfDay = getTime(ansicMatcher.group(3)); 88 | year = getYear(ansicMatcher.group(4)); 89 | } else { 90 | throw new IllegalArgumentException(); 91 | } 92 | } 93 | 94 | // FIXME: Y2038 BUG! 95 | if (year >= 2038) { 96 | year = 2038; 97 | month = Calendar.JANUARY; 98 | date = 1; 99 | } 100 | 101 | Time time = new Time(Time.TIMEZONE_UTC); 102 | time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, 103 | month, year); 104 | return time.toMillis(false /* use isDst */); 105 | } 106 | 107 | private static int getDate(String dateString) { 108 | if (dateString.length() == 2) { 109 | return (dateString.charAt(0) - '0') * 10 110 | + (dateString.charAt(1) - '0'); 111 | } else { 112 | return (dateString.charAt(0) - '0'); 113 | } 114 | } 115 | 116 | /* 117 | * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0 118 | * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20 119 | * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19 120 | * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9 121 | */ 122 | private static int getMonth(String monthString) { 123 | int hash = Character.toLowerCase(monthString.charAt(0)) + 124 | Character.toLowerCase(monthString.charAt(1)) + 125 | Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; 126 | switch (hash) { 127 | case 22: 128 | return Calendar.JANUARY; 129 | case 10: 130 | return Calendar.FEBRUARY; 131 | case 29: 132 | return Calendar.MARCH; 133 | case 32: 134 | return Calendar.APRIL; 135 | case 36: 136 | return Calendar.MAY; 137 | case 42: 138 | return Calendar.JUNE; 139 | case 40: 140 | return Calendar.JULY; 141 | case 26: 142 | return Calendar.AUGUST; 143 | case 37: 144 | return Calendar.SEPTEMBER; 145 | case 35: 146 | return Calendar.OCTOBER; 147 | case 48: 148 | return Calendar.NOVEMBER; 149 | case 9: 150 | return Calendar.DECEMBER; 151 | default: 152 | throw new IllegalArgumentException(); 153 | } 154 | } 155 | 156 | private static int getYear(String yearString) { 157 | if (yearString.length() == 2) { 158 | int year = (yearString.charAt(0) - '0') * 10 159 | + (yearString.charAt(1) - '0'); 160 | if (year >= 70) { 161 | return year + 1900; 162 | } else { 163 | return year + 2000; 164 | } 165 | } else if (yearString.length() == 3) { 166 | // According to RFC 2822, three digit years should be added to 1900. 167 | int year = (yearString.charAt(0) - '0') * 100 168 | + (yearString.charAt(1) - '0') * 10 169 | + (yearString.charAt(2) - '0'); 170 | return year + 1900; 171 | } else if (yearString.length() == 4) { 172 | return (yearString.charAt(0) - '0') * 1000 173 | + (yearString.charAt(1) - '0') * 100 174 | + (yearString.charAt(2) - '0') * 10 175 | + (yearString.charAt(3) - '0'); 176 | } else { 177 | return 1970; 178 | } 179 | } 180 | 181 | private static TimeOfDay getTime(String timeString) { 182 | // HH might be H 183 | int i = 0; 184 | int hour = timeString.charAt(i++) - '0'; 185 | if (timeString.charAt(i) != ':') 186 | hour = hour * 10 + (timeString.charAt(i++) - '0'); 187 | // Skip ':' 188 | i++; 189 | 190 | int minute = (timeString.charAt(i++) - '0') * 10 191 | + (timeString.charAt(i++) - '0'); 192 | // Skip ':' 193 | i++; 194 | 195 | int second = (timeString.charAt(i++) - '0') * 10 196 | + (timeString.charAt(i++) - '0'); 197 | 198 | return new TimeOfDay(hour, minute, second); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import com.android.vending.expansion.downloader.R; 20 | import com.google.android.vending.expansion.downloader.Helpers; 21 | 22 | import android.app.Notification; 23 | import android.app.PendingIntent; 24 | import android.content.Context; 25 | 26 | public class V14CustomNotification implements DownloadNotification.ICustomNotification { 27 | 28 | CharSequence mTitle; 29 | CharSequence mTicker; 30 | int mIcon; 31 | long mTotalKB = -1; 32 | long mCurrentKB = -1; 33 | long mTimeRemaining; 34 | PendingIntent mPendingIntent; 35 | 36 | @Override 37 | public void setIcon(int icon) { 38 | mIcon = icon; 39 | } 40 | 41 | @Override 42 | public void setTitle(CharSequence title) { 43 | mTitle = title; 44 | } 45 | 46 | @Override 47 | public void setTotalBytes(long totalBytes) { 48 | mTotalKB = totalBytes; 49 | } 50 | 51 | @Override 52 | public void setCurrentBytes(long currentBytes) { 53 | mCurrentKB = currentBytes; 54 | } 55 | 56 | void setProgress(Notification.Builder builder) { 57 | 58 | } 59 | 60 | @Override 61 | public Notification updateNotification(Context c) { 62 | Notification.Builder builder = new Notification.Builder(c); 63 | builder.setContentTitle(mTitle); 64 | if (mTotalKB > 0 && -1 != mCurrentKB) { 65 | builder.setProgress((int) (mTotalKB >> 8), (int) (mCurrentKB >> 8), false); 66 | } else { 67 | builder.setProgress(0, 0, true); 68 | } 69 | builder.setContentText(Helpers.getDownloadProgressString(mCurrentKB, mTotalKB)); 70 | builder.setContentInfo(c.getString(R.string.time_remaining_notification, 71 | Helpers.getTimeRemaining(mTimeRemaining))); 72 | if (mIcon != 0) { 73 | builder.setSmallIcon(mIcon); 74 | } else { 75 | int iconResource = android.R.drawable.stat_sys_download; 76 | builder.setSmallIcon(iconResource); 77 | } 78 | builder.setOngoing(true); 79 | builder.setTicker(mTicker); 80 | builder.setContentIntent(mPendingIntent); 81 | builder.setOnlyAlertOnce(true); 82 | 83 | return builder.getNotification(); 84 | } 85 | 86 | @Override 87 | public void setPendingIntent(PendingIntent contentIntent) { 88 | mPendingIntent = contentIntent; 89 | } 90 | 91 | @Override 92 | public void setTicker(CharSequence ticker) { 93 | mTicker = ticker; 94 | } 95 | 96 | @Override 97 | public void setTimeRemaining(long timeRemaining) { 98 | mTimeRemaining = timeRemaining; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import com.android.vending.expansion.downloader.R; 20 | import com.google.android.vending.expansion.downloader.Helpers; 21 | 22 | import android.app.Notification; 23 | import android.app.PendingIntent; 24 | import android.content.Context; 25 | import android.graphics.BitmapFactory; 26 | import android.view.View; 27 | import android.widget.RemoteViews; 28 | 29 | public class V3CustomNotification implements DownloadNotification.ICustomNotification { 30 | 31 | CharSequence mTitle; 32 | CharSequence mTicker; 33 | int mIcon; 34 | long mTotalBytes = -1; 35 | long mCurrentBytes = -1; 36 | long mTimeRemaining; 37 | PendingIntent mPendingIntent; 38 | Notification mNotification = new Notification(); 39 | 40 | @Override 41 | public void setIcon(int icon) { 42 | mIcon = icon; 43 | } 44 | 45 | @Override 46 | public void setTitle(CharSequence title) { 47 | mTitle = title; 48 | } 49 | 50 | @Override 51 | public void setTotalBytes(long totalBytes) { 52 | mTotalBytes = totalBytes; 53 | } 54 | 55 | @Override 56 | public void setCurrentBytes(long currentBytes) { 57 | mCurrentBytes = currentBytes; 58 | } 59 | 60 | @Override 61 | public Notification updateNotification(Context c) { 62 | Notification n = mNotification; 63 | 64 | n.icon = mIcon; 65 | 66 | n.flags |= Notification.FLAG_ONGOING_EVENT; 67 | 68 | if (android.os.Build.VERSION.SDK_INT > 10) { 69 | n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // only matters for 70 | // Honeycomb 71 | } 72 | 73 | // Build the RemoteView object 74 | RemoteViews expandedView = new RemoteViews( 75 | c.getPackageName(), 76 | R.layout.status_bar_ongoing_event_progress_bar); 77 | 78 | expandedView.setTextViewText(R.id.title, mTitle); 79 | // look at strings 80 | expandedView.setViewVisibility(R.id.description, View.VISIBLE); 81 | expandedView.setTextViewText(R.id.description, 82 | Helpers.getDownloadProgressString(mCurrentBytes, mTotalBytes)); 83 | expandedView.setViewVisibility(R.id.progress_bar_frame, View.VISIBLE); 84 | expandedView.setProgressBar(R.id.progress_bar, 85 | (int) (mTotalBytes >> 8), 86 | (int) (mCurrentBytes >> 8), 87 | mTotalBytes <= 0); 88 | expandedView.setViewVisibility(R.id.time_remaining, View.VISIBLE); 89 | expandedView.setTextViewText( 90 | R.id.time_remaining, 91 | c.getString(R.string.time_remaining_notification, 92 | Helpers.getTimeRemaining(mTimeRemaining))); 93 | expandedView.setTextViewText(R.id.progress_text, 94 | Helpers.getDownloadProgressPercent(mCurrentBytes, mTotalBytes)); 95 | expandedView.setImageViewResource(R.id.appIcon, mIcon); 96 | n.contentView = expandedView; 97 | n.contentIntent = mPendingIntent; 98 | return n; 99 | } 100 | 101 | @Override 102 | public void setPendingIntent(PendingIntent contentIntent) { 103 | mPendingIntent = contentIntent; 104 | } 105 | 106 | @Override 107 | public void setTicker(CharSequence ticker) { 108 | mTicker = ticker; 109 | } 110 | 111 | @Override 112 | public void setTimeRemaining(long timeRemaining) { 113 | mTimeRemaining = timeRemaining; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /dependency/play_licensing/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 |22 | * The LICENSED response from the server contains a user identifier unique to 23 | * the <application, user> pair. The developer can send this identifier 24 | * to their own server along with some device identifier (a random number 25 | * generated and stored once per application installation, 26 | * {@link android.telephony.TelephonyManager#getDeviceId getDeviceId}, 27 | * {@link android.provider.Settings.Secure#ANDROID_ID ANDROID_ID}, etc). 28 | * The more sources used to identify the device, the harder it will be for an 29 | * attacker to spoof. 30 | *
31 | * The server can look at the <application, user, device id> tuple and 32 | * restrict a user's application license to run on at most 10 different devices 33 | * in a week (for example). We recommend not being too restrictive because a 34 | * user might legitimately have multiple devices or be in the process of 35 | * changing phones. This will catch egregious violations of multiple people 36 | * sharing one license. 37 | */ 38 | public interface DeviceLimiter { 39 | 40 | /** 41 | * Checks if this device is allowed to use the given user's license. 42 | * 43 | * @param userId the user whose license the server responded with 44 | * @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs 45 | */ 46 | int isDeviceAllowed(String userId); 47 | } 48 | -------------------------------------------------------------------------------- /dependency/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is auto-generated. DO NOT MODIFY. 3 | * Original file: aidl/ILicenseResultListener.aidl 4 | */ 5 | package com.google.android.vending.licensing; 6 | import java.lang.String; 7 | import android.os.RemoteException; 8 | import android.os.IBinder; 9 | import android.os.IInterface; 10 | import android.os.Binder; 11 | import android.os.Parcel; 12 | public interface ILicenseResultListener extends android.os.IInterface 13 | { 14 | /** Local-side IPC implementation stub class. */ 15 | public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicenseResultListener 16 | { 17 | private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicenseResultListener"; 18 | /** Construct the stub at attach it to the interface. */ 19 | public Stub() 20 | { 21 | this.attachInterface(this, DESCRIPTOR); 22 | } 23 | /** 24 | * Cast an IBinder object into an ILicenseResultListener interface, 25 | * generating a proxy if needed. 26 | */ 27 | public static com.google.android.vending.licensing.ILicenseResultListener asInterface(android.os.IBinder obj) 28 | { 29 | if ((obj==null)) { 30 | return null; 31 | } 32 | android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); 33 | if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicenseResultListener))) { 34 | return ((com.google.android.vending.licensing.ILicenseResultListener)iin); 35 | } 36 | return new com.google.android.vending.licensing.ILicenseResultListener.Stub.Proxy(obj); 37 | } 38 | public android.os.IBinder asBinder() 39 | { 40 | return this; 41 | } 42 | public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException 43 | { 44 | switch (code) 45 | { 46 | case INTERFACE_TRANSACTION: 47 | { 48 | reply.writeString(DESCRIPTOR); 49 | return true; 50 | } 51 | case TRANSACTION_verifyLicense: 52 | { 53 | data.enforceInterface(DESCRIPTOR); 54 | int _arg0; 55 | _arg0 = data.readInt(); 56 | java.lang.String _arg1; 57 | _arg1 = data.readString(); 58 | java.lang.String _arg2; 59 | _arg2 = data.readString(); 60 | this.verifyLicense(_arg0, _arg1, _arg2); 61 | return true; 62 | } 63 | } 64 | return super.onTransact(code, data, reply, flags); 65 | } 66 | private static class Proxy implements com.google.android.vending.licensing.ILicenseResultListener 67 | { 68 | private android.os.IBinder mRemote; 69 | Proxy(android.os.IBinder remote) 70 | { 71 | mRemote = remote; 72 | } 73 | public android.os.IBinder asBinder() 74 | { 75 | return mRemote; 76 | } 77 | public java.lang.String getInterfaceDescriptor() 78 | { 79 | return DESCRIPTOR; 80 | } 81 | public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException 82 | { 83 | android.os.Parcel _data = android.os.Parcel.obtain(); 84 | try { 85 | _data.writeInterfaceToken(DESCRIPTOR); 86 | _data.writeInt(responseCode); 87 | _data.writeString(signedData); 88 | _data.writeString(signature); 89 | mRemote.transact(Stub.TRANSACTION_verifyLicense, _data, null, IBinder.FLAG_ONEWAY); 90 | } 91 | finally { 92 | _data.recycle(); 93 | } 94 | } 95 | } 96 | static final int TRANSACTION_verifyLicense = (IBinder.FIRST_CALL_TRANSACTION + 0); 97 | } 98 | public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException; 99 | } 100 | -------------------------------------------------------------------------------- /dependency/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is auto-generated. DO NOT MODIFY. 3 | * Original file: aidl/ILicensingService.aidl 4 | */ 5 | package com.google.android.vending.licensing; 6 | import java.lang.String; 7 | import android.os.RemoteException; 8 | import android.os.IBinder; 9 | import android.os.IInterface; 10 | import android.os.Binder; 11 | import android.os.Parcel; 12 | public interface ILicensingService extends android.os.IInterface 13 | { 14 | /** Local-side IPC implementation stub class. */ 15 | public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicensingService 16 | { 17 | private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicensingService"; 18 | /** Construct the stub at attach it to the interface. */ 19 | public Stub() 20 | { 21 | this.attachInterface(this, DESCRIPTOR); 22 | } 23 | /** 24 | * Cast an IBinder object into an ILicensingService interface, 25 | * generating a proxy if needed. 26 | */ 27 | public static com.google.android.vending.licensing.ILicensingService asInterface(android.os.IBinder obj) 28 | { 29 | if ((obj==null)) { 30 | return null; 31 | } 32 | android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); 33 | if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicensingService))) { 34 | return ((com.google.android.vending.licensing.ILicensingService)iin); 35 | } 36 | return new com.google.android.vending.licensing.ILicensingService.Stub.Proxy(obj); 37 | } 38 | public android.os.IBinder asBinder() 39 | { 40 | return this; 41 | } 42 | public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException 43 | { 44 | switch (code) 45 | { 46 | case INTERFACE_TRANSACTION: 47 | { 48 | reply.writeString(DESCRIPTOR); 49 | return true; 50 | } 51 | case TRANSACTION_checkLicense: 52 | { 53 | data.enforceInterface(DESCRIPTOR); 54 | long _arg0; 55 | _arg0 = data.readLong(); 56 | java.lang.String _arg1; 57 | _arg1 = data.readString(); 58 | com.google.android.vending.licensing.ILicenseResultListener _arg2; 59 | _arg2 = com.google.android.vending.licensing.ILicenseResultListener.Stub.asInterface(data.readStrongBinder()); 60 | this.checkLicense(_arg0, _arg1, _arg2); 61 | return true; 62 | } 63 | } 64 | return super.onTransact(code, data, reply, flags); 65 | } 66 | private static class Proxy implements com.google.android.vending.licensing.ILicensingService 67 | { 68 | private android.os.IBinder mRemote; 69 | Proxy(android.os.IBinder remote) 70 | { 71 | mRemote = remote; 72 | } 73 | public android.os.IBinder asBinder() 74 | { 75 | return mRemote; 76 | } 77 | public java.lang.String getInterfaceDescriptor() 78 | { 79 | return DESCRIPTOR; 80 | } 81 | public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException 82 | { 83 | android.os.Parcel _data = android.os.Parcel.obtain(); 84 | try { 85 | _data.writeInterfaceToken(DESCRIPTOR); 86 | _data.writeLong(nonce); 87 | _data.writeString(packageName); 88 | _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null))); 89 | mRemote.transact(Stub.TRANSACTION_checkLicense, _data, null, IBinder.FLAG_ONEWAY); 90 | } 91 | finally { 92 | _data.recycle(); 93 | } 94 | } 95 | } 96 | static final int TRANSACTION_checkLicense = (IBinder.FIRST_CALL_TRANSACTION + 0); 97 | } 98 | public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException; 99 | } 100 | -------------------------------------------------------------------------------- /dependency/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | import com.google.android.vending.licensing.util.Base64; 20 | import com.google.android.vending.licensing.util.Base64DecoderException; 21 | 22 | import android.content.ComponentName; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.ServiceConnection; 26 | import android.content.pm.PackageManager.NameNotFoundException; 27 | import android.os.Handler; 28 | import android.os.HandlerThread; 29 | import android.os.IBinder; 30 | import android.os.RemoteException; 31 | import android.provider.Settings.Secure; 32 | import android.util.Log; 33 | 34 | import java.security.KeyFactory; 35 | import java.security.NoSuchAlgorithmException; 36 | import java.security.PublicKey; 37 | import java.security.SecureRandom; 38 | import java.security.spec.InvalidKeySpecException; 39 | import java.security.spec.X509EncodedKeySpec; 40 | import java.util.Date; 41 | import java.util.HashSet; 42 | import java.util.LinkedList; 43 | import java.util.Queue; 44 | import java.util.Set; 45 | 46 | /** 47 | * Client library for Android Market license verifications. 48 | *
49 | * The LicenseChecker is configured via a {@link Policy} which contains the 50 | * logic to determine whether a user should have access to the application. For 51 | * example, the Policy can define a threshold for allowable number of server or 52 | * client failures before the library reports the user as not having access. 53 | *
54 | * Must also provide the Base64-encoded RSA public key associated with your
55 | * developer account. The public key is obtainable from the publisher site.
56 | */
57 | public class LicenseChecker implements ServiceConnection {
58 | private static final String TAG = "LicenseChecker";
59 |
60 | private static final String KEY_FACTORY_ALGORITHM = "RSA";
61 |
62 | // Timeout value (in milliseconds) for calls to service.
63 | private static final int TIMEOUT_MS = 10 * 1000;
64 |
65 | private static final SecureRandom RANDOM = new SecureRandom();
66 | private static final boolean DEBUG_LICENSE_ERROR = false;
67 |
68 | private ILicensingService mService;
69 |
70 | private PublicKey mPublicKey;
71 | private final Context mContext;
72 | private final Policy mPolicy;
73 | /**
74 | * A handler for running tasks on a background thread. We don't want license
75 | * processing to block the UI thread.
76 | */
77 | private Handler mHandler;
78 | private final String mPackageName;
79 | private final String mVersionCode;
80 | private final Set
128 | * NOTE: This call uses a trivially obfuscated string (base64-encoded). For best security,
129 | * we recommend obfuscating the string that is passed into bindService using another method
130 | * of your own devising.
131 | *
132 | * source string: "com.android.vending.licensing.ILicensingService"
133 | *
134 | * @param callback
135 | */
136 | public synchronized void checkAccess(LicenseCheckerCallback callback) {
137 | // If we have a valid recent LICENSED response, we can skip asking
138 | // Market.
139 | if (mPolicy.allowAccess()) {
140 | Log.i(TAG, "Using cached license response");
141 | callback.allow(Policy.LICENSED);
142 | } else {
143 | LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(),
144 | callback, generateNonce(), mPackageName, mVersionCode);
145 |
146 | if (mService == null) {
147 | Log.i(TAG, "Binding to licensing service.");
148 | try {
149 | boolean bindResult = mContext
150 | .bindService(
151 | new Intent(
152 | new String(
153 | Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")))
154 | .setPackage("com.android.vending"),
155 | this, // ServiceConnection.
156 | Context.BIND_AUTO_CREATE);
157 |
158 | if (bindResult) {
159 | mPendingChecks.offer(validator);
160 | } else {
161 | Log.e(TAG, "Could not bind to service.");
162 | handleServiceConnectionError(validator);
163 | }
164 | } catch (SecurityException e) {
165 | callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION);
166 | } catch (Base64DecoderException e) {
167 | e.printStackTrace();
168 | }
169 | } else {
170 | mPendingChecks.offer(validator);
171 | runChecks();
172 | }
173 | }
174 | }
175 |
176 | private void runChecks() {
177 | LicenseValidator validator;
178 | while ((validator = mPendingChecks.poll()) != null) {
179 | try {
180 | Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName());
181 | mService.checkLicense(
182 | validator.getNonce(), validator.getPackageName(),
183 | new ResultListener(validator));
184 | mChecksInProgress.add(validator);
185 | } catch (RemoteException e) {
186 | Log.w(TAG, "RemoteException in checkLicense call.", e);
187 | handleServiceConnectionError(validator);
188 | }
189 | }
190 | }
191 |
192 | private synchronized void finishCheck(LicenseValidator validator) {
193 | mChecksInProgress.remove(validator);
194 | if (mChecksInProgress.isEmpty()) {
195 | cleanupService();
196 | }
197 | }
198 |
199 | private class ResultListener extends ILicenseResultListener.Stub {
200 | private final LicenseValidator mValidator;
201 | private Runnable mOnTimeout;
202 |
203 | public ResultListener(LicenseValidator validator) {
204 | mValidator = validator;
205 | mOnTimeout = new Runnable() {
206 | public void run() {
207 | Log.i(TAG, "Check timed out.");
208 | handleServiceConnectionError(mValidator);
209 | finishCheck(mValidator);
210 | }
211 | };
212 | startTimeout();
213 | }
214 |
215 | private static final int ERROR_CONTACTING_SERVER = 0x101;
216 | private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
217 | private static final int ERROR_NON_MATCHING_UID = 0x103;
218 |
219 | // Runs in IPC thread pool. Post it to the Handler, so we can guarantee
220 | // either this or the timeout runs.
221 | public void verifyLicense(final int responseCode, final String signedData,
222 | final String signature) {
223 | mHandler.post(new Runnable() {
224 | public void run() {
225 | Log.i(TAG, "Received response.");
226 | // Make sure it hasn't already timed out.
227 | if (mChecksInProgress.contains(mValidator)) {
228 | clearTimeout();
229 | mValidator.verify(mPublicKey, responseCode, signedData, signature);
230 | finishCheck(mValidator);
231 | }
232 | if (DEBUG_LICENSE_ERROR) {
233 | boolean logResponse;
234 | String stringError = null;
235 | switch (responseCode) {
236 | case ERROR_CONTACTING_SERVER:
237 | logResponse = true;
238 | stringError = "ERROR_CONTACTING_SERVER";
239 | break;
240 | case ERROR_INVALID_PACKAGE_NAME:
241 | logResponse = true;
242 | stringError = "ERROR_INVALID_PACKAGE_NAME";
243 | break;
244 | case ERROR_NON_MATCHING_UID:
245 | logResponse = true;
246 | stringError = "ERROR_NON_MATCHING_UID";
247 | break;
248 | default:
249 | logResponse = false;
250 | }
251 |
252 | if (logResponse) {
253 | String android_id = Secure.getString(mContext.getContentResolver(),
254 | Secure.ANDROID_ID);
255 | Date date = new Date();
256 | Log.d(TAG, "Server Failure: " + stringError);
257 | Log.d(TAG, "Android ID: " + android_id);
258 | Log.d(TAG, "Time: " + date.toGMTString());
259 | }
260 | }
261 |
262 | }
263 | });
264 | }
265 |
266 | private void startTimeout() {
267 | Log.i(TAG, "Start monitoring timeout.");
268 | mHandler.postDelayed(mOnTimeout, TIMEOUT_MS);
269 | }
270 |
271 | private void clearTimeout() {
272 | Log.i(TAG, "Clearing timeout.");
273 | mHandler.removeCallbacks(mOnTimeout);
274 | }
275 | }
276 |
277 | public synchronized void onServiceConnected(ComponentName name, IBinder service) {
278 | mService = ILicensingService.Stub.asInterface(service);
279 | runChecks();
280 | }
281 |
282 | public synchronized void onServiceDisconnected(ComponentName name) {
283 | // Called when the connection with the service has been
284 | // unexpectedly disconnected. That is, Market crashed.
285 | // If there are any checks in progress, the timeouts will handle them.
286 | Log.w(TAG, "Service unexpectedly disconnected.");
287 | mService = null;
288 | }
289 |
290 | /**
291 | * Generates policy response for service connection errors, as a result of
292 | * disconnections or timeouts.
293 | */
294 | private synchronized void handleServiceConnectionError(LicenseValidator validator) {
295 | mPolicy.processServerResponse(Policy.RETRY, null);
296 |
297 | if (mPolicy.allowAccess()) {
298 | validator.getCallback().allow(Policy.RETRY);
299 | } else {
300 | validator.getCallback().dontAllow(Policy.RETRY);
301 | }
302 | }
303 |
304 | /** Unbinds service if necessary and removes reference to it. */
305 | private void cleanupService() {
306 | if (mService != null) {
307 | try {
308 | mContext.unbindService(this);
309 | } catch (IllegalArgumentException e) {
310 | // Somehow we've already been unbound. This is a non-fatal
311 | // error.
312 | Log.e(TAG, "Unable to unbind from licensing service (already unbound)");
313 | }
314 | mService = null;
315 | }
316 | }
317 |
318 | /**
319 | * Inform the library that the context is about to be destroyed, so that any
320 | * open connections can be cleaned up.
321 | *
322 | * Failure to call this method can result in a crash under certain
323 | * circumstances, such as during screen rotation if an Activity requests the
324 | * license check or when the user exits the application.
325 | */
326 | public synchronized void onDestroy() {
327 | cleanupService();
328 | mHandler.getLooper().quit();
329 | }
330 |
331 | /** Generates a nonce (number used once). */
332 | private int generateNonce() {
333 | return RANDOM.nextInt();
334 | }
335 |
336 | /**
337 | * Get version code for the application package name.
338 | *
339 | * @param context
340 | * @param packageName application package name
341 | * @return the version code or empty string if package not found
342 | */
343 | private static String getVersionCode(Context context, String packageName) {
344 | try {
345 | return String.valueOf(context.getPackageManager().getPackageInfo(packageName, 0).
346 | versionCode);
347 | } catch (NameNotFoundException e) {
348 | Log.e(TAG, "Package not found. could not get version code.");
349 | return "";
350 | }
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | /**
20 | * Callback for the license checker library.
21 | *
22 | * Upon checking with the Market server and conferring with the {@link Policy},
23 | * the library calls the appropriate callback method to communicate the result.
24 | *
25 | * The callback does not occur in the original checking thread. Your
26 | * application should post to the appropriate handling thread or lock
27 | * accordingly.
28 | *
29 | * The reason that is passed back with allow/dontAllow is the base status handed
30 | * to the policy for allowed/disallowing the license. Policy.RETRY will call
31 | * allow or dontAllow depending on other statistics associated with the policy,
32 | * while in most cases Policy.NOT_LICENSED will call dontAllow and
33 | * Policy.LICENSED will Allow.
34 | */
35 | public interface LicenseCheckerCallback {
36 |
37 | /**
38 | * Allow use. App should proceed as normal.
39 | *
40 | * @param reason Policy.LICENSED or Policy.RETRY typically. (although in
41 | * theory the policy can return Policy.NOT_LICENSED here as well)
42 | */
43 | public void allow(int reason);
44 |
45 | /**
46 | * Don't allow use. App should inform user and take appropriate action.
47 | *
48 | * @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory
49 | * the policy can return Policy.LICENSED here as well ---
50 | * perhaps the call to the LVL took too long, for example)
51 | */
52 | public void dontAllow(int reason);
53 |
54 | /** Application error codes. */
55 | public static final int ERROR_INVALID_PACKAGE_NAME = 1;
56 | public static final int ERROR_NON_MATCHING_UID = 2;
57 | public static final int ERROR_NOT_MARKET_MANAGED = 3;
58 | public static final int ERROR_CHECK_IN_PROGRESS = 4;
59 | public static final int ERROR_INVALID_PUBLIC_KEY = 5;
60 | public static final int ERROR_MISSING_PERMISSION = 6;
61 |
62 | /**
63 | * Error in application code. Caller did not call or set up license checker
64 | * correctly. Should be considered fatal.
65 | */
66 | public void applicationError(int errorCode);
67 | }
68 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | import com.google.android.vending.licensing.util.Base64;
20 | import com.google.android.vending.licensing.util.Base64DecoderException;
21 |
22 | import android.text.TextUtils;
23 | import android.util.Log;
24 |
25 | import java.security.InvalidKeyException;
26 | import java.security.NoSuchAlgorithmException;
27 | import java.security.PublicKey;
28 | import java.security.Signature;
29 | import java.security.SignatureException;
30 |
31 | /**
32 | * Contains data related to a licensing request and methods to verify
33 | * and process the response.
34 | */
35 | class LicenseValidator {
36 | private static final String TAG = "LicenseValidator";
37 |
38 | // Server response codes.
39 | private static final int LICENSED = 0x0;
40 | private static final int NOT_LICENSED = 0x1;
41 | private static final int LICENSED_OLD_KEY = 0x2;
42 | private static final int ERROR_NOT_MARKET_MANAGED = 0x3;
43 | private static final int ERROR_SERVER_FAILURE = 0x4;
44 | private static final int ERROR_OVER_QUOTA = 0x5;
45 |
46 | private static final int ERROR_CONTACTING_SERVER = 0x101;
47 | private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
48 | private static final int ERROR_NON_MATCHING_UID = 0x103;
49 |
50 | private final Policy mPolicy;
51 | private final LicenseCheckerCallback mCallback;
52 | private final int mNonce;
53 | private final String mPackageName;
54 | private final String mVersionCode;
55 | private final DeviceLimiter mDeviceLimiter;
56 |
57 | LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback,
58 | int nonce, String packageName, String versionCode) {
59 | mPolicy = policy;
60 | mDeviceLimiter = deviceLimiter;
61 | mCallback = callback;
62 | mNonce = nonce;
63 | mPackageName = packageName;
64 | mVersionCode = versionCode;
65 | }
66 |
67 | public LicenseCheckerCallback getCallback() {
68 | return mCallback;
69 | }
70 |
71 | public int getNonce() {
72 | return mNonce;
73 | }
74 |
75 | public String getPackageName() {
76 | return mPackageName;
77 | }
78 |
79 | private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
80 |
81 | /**
82 | * Verifies the response from server and calls appropriate callback method.
83 | *
84 | * @param publicKey public key associated with the developer account
85 | * @param responseCode server response code
86 | * @param signedData signed data from server
87 | * @param signature server signature
88 | */
89 | public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) {
90 | String userId = null;
91 | // Skip signature check for unsuccessful requests
92 | ResponseData data = null;
93 | if (responseCode == LICENSED || responseCode == NOT_LICENSED ||
94 | responseCode == LICENSED_OLD_KEY) {
95 | // Verify signature.
96 | try {
97 | Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
98 | sig.initVerify(publicKey);
99 | sig.update(signedData.getBytes());
100 |
101 | if (!sig.verify(Base64.decode(signature))) {
102 | Log.e(TAG, "Signature verification failed.");
103 | handleInvalidResponse();
104 | return;
105 | }
106 | } catch (NoSuchAlgorithmException e) {
107 | // This can't happen on an Android compatible device.
108 | throw new RuntimeException(e);
109 | } catch (InvalidKeyException e) {
110 | handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY);
111 | return;
112 | } catch (SignatureException e) {
113 | throw new RuntimeException(e);
114 | } catch (Base64DecoderException e) {
115 | Log.e(TAG, "Could not Base64-decode signature.");
116 | handleInvalidResponse();
117 | return;
118 | }
119 |
120 | // Parse and validate response.
121 | try {
122 | data = ResponseData.parse(signedData);
123 | } catch (IllegalArgumentException e) {
124 | Log.e(TAG, "Could not parse response.");
125 | handleInvalidResponse();
126 | return;
127 | }
128 |
129 | if (data.responseCode != responseCode) {
130 | Log.e(TAG, "Response codes don't match.");
131 | handleInvalidResponse();
132 | return;
133 | }
134 |
135 | if (data.nonce != mNonce) {
136 | Log.e(TAG, "Nonce doesn't match.");
137 | handleInvalidResponse();
138 | return;
139 | }
140 |
141 | if (!data.packageName.equals(mPackageName)) {
142 | Log.e(TAG, "Package name doesn't match.");
143 | handleInvalidResponse();
144 | return;
145 | }
146 |
147 | if (!data.versionCode.equals(mVersionCode)) {
148 | Log.e(TAG, "Version codes don't match.");
149 | handleInvalidResponse();
150 | return;
151 | }
152 |
153 | // Application-specific user identifier.
154 | userId = data.userId;
155 | if (TextUtils.isEmpty(userId)) {
156 | Log.e(TAG, "User identifier is empty.");
157 | handleInvalidResponse();
158 | return;
159 | }
160 | }
161 |
162 | switch (responseCode) {
163 | case LICENSED:
164 | case LICENSED_OLD_KEY:
165 | int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId);
166 | handleResponse(limiterResponse, data);
167 | break;
168 | case NOT_LICENSED:
169 | handleResponse(Policy.NOT_LICENSED, data);
170 | break;
171 | case ERROR_CONTACTING_SERVER:
172 | Log.w(TAG, "Error contacting licensing server.");
173 | handleResponse(Policy.RETRY, data);
174 | break;
175 | case ERROR_SERVER_FAILURE:
176 | Log.w(TAG, "An error has occurred on the licensing server.");
177 | handleResponse(Policy.RETRY, data);
178 | break;
179 | case ERROR_OVER_QUOTA:
180 | Log.w(TAG, "Licensing server is refusing to talk to this device, over quota.");
181 | handleResponse(Policy.RETRY, data);
182 | break;
183 | case ERROR_INVALID_PACKAGE_NAME:
184 | handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME);
185 | break;
186 | case ERROR_NON_MATCHING_UID:
187 | handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID);
188 | break;
189 | case ERROR_NOT_MARKET_MANAGED:
190 | handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED);
191 | break;
192 | default:
193 | Log.e(TAG, "Unknown response code for license check.");
194 | handleInvalidResponse();
195 | }
196 | }
197 |
198 | /**
199 | * Confers with policy and calls appropriate callback method.
200 | *
201 | * @param response
202 | * @param rawData
203 | */
204 | private void handleResponse(int response, ResponseData rawData) {
205 | // Update policy data and increment retry counter (if needed)
206 | mPolicy.processServerResponse(response, rawData);
207 |
208 | // Given everything we know, including cached data, ask the policy if we should grant
209 | // access.
210 | if (mPolicy.allowAccess()) {
211 | mCallback.allow(response);
212 | } else {
213 | mCallback.dontAllow(response);
214 | }
215 | }
216 |
217 | private void handleApplicationError(int code) {
218 | mCallback.applicationError(code);
219 | }
220 |
221 | private void handleInvalidResponse() {
222 | mCallback.dontAllow(Policy.NOT_LICENSED);
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | /**
20 | * A DeviceLimiter that doesn't limit the number of devices that can use a
21 | * given user's license.
22 | *
23 | * Unless you have reason to believe that your application is being pirated
24 | * by multiple users using the same license (signing in to Market as the same
25 | * user), we recommend you use this implementation.
26 | */
27 | public class NullDeviceLimiter implements DeviceLimiter {
28 |
29 | public int isDeviceAllowed(String userId) {
30 | return Policy.LICENSED;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | /**
20 | * Interface used as part of a {@link Policy} to allow application authors to obfuscate
21 | * licensing data that will be stored into a SharedPreferences file.
22 | *
23 | * Any transformation scheme must be reversable. Implementing classes may optionally implement an
24 | * integrity check to further prevent modification to preference data. Implementing classes
25 | * should use device-specific information as a key in the obfuscation algorithm to prevent
26 | * obfuscated preferences from being shared among devices.
27 | */
28 | public interface Obfuscator {
29 |
30 | /**
31 | * Obfuscate a string that is being stored into shared preferences.
32 | *
33 | * @param original The data that is to be obfuscated.
34 | * @param key The key for the data that is to be obfuscated.
35 | * @return A transformed version of the original data.
36 | */
37 | String obfuscate(String original, String key);
38 |
39 | /**
40 | * Undo the transformation applied to data by the obfuscate() method.
41 | *
42 | * @param original The data that is to be obfuscated.
43 | * @param key The key for the data that is to be obfuscated.
44 | * @return A transformed version of the original data.
45 | * @throws ValidationException Optionally thrown if a data integrity check fails.
46 | */
47 | String unobfuscate(String obfuscated, String key) throws ValidationException;
48 | }
49 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/Policy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | /**
20 | * Policy used by {@link LicenseChecker} to determine whether a user should have
21 | * access to the application.
22 | */
23 | public interface Policy {
24 |
25 | /**
26 | * Change these values to make it more difficult for tools to automatically
27 | * strip LVL protection from your APK.
28 | */
29 |
30 | /**
31 | * LICENSED means that the server returned back a valid license response
32 | */
33 | public static final int LICENSED = 0x0100;
34 | /**
35 | * NOT_LICENSED means that the server returned back a valid license response
36 | * that indicated that the user definitively is not licensed
37 | */
38 | public static final int NOT_LICENSED = 0x0231;
39 | /**
40 | * RETRY means that the license response was unable to be determined ---
41 | * perhaps as a result of faulty networking
42 | */
43 | public static final int RETRY = 0x0123;
44 |
45 | /**
46 | * Provide results from contact with the license server. Retry counts are
47 | * incremented if the current value of response is RETRY. Results will be
48 | * used for any future policy decisions.
49 | *
50 | * @param response the result from validating the server response
51 | * @param rawData the raw server response data, can be null for RETRY
52 | */
53 | void processServerResponse(int response, ResponseData rawData);
54 |
55 | /**
56 | * Check if the user should be allowed access to the application.
57 | */
58 | boolean allowAccess();
59 | }
60 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | import android.content.SharedPreferences;
20 | import android.util.Log;
21 |
22 | /**
23 | * An wrapper for SharedPreferences that transparently performs data obfuscation.
24 | */
25 | public class PreferenceObfuscator {
26 |
27 | private static final String TAG = "PreferenceObfuscator";
28 |
29 | private final SharedPreferences mPreferences;
30 | private final Obfuscator mObfuscator;
31 | private SharedPreferences.Editor mEditor;
32 |
33 | /**
34 | * Constructor.
35 | *
36 | * @param sp A SharedPreferences instance provided by the system.
37 | * @param o The Obfuscator to use when reading or writing data.
38 | */
39 | public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) {
40 | mPreferences = sp;
41 | mObfuscator = o;
42 | mEditor = null;
43 | }
44 |
45 | public void putString(String key, String value) {
46 | if (mEditor == null) {
47 | mEditor = mPreferences.edit();
48 | }
49 | String obfuscatedValue = mObfuscator.obfuscate(value, key);
50 | mEditor.putString(key, obfuscatedValue);
51 | }
52 |
53 | public String getString(String key, String defValue) {
54 | String result;
55 | String value = mPreferences.getString(key, null);
56 | if (value != null) {
57 | try {
58 | result = mObfuscator.unobfuscate(value, key);
59 | } catch (ValidationException e) {
60 | // Unable to unobfuscate, data corrupt or tampered
61 | Log.w(TAG, "Validation error while reading preference: " + key);
62 | result = defValue;
63 | }
64 | } else {
65 | // Preference not found
66 | result = defValue;
67 | }
68 | return result;
69 | }
70 |
71 | public void commit() {
72 | if (mEditor != null) {
73 | mEditor.commit();
74 | mEditor = null;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/ResponseData.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | import java.util.regex.Pattern;
20 |
21 | import android.text.TextUtils;
22 |
23 | /**
24 | * ResponseData from licensing server.
25 | */
26 | public class ResponseData {
27 |
28 | public int responseCode;
29 | public int nonce;
30 | public String packageName;
31 | public String versionCode;
32 | public String userId;
33 | public long timestamp;
34 | /** Response-specific data. */
35 | public String extra;
36 |
37 | /**
38 | * Parses response string into ResponseData.
39 | *
40 | * @param responseData response data string
41 | * @throws IllegalArgumentException upon parsing error
42 | * @return ResponseData object
43 | */
44 | public static ResponseData parse(String responseData) {
45 | // Must parse out main response data and response-specific data.
46 | int index = responseData.indexOf(':');
47 | String mainData, extraData;
48 | if ( -1 == index ) {
49 | mainData = responseData;
50 | extraData = "";
51 | } else {
52 | mainData = responseData.substring(0, index);
53 | extraData = index >= responseData.length() ? "" : responseData.substring(index+1);
54 | }
55 |
56 | String [] fields = TextUtils.split(mainData, Pattern.quote("|"));
57 | if (fields.length < 6) {
58 | throw new IllegalArgumentException("Wrong number of fields.");
59 | }
60 |
61 | ResponseData data = new ResponseData();
62 | data.extra = extraData;
63 | data.responseCode = Integer.parseInt(fields[0]);
64 | data.nonce = Integer.parseInt(fields[1]);
65 | data.packageName = fields[2];
66 | data.versionCode = fields[3];
67 | // Application-specific user identifier.
68 | data.userId = fields[4];
69 | data.timestamp = Long.parseLong(fields[5]);
70 |
71 | return data;
72 | }
73 |
74 | @Override
75 | public String toString() {
76 | return TextUtils.join("|", new Object [] { responseCode, nonce, packageName, versionCode,
77 | userId, timestamp });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | import org.apache.http.NameValuePair;
20 | import org.apache.http.client.utils.URLEncodedUtils;
21 |
22 | import java.net.URI;
23 | import java.net.URISyntaxException;
24 | import java.util.HashMap;
25 | import java.util.List;
26 | import java.util.Map;
27 |
28 | import android.content.Context;
29 | import android.content.SharedPreferences;
30 | import android.util.Log;
31 |
32 | /**
33 | * Default policy. All policy decisions are based off of response data received
34 | * from the licensing service. Specifically, the licensing server sends the
35 | * following information: response validity period, error retry period, and
36 | * error retry count.
37 | *
38 | * These values will vary based on the the way the application is configured in
39 | * the Android Market publishing console, such as whether the application is
40 | * marked as free or is within its refund period, as well as how often an
41 | * application is checking with the licensing service.
42 | *
43 | * Developers who need more fine grained control over their application's
44 | * licensing policy should implement a custom Policy.
45 | */
46 | public class ServerManagedPolicy implements Policy {
47 |
48 | private static final String TAG = "ServerManagedPolicy";
49 | private static final String PREFS_FILE = "com.android.vending.licensing.ServerManagedPolicy";
50 | private static final String PREF_LAST_RESPONSE = "lastResponse";
51 | private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp";
52 | private static final String PREF_RETRY_UNTIL = "retryUntil";
53 | private static final String PREF_MAX_RETRIES = "maxRetries";
54 | private static final String PREF_RETRY_COUNT = "retryCount";
55 | private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
56 | private static final String DEFAULT_RETRY_UNTIL = "0";
57 | private static final String DEFAULT_MAX_RETRIES = "0";
58 | private static final String DEFAULT_RETRY_COUNT = "0";
59 |
60 | private static final long MILLIS_PER_MINUTE = 60 * 1000;
61 |
62 | private long mValidityTimestamp;
63 | private long mRetryUntil;
64 | private long mMaxRetries;
65 | private long mRetryCount;
66 | private long mLastResponseTime = 0;
67 | private int mLastResponse;
68 | private PreferenceObfuscator mPreferences;
69 |
70 | /**
71 | * @param context The context for the current application
72 | * @param obfuscator An obfuscator to be used with preferences.
73 | */
74 | public ServerManagedPolicy(Context context, Obfuscator obfuscator) {
75 | // Import old values
76 | SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
77 | mPreferences = new PreferenceObfuscator(sp, obfuscator);
78 | mLastResponse = Integer.parseInt(
79 | mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)));
80 | mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP,
81 | DEFAULT_VALIDITY_TIMESTAMP));
82 | mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
83 | mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
84 | mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
85 | }
86 |
87 | /**
88 | * Process a new response from the license server.
89 | *
90 | * This data will be used for computing future policy decisions. The
91 | * following parameters are processed:
92 | *
23 | * Using a non-caching policy ensures that there is no local preference data
24 | * for malicious users to tamper with. As a side effect, applications
25 | * will not be permitted to run while offline. Developers should carefully
26 | * weigh the risks of using this Policy over one which implements caching,
27 | * such as ServerManagedPolicy.
28 | *
29 | * Access to the application is only allowed if a LICESNED response is.
30 | * received. All other responses (including RETRY) will deny access.
31 | */
32 | public class StrictPolicy implements Policy {
33 |
34 | private int mLastResponse;
35 |
36 | public StrictPolicy() {
37 | // Set default policy. This will force the application to check the policy on launch.
38 | mLastResponse = Policy.RETRY;
39 | }
40 |
41 | /**
42 | * Process a new response from the license server. Since we aren't
43 | * performing any caching, this equates to reading the LicenseResponse.
44 | * Any ResponseData provided is ignored.
45 | *
46 | * @param response the result from validating the server response
47 | * @param rawData the raw server response data
48 | */
49 | public void processServerResponse(int response, ResponseData rawData) {
50 | mLastResponse = response;
51 | }
52 |
53 | /**
54 | * {@inheritDoc}
55 | *
56 | * This implementation allows access if and only if a LICENSED response
57 | * was received the last time the server was contacted.
58 | */
59 | public boolean allowAccess() {
60 | return (mLastResponse == Policy.LICENSED);
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/ValidationException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.android.vending.licensing;
18 |
19 | /**
20 | * Indicates that an error occurred while validating the integrity of data managed by an
21 | * {@link Obfuscator}.}
22 | */
23 | public class ValidationException extends Exception {
24 | public ValidationException() {
25 | super();
26 | }
27 |
28 | public ValidationException(String s) {
29 | super(s);
30 | }
31 |
32 | private static final long serialVersionUID = 1L;
33 | }
34 |
--------------------------------------------------------------------------------
/dependency/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java:
--------------------------------------------------------------------------------
1 | // Copyright 2002, Google, Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package com.google.android.vending.licensing.util;
16 |
17 | /**
18 | * Exception thrown when encountering an invalid Base64 input character.
19 | *
20 | * @author nelson
21 | */
22 | public class Base64DecoderException extends Exception {
23 | public Base64DecoderException() {
24 | super();
25 | }
26 |
27 | public Base64DecoderException(String s) {
28 | super(s);
29 | }
30 |
31 | private static final long serialVersionUID = 1L;
32 | }
33 |
--------------------------------------------------------------------------------
/haxelib.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "android-expansion",
3 | "url": "",
4 | "license": "MIT",
5 | "tags": [],
6 | "description": "",
7 | "version": "1.0.0",
8 | "releasenote": "",
9 | "contributors": [""],
10 | "dependencies": {
11 | }
12 | }
--------------------------------------------------------------------------------
/include.xml:
--------------------------------------------------------------------------------
1 |
2 |
93 | *
98 | *
99 | * @param response the result from validating the server response
100 | * @param rawData the raw server response data
101 | */
102 | public void processServerResponse(int response, ResponseData rawData) {
103 |
104 | // Update retry counter
105 | if (response != Policy.RETRY) {
106 | setRetryCount(0);
107 | } else {
108 | setRetryCount(mRetryCount + 1);
109 | }
110 |
111 | if (response == Policy.LICENSED) {
112 | // Update server policy data
113 | Map
239 | *
240 | *
244 | */
245 | public boolean allowAccess() {
246 | long ts = System.currentTimeMillis();
247 | if (mLastResponse == Policy.LICENSED) {
248 | // Check if the LICENSED response occurred within the validity timeout.
249 | if (ts <= mValidityTimestamp) {
250 | // Cached LICENSED response is still valid.
251 | return true;
252 | }
253 | } else if (mLastResponse == Policy.RETRY &&
254 | ts < mLastResponseTime + MILLIS_PER_MINUTE) {
255 | // Only allow access if we are within the retry period or we haven't used up our
256 | // max retries.
257 | return (ts <= mRetryUntil || mRetryCount <= mMaxRetries);
258 | }
259 | return false;
260 | }
261 |
262 | private Map