├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── build.sh ├── dependency ├── android-expansion │ ├── AndroidManifest.xml │ ├── build.gradle │ ├── build.xml │ ├── project.properties │ └── src │ │ └── com │ │ └── thomasuster │ │ └── androidExpansion │ │ ├── DownloaderClientImpl.java │ │ ├── DownloaderReceiver.java │ │ ├── Expansion.java │ │ └── ExtensionDownloaderService.java ├── downloader_library │ ├── AndroidManifest.xml │ ├── build.gradle │ ├── build.xml │ ├── libs │ │ └── org.apache.http.legacy.jar │ ├── project.properties │ ├── res │ │ ├── drawable-hdpi │ │ │ └── notify_panel_notification_icon_bg.png │ │ ├── drawable-mdpi │ │ │ └── notify_panel_notification_icon_bg.png │ │ ├── layout │ │ │ └── status_bar_ongoing_event_progress_bar.xml │ │ ├── values-v11 │ │ │ └── styles.xml │ │ ├── values-v9 │ │ │ └── styles.xml │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── src │ │ └── com │ │ └── google │ │ └── android │ │ └── vending │ │ └── expansion │ │ └── downloader │ │ ├── Constants.java │ │ ├── DownloadProgressInfo.java │ │ ├── DownloaderClientMarshaller.java │ │ ├── DownloaderServiceMarshaller.java │ │ ├── Helpers.java │ │ ├── IDownloaderClient.java │ │ ├── IDownloaderService.java │ │ ├── IStub.java │ │ ├── SystemFacade.java │ │ └── impl │ │ ├── AndroidHttpClient.java │ │ ├── CustomIntentService.java │ │ ├── CustomNotificationFactory.java │ │ ├── DownloadInfo.java │ │ ├── DownloadNotification.java │ │ ├── DownloadThread.java │ │ ├── DownloaderService.java │ │ ├── DownloadsDB.java │ │ ├── HttpDateTime.java │ │ ├── V14CustomNotification.java │ │ └── V3CustomNotification.java └── play_licensing │ ├── AndroidManifest.xml │ ├── aidl │ ├── ILicenseResultListener.aidl │ └── ILicensingService.aidl │ ├── build.gradle │ ├── build.xml │ ├── libs │ └── org.apache.http.legacy.jar │ ├── project.properties │ └── src │ └── com │ └── google │ └── android │ └── vending │ └── licensing │ ├── AESObfuscator.java │ ├── APKExpansionPolicy.java │ ├── DeviceLimiter.java │ ├── ILicenseResultListener.java │ ├── ILicensingService.java │ ├── LicenseChecker.java │ ├── LicenseCheckerCallback.java │ ├── LicenseValidator.java │ ├── NullDeviceLimiter.java │ ├── Obfuscator.java │ ├── Policy.java │ ├── PreferenceObfuscator.java │ ├── ResponseData.java │ ├── ServerManagedPolicy.java │ ├── StrictPolicy.java │ ├── ValidationException.java │ └── util │ ├── Base64.java │ └── Base64DecoderException.java ├── haxelib.json ├── include.xml ├── run.n ├── samples └── DisplayingABitmap │ ├── Assets │ ├── nme.png │ └── nme.svg │ ├── Source │ └── Main.hx │ ├── main-expansion │ └── nme.png │ └── project.nmml ├── src └── extension │ └── androidExpansion │ ├── AndroidExpansion.hx │ └── ExpansionReader.hx ├── test.sh └── tools ├── run ├── RunMain.hx └── compile.hxml ├── src └── com │ └── thomasuster │ └── expansion │ ├── ExpansionTool.hx │ ├── RealSys.hx │ └── SysProxy.hx └── test ├── TestMain.hx ├── TestTool.hx ├── com └── thomasuster │ └── expansion │ ├── Command.hx │ └── MockSys.hx └── compile.hxml /.gitignore: -------------------------------------------------------------------------------- 1 | project/obj/* 2 | ndll/* 3 | lib/ 4 | tools/bin 5 | *.obb 6 | samples/DisplayingABitmap/Export/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | install: 4 | - brew install neko haxe 5 | - cd .. 6 | - mkdir haxelib 7 | - haxelib setup haxelib 8 | - haxelib install hxcpp 3.4.64 > /dev/null 9 | before_script: 10 | - cd $TRAVIS_BUILD_DIR 11 | script: 12 | - sh test.sh -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Thomas Uster 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NME Extension for Android Expansion files 2 | 3 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE.md) 4 | [![TravisCI Build Status](https://travis-ci.org/thomasuster/android-expansion.svg?branch=master)](https://travis-ci.org/thomasuster/android-expansion ) 5 | 6 | Assumes one main obb file and no patch obb file. 7 | 8 | ``` 9 | haxelib dev android-expansion android-expansion 10 | cd android-expansion 11 | cd samples/DisplayingABitmap/ 12 | haxelib run nme test android -gradle 13 | ``` 14 | 15 | You should see an orange NME logo. Now close the app. For testing we're going to use 'android-expansion push' which assumes you have Android 6.0 on your phone, go ahead and update the OS if you're under 6.0. 16 | 17 | ``` 18 | haxelib run android-expansion zip io.nme.samples.displayingabitmap 181 main-expansion 19 | haxelib run android-expansion push io.nme.samples.displayingabitmap 181 20 | ``` 21 | 22 | Open the app, you should now see a Pink NME logo. 23 | Now look at the sample to see how it works. 24 | 25 | ``` 26 | open project.nmml 27 | open Source/Main.hx 28 | ``` 29 | 30 | --- 31 | 32 | Test to make sure your app can launch without the obb file. You can remove the obb file using adb shell. 33 | ``` 34 | adb shell 35 | cd /mnt/shell/emulated/obb 36 | ls 37 | cd io.nme.samples.displayingabitmap 38 | rm main.180.io.nme.samples.displayingabitmap.obb 39 | ``` 40 | Once your APK and OBB file are ready through Google Play you should also test that your app properly 41 | downloads the obb file when it has been removed. -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | cd tools/run 2 | haxe compile.hxml 3 | cd ../.. -------------------------------------------------------------------------------- /dependency/android-expansion/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dependency/android-expansion/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android-library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | defaultConfig { 7 | targetSdkVersion ::ANDROID_TARGET_SDK_VERSION:: 8 | } 9 | 10 | useLibrary 'org.apache.http.legacy' 11 | 12 | sourceSets { 13 | main { 14 | manifest.srcFile 'AndroidManifest.xml' 15 | java.srcDirs = ['src'] 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation project(':extension-api') 22 | implementation project(':downloader_library') 23 | } -------------------------------------------------------------------------------- /dependency/android-expansion/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dependency/android-expansion/project.properties: -------------------------------------------------------------------------------- 1 | android.library=true 2 | target=android-::ANDROID_TARGET_SDK_VERSION:: 3 | android.library.reference.1=../extension-api 4 | android.library.reference.2=../downloader_library 5 | android.library.reference.3=../play_licensing -------------------------------------------------------------------------------- /dependency/android-expansion/src/com/thomasuster/androidExpansion/DownloaderClientImpl.java: -------------------------------------------------------------------------------- 1 | package com.thomasuster.androidExpansion; 2 | 3 | import com.google.android.vending.expansion.downloader.IDownloaderClient; 4 | import android.os.Messenger; 5 | import com.google.android.vending.expansion.downloader.DownloadProgressInfo; 6 | 7 | public class DownloaderClientImpl implements IDownloaderClient { 8 | 9 | public DownloadProgressInfo progress; 10 | 11 | public DownloaderClientImpl() {} 12 | 13 | public void onServiceConnected(Messenger m) { 14 | // mRemoteService = DownloaderServiceMarshaller.CreateProxy(m); 15 | // mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); 16 | } 17 | 18 | public void onDownloadStateChanged(int newState) { 19 | 20 | } 21 | 22 | public void onDownloadProgress(DownloadProgressInfo progress) { 23 | this.progress = progress; 24 | } 25 | } -------------------------------------------------------------------------------- /dependency/android-expansion/src/com/thomasuster/androidExpansion/DownloaderReceiver.java: -------------------------------------------------------------------------------- 1 | package com.thomasuster.androidExpansion; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.database.Cursor; 8 | import android.database.sqlite.SQLiteDatabase; 9 | import android.os.Bundle; 10 | import org.haxe.extension.Extension; 11 | import java.util.Map; 12 | import android.content.BroadcastReceiver; 13 | import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; 14 | import android.content.pm.PackageManager.NameNotFoundException; 15 | import java.util.Calendar; 16 | 17 | public class DownloaderReceiver extends BroadcastReceiver { 18 | @Override 19 | public void onReceive(Context context, Intent intent) { 20 | try { 21 | DownloaderClientMarshaller.startDownloadServiceIfRequired(context, 22 | intent, ExtensionDownloaderService.class); 23 | } catch (NameNotFoundException e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /dependency/android-expansion/src/com/thomasuster/androidExpansion/Expansion.java: -------------------------------------------------------------------------------- 1 | package com.thomasuster.androidExpansion; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import org.haxe.extension.Extension; 7 | import android.util.Log; 8 | import java.io.File; 9 | import android.os.Environment; 10 | import android.content.Context; 11 | import java.util.Vector; 12 | import com.google.android.vending.expansion.downloader.Helpers; 13 | import com.google.android.vending.expansion.downloader.IStub; 14 | import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; 15 | import android.content.pm.PackageInfo; 16 | import android.content.pm.PackageManager; 17 | 18 | public class Expansion extends Extension { 19 | 20 | public static String BASE64_PUBLIC_KEY; 21 | public static byte[] SALT; 22 | 23 | private static IStub mDownloaderClientStub; 24 | private static DownloaderClientImpl downloaderClient; 25 | private static int version; 26 | private static long bytes; 27 | 28 | public static void init() {} 29 | 30 | public static void setKey(String v) { 31 | BASE64_PUBLIC_KEY = v; 32 | } 33 | 34 | public static void setSalt(byte[] v) { 35 | SALT = v; 36 | } 37 | 38 | public static void setVersion(int v) { 39 | version = v; 40 | } 41 | 42 | public static int getAPKVersion() { 43 | try { 44 | PackageInfo info = mainContext.getPackageManager().getPackageInfo(getPackageName(), 0); 45 | return info.versionCode; 46 | } catch (PackageManager.NameNotFoundException e) { 47 | e.printStackTrace(); 48 | } 49 | return 0; 50 | } 51 | 52 | public static void setBytes(long v) { 53 | bytes = v; 54 | } 55 | 56 | public static int expansionFilesDelivered() { 57 | String fileName = Helpers.getExpansionAPKFileName(mainContext, true,version); 58 | System.out.println("Checking " + fileName); 59 | if (Helpers.doesFileExist(mainContext, fileName, bytes, false)) 60 | return 1; 61 | return 0; 62 | } 63 | 64 | public static int startDownloadServiceIfRequired() { 65 | if(expansionFilesDelivered() == 1) 66 | return 0; 67 | // Build an Intent to start this activity from the Notification 68 | Intent notifierIntent = new Intent(mainContext, mainActivity.getClass()); 69 | notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 70 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 71 | 72 | PendingIntent pendingIntent = PendingIntent.getActivity(mainContext, 0, 73 | notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); 74 | int startResult = 0; 75 | try { 76 | startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(mainContext, 77 | pendingIntent, ExtensionDownloaderService.class); 78 | if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { 79 | Extension.mainActivity.runOnUiThread(new Runnable() { 80 | public void run() { 81 | downloaderClient = new DownloaderClientImpl(); 82 | mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(downloaderClient, 83 | ExtensionDownloaderService.class); 84 | } 85 | }); 86 | } 87 | } 88 | catch (Exception e) { 89 | System.out.println("startDownloadServiceIfRequired error"); 90 | e.printStackTrace(); 91 | } 92 | return startResult; 93 | } 94 | 95 | @Override 96 | public void onResume() { 97 | System.out.println("onRESUME!!!!!!!"); 98 | if (mDownloaderClientStub != null) { 99 | mDownloaderClientStub.connect(mainContext); 100 | } 101 | } 102 | 103 | 104 | @Override 105 | public void onStop() { 106 | if (mDownloaderClientStub != null) { 107 | mDownloaderClientStub.disconnect(mainContext); 108 | } 109 | } 110 | 111 | public static String getMainFile() { 112 | return getAPKExpansionFiles(mainContext, version, 0)[0]; 113 | } 114 | 115 | private final static String EXP_PATH = "/Android/obb/"; 116 | 117 | static String[] getAPKExpansionFiles(Context ctx, int mainVersion, 118 | int patchVersion) { 119 | String packageName = ctx.getPackageName(); 120 | Vector ret = new Vector(); 121 | if (Environment.getExternalStorageState() 122 | .equals(Environment.MEDIA_MOUNTED)) { 123 | File root = Environment.getExternalStorageDirectory(); 124 | File expPath = new File(root.toString() + EXP_PATH + packageName); 125 | 126 | if (expPath.exists()) { 127 | if ( mainVersion > 0 ) { 128 | String strMainPath = expPath + File.separator + "main." + 129 | mainVersion + "." + packageName + ".obb"; 130 | File main = new File(strMainPath); 131 | if ( main.isFile() ) { 132 | ret.add(strMainPath); 133 | } 134 | } 135 | if ( patchVersion > 0 ) { 136 | String strPatchPath = expPath + File.separator + "patch." + 137 | mainVersion + "." + packageName + ".obb"; 138 | File main = new File(strPatchPath); 139 | if ( main.isFile() ) { 140 | ret.add(strPatchPath); 141 | } 142 | } 143 | } 144 | } 145 | String[] retArray = new String[ret.size()]; 146 | ret.toArray(retArray); 147 | return retArray; 148 | } 149 | 150 | public static String getPackageName () 151 | { 152 | return mainContext.getPackageName(); 153 | } 154 | 155 | public static String getLocalStoragePath () 156 | { 157 | return Environment.getExternalStorageDirectory().getAbsolutePath(); 158 | } 159 | 160 | public static long overallTotal() 161 | { 162 | if(downloaderClient == null) 163 | return 0; 164 | if(downloaderClient.progress == null) 165 | return 0; 166 | return downloaderClient.progress.mOverallTotal; 167 | } 168 | } -------------------------------------------------------------------------------- /dependency/android-expansion/src/com/thomasuster/androidExpansion/ExtensionDownloaderService.java: -------------------------------------------------------------------------------- 1 | package com.thomasuster.androidExpansion; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.database.Cursor; 8 | import android.database.sqlite.SQLiteDatabase; 9 | import android.os.Bundle; 10 | import org.haxe.extension.Extension; 11 | import java.util.Map; 12 | //import android.content.Service; 13 | import com.google.android.vending.expansion.downloader.impl.DownloaderService; 14 | 15 | import java.util.Calendar; 16 | 17 | public class ExtensionDownloaderService extends DownloaderService { 18 | 19 | @Override 20 | public String getPublicKey() { 21 | return Expansion.BASE64_PUBLIC_KEY; 22 | } 23 | 24 | @Override 25 | public byte[] getSALT() { 26 | return Expansion.SALT; 27 | } 28 | 29 | @Override 30 | public String getAlarmReceiverClassName() { 31 | return DownloaderReceiver.class.getName(); 32 | } 33 | } -------------------------------------------------------------------------------- /dependency/downloader_library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /dependency/downloader_library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 24 5 | useLibrary 'org.apache.http.legacy' 6 | 7 | defaultConfig { 8 | //applicationId "com.android.vending.expansion.downloader" 9 | minSdkVersion 10 10 | targetSdkVersion 25 11 | } 12 | buildTypes { 13 | release { 14 | //runProguard false 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 17 | } 18 | } 19 | productFlavors { 20 | } 21 | 22 | sourceSets { 23 | main { 24 | manifest.srcFile 'AndroidManifest.xml' 25 | java.srcDirs = ['src'] 26 | res.srcDirs = ['res'] 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation project(':play_licensing') 33 | } -------------------------------------------------------------------------------- /dependency/downloader_library/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dependency/downloader_library/libs/org.apache.http.legacy.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasuster/android-expansion/29711b95780df2b1bc4bf3a2a4387f3e67f0a954/dependency/downloader_library/libs/org.apache.http.legacy.jar -------------------------------------------------------------------------------- /dependency/downloader_library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | android.library=true 12 | target=android-::ANDROID_TARGET_SDK_VERSION:: 13 | android.library.reference.1=../play_licensing 14 | #android.library.reference.1=../extension-api 15 | -------------------------------------------------------------------------------- /dependency/downloader_library/res/drawable-hdpi/notify_panel_notification_icon_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasuster/android-expansion/29711b95780df2b1bc4bf3a2a4387f3e67f0a954/dependency/downloader_library/res/drawable-hdpi/notify_panel_notification_icon_bg.png -------------------------------------------------------------------------------- /dependency/downloader_library/res/drawable-mdpi/notify_panel_notification_icon_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasuster/android-expansion/29711b95780df2b1bc4bf3a2a4387f3e67f0a954/dependency/downloader_library/res/drawable-mdpi/notify_panel_notification_icon_bg.png -------------------------------------------------------------------------------- /dependency/downloader_library/res/layout/status_bar_ongoing_event_progress_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 24 | 25 | 30 | 31 | 39 | 40 | 50 | 51 | 52 | 61 | 62 | 69 | 70 | 77 | 78 | 79 | 84 | 85 | 91 | 92 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /dependency/downloader_library/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /dependency/downloader_library/res/values-v9/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 15 | 16 | 20 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/Constants.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 java.io.File; 20 | 21 | 22 | /** 23 | * Contains the internal constants that are used in the download manager. 24 | * As a general rule, modifying these constants should be done with care. 25 | */ 26 | public class Constants { 27 | /** Tag used for debugging/logging */ 28 | public static final String TAG = "LVLDL"; 29 | 30 | /** 31 | * Expansion path where we store obb files 32 | */ 33 | public static final String EXP_PATH = File.separator + "Android" 34 | + File.separator + "obb" + File.separator; 35 | 36 | /** The intent that gets sent when the service must wake up for a retry */ 37 | public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; 38 | 39 | /** the intent that gets sent when clicking a successful download */ 40 | public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; 41 | 42 | /** the intent that gets sent when clicking an incomplete/failed download */ 43 | public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; 44 | 45 | /** the intent that gets sent when deleting the notification of a completed download */ 46 | public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; 47 | 48 | /** 49 | * When a number has to be appended to the filename, this string is used to separate the 50 | * base filename from the sequence number 51 | */ 52 | public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; 53 | 54 | /** The default user agent used for downloads */ 55 | public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; 56 | 57 | /** The buffer size used to stream the data */ 58 | public static final int BUFFER_SIZE = 4096; 59 | 60 | /** The minimum amount of progress that has to be done before the progress bar gets updated */ 61 | public static final int MIN_PROGRESS_STEP = 4096; 62 | 63 | /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */ 64 | public static final long MIN_PROGRESS_TIME = 1000; 65 | 66 | /** The maximum number of rows in the database (FIFO) */ 67 | public static final int MAX_DOWNLOADS = 1000; 68 | 69 | /** 70 | * The number of times that the download manager will retry its network 71 | * operations when no progress is happening before it gives up. 72 | */ 73 | public static final int MAX_RETRIES = 5; 74 | 75 | /** 76 | * The minimum amount of time that the download manager accepts for 77 | * a Retry-After response header with a parameter in delta-seconds. 78 | */ 79 | public static final int MIN_RETRY_AFTER = 30; // 30s 80 | 81 | /** 82 | * The maximum amount of time that the download manager accepts for 83 | * a Retry-After response header with a parameter in delta-seconds. 84 | */ 85 | public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h 86 | 87 | /** 88 | * The maximum number of redirects. 89 | */ 90 | public static final int MAX_REDIRECTS = 5; // can't be more than 7. 91 | 92 | /** 93 | * The time between a failure and the first retry after an IOException. 94 | * Each subsequent retry grows exponentially, doubling each time. 95 | * The time is in seconds. 96 | */ 97 | public static final int RETRY_FIRST_DELAY = 30; 98 | 99 | /** Enable separate connectivity logging */ 100 | public static final boolean LOGX = true; 101 | 102 | /** Enable verbose logging */ 103 | public static final boolean LOGV = false; 104 | 105 | /** Enable super-verbose logging */ 106 | private static final boolean LOCAL_LOGVV = false; 107 | public static final boolean LOGVV = LOCAL_LOGVV && LOGV; 108 | 109 | /** 110 | * This download has successfully completed. 111 | * Warning: there might be other status values that indicate success 112 | * in the future. 113 | * Use isSucccess() to capture the entire category. 114 | */ 115 | public static final int STATUS_SUCCESS = 200; 116 | 117 | /** 118 | * This request couldn't be parsed. This is also used when processing 119 | * requests with unknown/unsupported URI schemes. 120 | */ 121 | public static final int STATUS_BAD_REQUEST = 400; 122 | 123 | /** 124 | * This download can't be performed because the content type cannot be 125 | * handled. 126 | */ 127 | public static final int STATUS_NOT_ACCEPTABLE = 406; 128 | 129 | /** 130 | * This download cannot be performed because the length cannot be 131 | * determined accurately. This is the code for the HTTP error "Length 132 | * Required", which is typically used when making requests that require 133 | * a content length but don't have one, and it is also used in the 134 | * client when a response is received whose length cannot be determined 135 | * accurately (therefore making it impossible to know when a download 136 | * completes). 137 | */ 138 | public static final int STATUS_LENGTH_REQUIRED = 411; 139 | 140 | /** 141 | * This download was interrupted and cannot be resumed. 142 | * This is the code for the HTTP error "Precondition Failed", and it is 143 | * also used in situations where the client doesn't have an ETag at all. 144 | */ 145 | public static final int STATUS_PRECONDITION_FAILED = 412; 146 | 147 | /** 148 | * The lowest-valued error status that is not an actual HTTP status code. 149 | */ 150 | public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; 151 | 152 | /** 153 | * The requested destination file already exists. 154 | */ 155 | public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; 156 | 157 | /** 158 | * Some possibly transient error occurred, but we can't resume the download. 159 | */ 160 | public static final int STATUS_CANNOT_RESUME = 489; 161 | 162 | /** 163 | * This download was canceled 164 | */ 165 | public static final int STATUS_CANCELED = 490; 166 | 167 | /** 168 | * This download has completed with an error. 169 | * Warning: there will be other status values that indicate errors in 170 | * the future. Use isStatusError() to capture the entire category. 171 | */ 172 | public static final int STATUS_UNKNOWN_ERROR = 491; 173 | 174 | /** 175 | * This download couldn't be completed because of a storage issue. 176 | * Typically, that's because the filesystem is missing or full. 177 | * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} 178 | * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. 179 | */ 180 | public static final int STATUS_FILE_ERROR = 492; 181 | 182 | /** 183 | * This download couldn't be completed because of an HTTP 184 | * redirect response that the download manager couldn't 185 | * handle. 186 | */ 187 | public static final int STATUS_UNHANDLED_REDIRECT = 493; 188 | 189 | /** 190 | * This download couldn't be completed because of an 191 | * unspecified unhandled HTTP code. 192 | */ 193 | public static final int STATUS_UNHANDLED_HTTP_CODE = 494; 194 | 195 | /** 196 | * This download couldn't be completed because of an 197 | * error receiving or processing data at the HTTP level. 198 | */ 199 | public static final int STATUS_HTTP_DATA_ERROR = 495; 200 | 201 | /** 202 | * This download couldn't be completed because of an 203 | * HttpException while setting up the request. 204 | */ 205 | public static final int STATUS_HTTP_EXCEPTION = 496; 206 | 207 | /** 208 | * This download couldn't be completed because there were 209 | * too many redirects. 210 | */ 211 | public static final int STATUS_TOO_MANY_REDIRECTS = 497; 212 | 213 | /** 214 | * This download couldn't be completed due to insufficient storage 215 | * space. Typically, this is because the SD card is full. 216 | */ 217 | public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; 218 | 219 | /** 220 | * This download couldn't be completed because no external storage 221 | * device was found. Typically, this is because the SD card is not 222 | * mounted. 223 | */ 224 | public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; 225 | 226 | /** 227 | * The wake duration to check to see if a download is possible. 228 | */ 229 | public static final long WATCHDOG_WAKE_TIMER = 60*1000; 230 | 231 | /** 232 | * The wake duration to check to see if the process was killed. 233 | */ 234 | public static final long ACTIVE_THREAD_WATCHDOG = 5*1000; 235 | 236 | } -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.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.Parcel; 20 | import android.os.Parcelable; 21 | 22 | 23 | /** 24 | * This class contains progress information about the active download(s). 25 | * 26 | * When you build the Activity that initiates a download and tracks the 27 | * progress by implementing the {@link IDownloaderClient} interface, you'll 28 | * receive a DownloadProgressInfo object in each call to the {@link 29 | * IDownloaderClient#onDownloadProgress} method. This allows you to update 30 | * your activity's UI with information about the download progress, such 31 | * as the progress so far, time remaining and current speed. 32 | */ 33 | public class DownloadProgressInfo implements Parcelable { 34 | public long mOverallTotal; 35 | public long mOverallProgress; 36 | public long mTimeRemaining; // time remaining 37 | public float mCurrentSpeed; // speed in KB/S 38 | 39 | @Override 40 | public int describeContents() { 41 | return 0; 42 | } 43 | 44 | @Override 45 | public void writeToParcel(Parcel p, int i) { 46 | p.writeLong(mOverallTotal); 47 | p.writeLong(mOverallProgress); 48 | p.writeLong(mTimeRemaining); 49 | p.writeFloat(mCurrentSpeed); 50 | } 51 | 52 | public DownloadProgressInfo(Parcel p) { 53 | mOverallTotal = p.readLong(); 54 | mOverallProgress = p.readLong(); 55 | mTimeRemaining = p.readLong(); 56 | mCurrentSpeed = p.readFloat(); 57 | } 58 | 59 | public DownloadProgressInfo(long overallTotal, long overallProgress, 60 | long timeRemaining, 61 | float currentSpeed) { 62 | this.mOverallTotal = overallTotal; 63 | this.mOverallProgress = overallProgress; 64 | this.mTimeRemaining = timeRemaining; 65 | this.mCurrentSpeed = currentSpeed; 66 | } 67 | 68 | public static final Creator CREATOR = new Creator() { 69 | @Override 70 | public DownloadProgressInfo createFromParcel(Parcel parcel) { 71 | return new DownloadProgressInfo(parcel); 72 | } 73 | 74 | @Override 75 | public DownloadProgressInfo[] newArray(int i) { 76 | return new DownloadProgressInfo[i]; 77 | } 78 | }; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /dependency/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.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.app.PendingIntent; 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.Bundle; 28 | import android.os.Handler; 29 | import android.os.IBinder; 30 | import android.os.Message; 31 | import android.os.Messenger; 32 | import android.os.RemoteException; 33 | import android.util.Log; 34 | 35 | 36 | 37 | /** 38 | * This class binds the service API to your application client. It contains the IDownloaderClient proxy, 39 | * which is used to call functions in your client as well as the Stub, which is used to call functions 40 | * in the client implementation of IDownloaderClient. 41 | * 42 | *

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 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /dependency/play_licensing/aidl/ILicenseResultListener.aidl: -------------------------------------------------------------------------------- 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.android.vending.licensing; 18 | 19 | // Android library projects do not yet support AIDL, so this has been 20 | // precompiled into the src directory. 21 | oneway interface ILicenseResultListener { 22 | void verifyLicense(int responseCode, String signedData, String signature); 23 | } 24 | -------------------------------------------------------------------------------- /dependency/play_licensing/aidl/ILicensingService.aidl: -------------------------------------------------------------------------------- 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.android.vending.licensing; 18 | 19 | import com.android.vending.licensing.ILicenseResultListener; 20 | 21 | // Android library projects do not yet support AIDL, so this has been 22 | // precompiled into the src directory. 23 | oneway interface ILicensingService { 24 | void checkLicense(long nonce, String packageName, in ILicenseResultListener listener); 25 | } 26 | -------------------------------------------------------------------------------- /dependency/play_licensing/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | } 10 | } 11 | 12 | android { 13 | compileSdkVersion 24 14 | useLibrary 'org.apache.http.legacy' 15 | 16 | defaultConfig { 17 | //applicationId "com.google.android.vending.licensing" 18 | minSdkVersion 10 19 | targetSdkVersion 25 20 | } 21 | buildTypes { 22 | release { 23 | //runProguard false 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 26 | } 27 | } 28 | productFlavors { 29 | } 30 | 31 | sourceSets { 32 | main { 33 | manifest.srcFile 'AndroidManifest.xml' 34 | java.srcDirs = ['src'] 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /dependency/play_licensing/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dependency/play_licensing/libs/org.apache.http.legacy.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasuster/android-expansion/29711b95780df2b1bc4bf3a2a4387f3e67f0a954/dependency/play_licensing/libs/org.apache.http.legacy.jar -------------------------------------------------------------------------------- /dependency/play_licensing/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | android.library=true 11 | # Project target. 12 | target=android-::ANDROID_TARGET_SDK_VERSION:: -------------------------------------------------------------------------------- /dependency/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.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 java.io.UnsupportedEncodingException; 23 | import java.security.GeneralSecurityException; 24 | import java.security.spec.KeySpec; 25 | 26 | import javax.crypto.BadPaddingException; 27 | import javax.crypto.Cipher; 28 | import javax.crypto.IllegalBlockSizeException; 29 | import javax.crypto.SecretKey; 30 | import javax.crypto.SecretKeyFactory; 31 | import javax.crypto.spec.IvParameterSpec; 32 | import javax.crypto.spec.PBEKeySpec; 33 | import javax.crypto.spec.SecretKeySpec; 34 | 35 | /** 36 | * An Obfuscator that uses AES to encrypt data. 37 | */ 38 | public class AESObfuscator implements Obfuscator { 39 | private static final String UTF8 = "UTF-8"; 40 | private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC"; 41 | private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; 42 | private static final byte[] IV = 43 | { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 }; 44 | private static final String header = "com.android.vending.licensing.AESObfuscator-1|"; 45 | 46 | private Cipher mEncryptor; 47 | private Cipher mDecryptor; 48 | 49 | /** 50 | * @param salt an array of random bytes to use for each (un)obfuscation 51 | * @param applicationId application identifier, e.g. the package name 52 | * @param deviceId device identifier. Use as many sources as possible to 53 | * create this unique identifier. 54 | */ 55 | public AESObfuscator(byte[] salt, String applicationId, String deviceId) { 56 | try { 57 | SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); 58 | KeySpec keySpec = 59 | new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); 60 | SecretKey tmp = factory.generateSecret(keySpec); 61 | SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); 62 | mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM); 63 | mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV)); 64 | mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM); 65 | mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV)); 66 | } catch (GeneralSecurityException e) { 67 | // This can't happen on a compatible Android device. 68 | throw new RuntimeException("Invalid environment", e); 69 | } 70 | } 71 | 72 | public String obfuscate(String original, String key) { 73 | if (original == null) { 74 | return null; 75 | } 76 | try { 77 | // Header is appended as an integrity check 78 | return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8))); 79 | } catch (UnsupportedEncodingException e) { 80 | throw new RuntimeException("Invalid environment", e); 81 | } catch (GeneralSecurityException e) { 82 | throw new RuntimeException("Invalid environment", e); 83 | } 84 | } 85 | 86 | public String unobfuscate(String obfuscated, String key) throws ValidationException { 87 | if (obfuscated == null) { 88 | return null; 89 | } 90 | try { 91 | String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8); 92 | // Check for presence of header. This serves as a final integrity check, for cases 93 | // where the block size is correct during decryption. 94 | int headerIndex = result.indexOf(header+key); 95 | if (headerIndex != 0) { 96 | throw new ValidationException("Header not found (invalid data or key)" + ":" + 97 | obfuscated); 98 | } 99 | return result.substring(header.length()+key.length(), result.length()); 100 | } catch (Base64DecoderException e) { 101 | throw new ValidationException(e.getMessage() + ":" + obfuscated); 102 | } catch (IllegalBlockSizeException e) { 103 | throw new ValidationException(e.getMessage() + ":" + obfuscated); 104 | } catch (BadPaddingException e) { 105 | throw new ValidationException(e.getMessage() + ":" + obfuscated); 106 | } catch (UnsupportedEncodingException e) { 107 | throw new RuntimeException("Invalid environment", e); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /dependency/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.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 | * Allows the developer to limit the number of devices using a single license. 21 | *

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 mChecksInProgress = new HashSet(); 81 | private final Queue mPendingChecks = new LinkedList(); 82 | 83 | /** 84 | * @param context a Context 85 | * @param policy implementation of Policy 86 | * @param encodedPublicKey Base64-encoded RSA public key 87 | * @throws IllegalArgumentException if encodedPublicKey is invalid 88 | */ 89 | public LicenseChecker(Context context, Policy policy, String encodedPublicKey) { 90 | mContext = context; 91 | mPolicy = policy; 92 | mPublicKey = generatePublicKey(encodedPublicKey); 93 | mPackageName = mContext.getPackageName(); 94 | mVersionCode = getVersionCode(context, mPackageName); 95 | HandlerThread handlerThread = new HandlerThread("background thread"); 96 | handlerThread.start(); 97 | mHandler = new Handler(handlerThread.getLooper()); 98 | } 99 | 100 | /** 101 | * Generates a PublicKey instance from a string containing the 102 | * Base64-encoded public key. 103 | * 104 | * @param encodedPublicKey Base64-encoded public key 105 | * @throws IllegalArgumentException if encodedPublicKey is invalid 106 | */ 107 | private static PublicKey generatePublicKey(String encodedPublicKey) { 108 | try { 109 | byte[] decodedKey = Base64.decode(encodedPublicKey); 110 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); 111 | 112 | return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); 113 | } catch (NoSuchAlgorithmException e) { 114 | // This won't happen in an Android-compatible environment. 115 | throw new RuntimeException(e); 116 | } catch (Base64DecoderException e) { 117 | Log.e(TAG, "Could not decode from Base64."); 118 | throw new IllegalArgumentException(e); 119 | } catch (InvalidKeySpecException e) { 120 | Log.e(TAG, "Invalid key specification."); 121 | throw new IllegalArgumentException(e); 122 | } 123 | } 124 | 125 | /** 126 | * Checks if the user should have access to the app. Binds the service if necessary. 127 | *

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 | *

    93 | *
  • VT: the timestamp that the client should consider the response 94 | * valid until 95 | *
  • GT: the timestamp that the client should ignore retry errors until 96 | *
  • GR: the number of retry errors that the client should ignore 97 | *
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 extras = decodeExtras(rawData.extra); 114 | mLastResponse = response; 115 | setValidityTimestamp(extras.get("VT")); 116 | setRetryUntil(extras.get("GT")); 117 | setMaxRetries(extras.get("GR")); 118 | } else if (response == Policy.NOT_LICENSED) { 119 | // Clear out stale policy data 120 | setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); 121 | setRetryUntil(DEFAULT_RETRY_UNTIL); 122 | setMaxRetries(DEFAULT_MAX_RETRIES); 123 | } 124 | 125 | setLastResponse(response); 126 | mPreferences.commit(); 127 | } 128 | 129 | /** 130 | * Set the last license response received from the server and add to 131 | * preferences. You must manually call PreferenceObfuscator.commit() to 132 | * commit these changes to disk. 133 | * 134 | * @param l the response 135 | */ 136 | private void setLastResponse(int l) { 137 | mLastResponseTime = System.currentTimeMillis(); 138 | mLastResponse = l; 139 | mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); 140 | } 141 | 142 | /** 143 | * Set the current retry count and add to preferences. You must manually 144 | * call PreferenceObfuscator.commit() to commit these changes to disk. 145 | * 146 | * @param c the new retry count 147 | */ 148 | private void setRetryCount(long c) { 149 | mRetryCount = c; 150 | mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); 151 | } 152 | 153 | public long getRetryCount() { 154 | return mRetryCount; 155 | } 156 | 157 | /** 158 | * Set the last validity timestamp (VT) received from the server and add to 159 | * preferences. You must manually call PreferenceObfuscator.commit() to 160 | * commit these changes to disk. 161 | * 162 | * @param validityTimestamp the VT string received 163 | */ 164 | private void setValidityTimestamp(String validityTimestamp) { 165 | Long lValidityTimestamp; 166 | try { 167 | lValidityTimestamp = Long.parseLong(validityTimestamp); 168 | } catch (NumberFormatException e) { 169 | // No response or not parsable, expire in one minute. 170 | Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); 171 | lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; 172 | validityTimestamp = Long.toString(lValidityTimestamp); 173 | } 174 | 175 | mValidityTimestamp = lValidityTimestamp; 176 | mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); 177 | } 178 | 179 | public long getValidityTimestamp() { 180 | return mValidityTimestamp; 181 | } 182 | 183 | /** 184 | * Set the retry until timestamp (GT) received from the server and add to 185 | * preferences. You must manually call PreferenceObfuscator.commit() to 186 | * commit these changes to disk. 187 | * 188 | * @param retryUntil the GT string received 189 | */ 190 | private void setRetryUntil(String retryUntil) { 191 | Long lRetryUntil; 192 | try { 193 | lRetryUntil = Long.parseLong(retryUntil); 194 | } catch (NumberFormatException e) { 195 | // No response or not parsable, expire immediately 196 | Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); 197 | retryUntil = "0"; 198 | lRetryUntil = 0l; 199 | } 200 | 201 | mRetryUntil = lRetryUntil; 202 | mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); 203 | } 204 | 205 | public long getRetryUntil() { 206 | return mRetryUntil; 207 | } 208 | 209 | /** 210 | * Set the max retries value (GR) as received from the server and add to 211 | * preferences. You must manually call PreferenceObfuscator.commit() to 212 | * commit these changes to disk. 213 | * 214 | * @param maxRetries the GR string received 215 | */ 216 | private void setMaxRetries(String maxRetries) { 217 | Long lMaxRetries; 218 | try { 219 | lMaxRetries = Long.parseLong(maxRetries); 220 | } catch (NumberFormatException e) { 221 | // No response or not parsable, expire immediately 222 | Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); 223 | maxRetries = "0"; 224 | lMaxRetries = 0l; 225 | } 226 | 227 | mMaxRetries = lMaxRetries; 228 | mPreferences.putString(PREF_MAX_RETRIES, maxRetries); 229 | } 230 | 231 | public long getMaxRetries() { 232 | return mMaxRetries; 233 | } 234 | 235 | /** 236 | * {@inheritDoc} 237 | * 238 | * This implementation allows access if either:
239 | *
    240 | *
  1. a LICENSED response was received within the validity period 241 | *
  2. a RETRY response was received in the last minute, and we are under 242 | * the RETRY count or in the RETRY period. 243 | *
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 decodeExtras(String extras) { 263 | Map results = new HashMap(); 264 | try { 265 | URI rawExtras = new URI("?" + extras); 266 | List extraList = URLEncodedUtils.parse(rawExtras, "UTF-8"); 267 | for (NameValuePair item : extraList) { 268 | results.put(item.getName(), item.getValue()); 269 | } 270 | } catch (URISyntaxException e) { 271 | Log.w(TAG, "Invalid syntax error while decoding extras data from server."); 272 | } 273 | return results; 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /dependency/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.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 | * Non-caching policy. All requests will be sent to the licensing service, 21 | * and no local caching is performed. 22 | *

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 | 3 | 4 | 5 | 6 |

7 | 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /run.n: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasuster/android-expansion/29711b95780df2b1bc4bf3a2a4387f3e67f0a954/run.n -------------------------------------------------------------------------------- /samples/DisplayingABitmap/Assets/nme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasuster/android-expansion/29711b95780df2b1bc4bf3a2a4387f3e67f0a954/samples/DisplayingABitmap/Assets/nme.png -------------------------------------------------------------------------------- /samples/DisplayingABitmap/Assets/nme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/DisplayingABitmap/Source/Main.hx: -------------------------------------------------------------------------------- 1 | import flash.display.BitmapData; 2 | import nme.display.Bitmap; 3 | import nme.display.Sprite; 4 | import nme.Assets; 5 | import nme.Lib; 6 | import extension.androidExpansion.AndroidExpansion; 7 | import extension.androidExpansion.ExpansionReader; 8 | class Main extends Sprite { 9 | 10 | public function new () { 11 | super(); 12 | addBitmapDataToStage(Assets.getBitmapData ("assets/nme.png")); 13 | handleExpansion(); 14 | } 15 | 16 | function handleExpansion():Void { 17 | //Google Play -> Services & API -> Base64-encoded RSA public key 18 | AndroidExpansion.setKey('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAul9VjbxKwReCzuLeVfNi1sCrsv/upIiSQsTS+5ToOiFD5tjEHNd4p/m95m4/8wf7aLgTjkhi7w8LTKwm+JVdFl5l4ZADY66Y+klysm95jrYhCHnbNjZIpQp6dKD94pVzo8Hf3d00vB0ZnyVhOZO8aMg9RrJtyzdioJgdMrpBq8yHXL8X/gvD00w/AkqHT+YUAXVn7FwuljMiDSAK15wO/uc/ec37yA4m8zMEK8K4skvzbA9cbAdBp+0PkxK+ep7zxNBtDtfCBJjlW4l1Fb3O7VA3i2abrxeWUnWZu3I51Rj/00h/cUXKz19TE8x/pDsUWUrRPqO5y98MsYctxX2drwIDAQAB'); 19 | 20 | /* 21 | Use AndroidExpansion.getAPKVersion() for production applications. 22 | This is needed if you're using `haxelib run nme test android -gradle` which produces an APK per architecture 23 | which dynamically creates a version code for each APK. If you upload your expansion file to google play using 24 | the base expansion. Say "main.181.io.nme.samples.displayingabitmap.obb" the google play UI will actually rename it automatically to 25 | the APK it's attaching to. 26 | */ 27 | // AndroidExpansion.setVersion(AndroidExpansion.getAPKVersion()); 28 | 29 | AndroidExpansion.setVersion(181); 30 | //The number of bytes of the OBB file, this number is printed at the end of android-expansion zip 31 | AndroidExpansion.setBytes(6342); 32 | //Modify this salt 33 | AndroidExpansion.setSalt([1, 42, -12, -1, 99, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84]); 34 | if(AndroidExpansion.expansionFilesDelivered()) { 35 | var expansionReader:ExpansionReader = new ExpansionReader(); 36 | addBitmapDataToStage(expansionReader.getBitmapData('main-expansion/nme.png')); 37 | } 38 | else { 39 | AndroidExpansion.startDownloadServiceIfRequired(); 40 | } 41 | } 42 | 43 | function addBitmapDataToStage(data:BitmapData):Void { 44 | var bitmap = new Bitmap (data); 45 | addChild (bitmap); 46 | bitmap.x = (Lib.current.stage.stageWidth - bitmap.width) / 2; 47 | bitmap.y = (Lib.current.stage.stageHeight - bitmap.height) / 2; 48 | } 49 | } -------------------------------------------------------------------------------- /samples/DisplayingABitmap/main-expansion/nme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasuster/android-expansion/29711b95780df2b1bc4bf3a2a4387f3e67f0a954/samples/DisplayingABitmap/main-expansion/nme.png -------------------------------------------------------------------------------- /samples/DisplayingABitmap/project.nmml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/extension/androidExpansion/AndroidExpansion.hx: -------------------------------------------------------------------------------- 1 | package extension.androidExpansion; 2 | 3 | #if android 4 | import openfl.utils.JNI; 5 | #end 6 | 7 | class AndroidExpansion { 8 | 9 | public static var version:Int; 10 | 11 | static var _init:Void->Void; 12 | static var _expansionFilesDelivered:Dynamic; 13 | static var _startDownloadServiceIfRequired:Dynamic; 14 | static var _getMainFile:Dynamic; 15 | static var _getPackageName:Dynamic; 16 | static var _getLocalStoragePath:Dynamic; 17 | static var _setVersion:Dynamic; 18 | static var _getAPKVersion:Dynamic; 19 | static var _setBytes:Dynamic; 20 | static var _setKey:Dynamic; 21 | static var _setSalt:Dynamic; 22 | 23 | public static function init():Void { 24 | initJNI(); 25 | _init(); 26 | } 27 | 28 | public static function setVersion(v:Int):Void { 29 | initJNI(); 30 | version = v; 31 | _setVersion(v); 32 | } 33 | 34 | public static function getAPKVersion():Int { 35 | initJNI(); 36 | return _getAPKVersion(); 37 | } 38 | 39 | public static function setBytes(v:Float):Void { 40 | initJNI(); 41 | _setBytes(v); 42 | } 43 | 44 | public static function setKey(v:String):Void { 45 | initJNI(); 46 | _setKey(v); 47 | } 48 | 49 | public static function setSalt(v:Array):Void { 50 | initJNI(); 51 | _setSalt(v); 52 | } 53 | 54 | public static function expansionFilesDelivered():Bool { 55 | initJNI(); 56 | if(_expansionFilesDelivered() == 1) 57 | return true; 58 | return false; 59 | } 60 | 61 | public static function startDownloadServiceIfRequired():Int { 62 | initJNI(); 63 | return _startDownloadServiceIfRequired(); 64 | } 65 | 66 | public static function getMainFile():String { 67 | initJNI(); 68 | return _getMainFile()[0]; 69 | } 70 | 71 | public static function getPackageName():String { 72 | initJNI(); 73 | return _getPackageName(); 74 | } 75 | 76 | public static function getLocalStoragePath():String { 77 | initJNI(); 78 | return _getLocalStoragePath(); 79 | } 80 | 81 | private static function initJNI():Void { 82 | if(_init == null) { 83 | #if android 84 | _init = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "init", "()V"); 85 | _expansionFilesDelivered = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "expansionFilesDelivered", "()I"); 86 | _startDownloadServiceIfRequired = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "startDownloadServiceIfRequired", "()I"); 87 | _getMainFile = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "getMainFile", "()Ljava/lang/String;"); 88 | 89 | _getPackageName = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "getPackageName", "()Ljava/lang/String;"); 90 | _getLocalStoragePath = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "getLocalStoragePath", "()Ljava/lang/String;"); 91 | 92 | _setVersion = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "setVersion", "(I)V"); 93 | _getAPKVersion = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "getAPKVersion", "()I"); 94 | _setBytes = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "setBytes", "(J)V"); 95 | _setKey = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "setKey", "(Ljava/lang/String;)V"); 96 | _setSalt = JNI.createStaticMethod("com/thomasuster/androidExpansion/Expansion", "setSalt", "([B)V"); 97 | #end 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/extension/androidExpansion/ExpansionReader.hx: -------------------------------------------------------------------------------- 1 | package extension.androidExpansion; 2 | import flash.utils.ByteArray; 3 | import haxe.zip.Reader; 4 | import sys.io.File; 5 | import sys.io.FileInput; 6 | import sys.FileSystem; 7 | import flash.display.BitmapData; 8 | class ExpansionReader { 9 | 10 | var reg:EReg; 11 | 12 | public function new():Void { 13 | reg = ~/[\/\\]/g; 14 | } 15 | 16 | public function getBitmapData(id:String):BitmapData { 17 | return BitmapData.loadFromBytes(getByteArray(id)); 18 | } 19 | 20 | public function getText(id:String):String { 21 | return Std.string(getByteArray(id)); 22 | } 23 | 24 | function getByteArray(id:String):ByteArray { 25 | var localStoragePath:String = AndroidExpansion.getLocalStoragePath(); 26 | var packageName:String = AndroidExpansion.getPackageName(); 27 | var assetsDirectory:String = localStoragePath + "/Android/obb/" + packageName + "/"; 28 | var assetsFilePath:String = assetsDirectory + "main." + AndroidExpansion.version + "." + packageName + ".obb"; 29 | 30 | if(FileSystem.exists(assetsFilePath)) { 31 | var file:FileInput = File.read(assetsFilePath); 32 | var entries = Reader.readZip(file); 33 | for(entry in entries) { 34 | var fileName:String = entry.fileName; 35 | if (fileName.charAt (0) != "/" && fileName.charAt (0) != "\\" && fileName.split ("..").length <= 1) { 36 | var dirs:Array = reg.split(fileName); 37 | var path:String = ''; 38 | var file = dirs.pop(); 39 | for( d in dirs ) { 40 | path += d; 41 | path += "/"; 42 | } 43 | path += file; 44 | if(path == id) { 45 | var data = Reader.unzip(entry); 46 | var byteArray:ByteArray = ByteArray.fromBytes(data); 47 | return byteArray; 48 | } 49 | } 50 | } 51 | } 52 | return null; 53 | } 54 | } -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | cd tools/test 2 | haxe compile.hxml 3 | cd .. 4 | bin/test/TestMain 5 | -------------------------------------------------------------------------------- /tools/run/RunMain.hx: -------------------------------------------------------------------------------- 1 | import com.thomasuster.expansion.ExpansionTool; 2 | import Array; 3 | class RunMain 4 | { 5 | static var tool:ExpansionTool; 6 | 7 | public static function main() { 8 | tool = new ExpansionTool(); 9 | tool.args = Sys.args(); 10 | tool.handleArgs(); 11 | } 12 | } -------------------------------------------------------------------------------- /tools/run/compile.hxml: -------------------------------------------------------------------------------- 1 | -cp ../src 2 | -main RunMain 3 | -neko ../../run.n 4 | -debug -------------------------------------------------------------------------------- /tools/src/com/thomasuster/expansion/ExpansionTool.hx: -------------------------------------------------------------------------------- 1 | package com.thomasuster.expansion; 2 | class ExpansionTool 3 | { 4 | public var sysProxy:SysProxy; 5 | public var args:Array; 6 | 7 | public function new():Void { 8 | sysProxy = new RealSys(); 9 | } 10 | 11 | public function handleArgs():Void { 12 | if(args[0] == 'zip') 13 | makeZip(); 14 | if(args[0] == 'push') 15 | makePush(); 16 | } 17 | 18 | function makeZip():Void { 19 | var pack:String = getArg(0); 20 | var version:Int = Std.parseInt(getArg(1)); 21 | var source:String = getArg(2); 22 | var dest:String = getLastArg(); 23 | sysProxy.setCwd(dest); 24 | sysProxy.command('zip', ['-r', 'main.$version.$pack.obb', '$source']); 25 | sysProxy.command('stat', ['-f%z', 'main.$version.$pack.obb']); 26 | } 27 | 28 | function makePush():Void { 29 | var pack:String = getArg(0); 30 | var version:Int = Std.parseInt(getArg(1)); 31 | var dest:String = getLastArg(); 32 | var androidDestination:String = '/storage/emulated/0/Android/obb'; 33 | var name:String = 'main.$version.$pack.obb'; 34 | sysProxy.command('adb',['push', '$dest$name', '$androidDestination/$pack/$name']); 35 | } 36 | 37 | public function getArg(i:Int):String { 38 | if(args.length > i) { 39 | return args[i+1]; 40 | } 41 | return ''; 42 | } 43 | 44 | public function getLastArg():String { 45 | return args[args.length-1]; 46 | } 47 | 48 | public function log(s:String) { 49 | Sys.println(s); 50 | } 51 | } -------------------------------------------------------------------------------- /tools/src/com/thomasuster/expansion/RealSys.hx: -------------------------------------------------------------------------------- 1 | package com.thomasuster.expansion; 2 | class RealSys extends SysProxy 3 | { 4 | 5 | override public function setCwd(s:String):Void { 6 | Sys.setCwd(s); 7 | } 8 | 9 | override public function command( cmd : String, ?args : Array ) : Int { 10 | return Sys.command(cmd, args); 11 | } 12 | } -------------------------------------------------------------------------------- /tools/src/com/thomasuster/expansion/SysProxy.hx: -------------------------------------------------------------------------------- 1 | package com.thomasuster.expansion; 2 | class SysProxy 3 | { 4 | public function new():Void {} 5 | 6 | public function setCwd( s : String ) : Void { 7 | 8 | } 9 | 10 | public function command( cmd : String, ?args : Array ) : Int { 11 | return 0; 12 | } 13 | } -------------------------------------------------------------------------------- /tools/test/TestMain.hx: -------------------------------------------------------------------------------- 1 | import haxe.unit.TestRunner; 2 | class TestMain { 3 | 4 | static function main() { 5 | var runner:TestRunner = new TestRunner(); 6 | runner.add(new TestTool()); 7 | runner.run(); 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /tools/test/TestTool.hx: -------------------------------------------------------------------------------- 1 | import com.thomasuster.expansion.Command; 2 | import com.thomasuster.expansion.MockSys; 3 | import com.thomasuster.expansion.ExpansionTool; 4 | class TestTool extends haxe.unit.TestCase { 5 | 6 | var tool:ExpansionTool; 7 | var mockSys:MockSys; 8 | var command:Command; 9 | 10 | override public function setup():Void { 11 | tool = new ExpansionTool(); 12 | mockSys = new MockSys(); 13 | tool.sysProxy = mockSys; 14 | } 15 | 16 | public function testZip() { 17 | setupZip(); 18 | tool.handleArgs(); 19 | command = mockSys.commands[0]; 20 | assertTrue(command.name == 'zip'); 21 | assertTrue(command.cwd == '/Users/person/project/'); 22 | assertTrue(command.args.length == 3); 23 | assertTrue(command.args[0] == '-r'); 24 | assertTrue(command.args[1] == 'main.178.com.companany.project.obb'); 25 | assertTrue(command.args[2] == 'main-expansion'); 26 | } 27 | 28 | public function testBytesShownForZip():Void { 29 | setupZip(); 30 | tool.handleArgs(); 31 | command = mockSys.commands[1]; 32 | assertTrue(command.name == 'stat'); 33 | } 34 | 35 | public function testPushForADB() { 36 | tool.args = []; 37 | tool.args.push('push'); 38 | tool.args.push('com.companany.project'); 39 | tool.args.push('178'); 40 | tool.args.push('/Users/person/project/'); 41 | tool.handleArgs(); 42 | command = mockSys.commands[0]; 43 | assertTrue(command.name == 'adb'); 44 | assertTrue(command.args.length == 3); 45 | assertTrue(command.args[0] == 'push'); 46 | assertTrue(command.args[1] == '/Users/person/project/main.178.com.companany.project.obb'); 47 | assertTrue(command.args[2] == '/storage/emulated/0/Android/obb/com.companany.project/main.178.com.companany.project.obb'); 48 | } 49 | 50 | function setupZip():Void { 51 | tool.args = []; 52 | tool.args.push('zip'); 53 | tool.args.push('com.companany.project'); 54 | tool.args.push('178'); 55 | tool.args.push('main-expansion'); 56 | tool.args.push('/Users/person/project/'); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /tools/test/com/thomasuster/expansion/Command.hx: -------------------------------------------------------------------------------- 1 | package com.thomasuster.expansion; 2 | class Command 3 | { 4 | public var name:String; 5 | public var args:Array; 6 | public var cwd:String; 7 | 8 | public function new():Void {} 9 | } -------------------------------------------------------------------------------- /tools/test/com/thomasuster/expansion/MockSys.hx: -------------------------------------------------------------------------------- 1 | package com.thomasuster.expansion; 2 | class MockSys extends SysProxy 3 | { 4 | public var commands:Array; 5 | 6 | var cwd:String; 7 | 8 | public function new():Void { 9 | super(); 10 | commands = []; 11 | } 12 | 13 | override public function setCwd(s:String):Void { 14 | cwd = s; 15 | } 16 | 17 | override public function command( cmd : String, ?args : Array ) : Int { 18 | var command:Command = new Command(); 19 | command.name = cmd; 20 | command.args = args; 21 | command.cwd = cwd; 22 | commands.push(command); 23 | return 0; 24 | } 25 | } -------------------------------------------------------------------------------- /tools/test/compile.hxml: -------------------------------------------------------------------------------- 1 | -cpp ../bin/test 2 | -cp . 3 | -cp ../src 4 | -main TestMain 5 | --------------------------------------------------------------------------------