From 401622cc229317bd218f070dd07a3bd8db582f16 Mon Sep 17 00:00:00 2001 From: Juan Linietsky Date: Fri, 8 Jan 2016 13:36:44 -0300 Subject: -Removed ANT build system for Android, as it was deprecated by Google -Added new Gradle build system, as it is the required build system --- platform/android/AndroidManifest.xml.template | 2 +- platform/android/SCsub | 60 +- platform/android/build.gradle.template | 72 ++ platform/android/detect.py | 4 - .../vending/billing/IInAppBillingService.aidl | 144 +++ platform/android/java/ant.properties | 17 - platform/android/java/build.properties | 17 - platform/android/java/build.xml | 92 -- platform/android/java/default.properties | 11 - .../android/java/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../java/gradle/wrapper/gradle-wrapper.properties | 6 + platform/android/java/gradlew | 164 +++ platform/android/java/gradlew.bat | 90 ++ platform/android/java/my-release-key.keystore | Bin 2218 -> 0 bytes platform/android/java/proguard-project.txt | 20 - platform/android/java/proguard.cfg | 36 - .../notify_panel_notification_icon_bg.png | Bin 0 -> 1027 bytes .../notify_panel_notification_icon_bg.png | Bin 0 -> 1125 bytes .../status_bar_ongoing_event_progress_bar.xml | 104 ++ platform/android/java/res/values-v11/styles.xml | 6 + platform/android/java/res/values-v9/styles.xml | 5 + platform/android/java/res/values/strings.xml | 42 +- platform/android/java/res/values/styles.xml | 25 + .../java/src/com/android/godot/Dictionary.java | 2 +- .../android/java/src/com/android/godot/Godot.java | 2 +- .../java/src/com/android/godot/GodotIO.java | 2 +- .../java/src/com/android/godot/GodotLib.java | 2 +- .../java/src/com/android/godot/GodotView.java | 2 +- .../android/vending/licensing/AESObfuscator.java | 110 ++ .../vending/licensing/APKExpansionPolicy.java | 397 ++++++ .../android/vending/licensing/DeviceLimiter.java | 47 + .../vending/licensing/ILicenseResultListener.aidl | 23 + .../vending/licensing/ILicenseResultListener.java | 99 ++ .../vending/licensing/ILicensingService.aidl | 25 + .../vending/licensing/ILicensingService.java | 99 ++ .../android/vending/licensing/LicenseChecker.java | 351 +++++ .../vending/licensing/LicenseCheckerCallback.java | 67 + .../vending/licensing/LicenseValidator.java | 224 ++++ .../vending/licensing/NullDeviceLimiter.java | 32 + .../com/android/vending/licensing/Obfuscator.java | 48 + .../src/com/android/vending/licensing/Policy.java | 59 + .../vending/licensing/PreferenceObfuscator.java | 77 ++ .../android/vending/licensing/ResponseData.java | 79 ++ .../vending/licensing/ServerManagedPolicy.java | 276 ++++ .../android/vending/licensing/StrictPolicy.java | 63 + .../vending/licensing/ValidationException.java | 33 + .../com/android/vending/licensing/util/Base64.java | 570 +++++++++ .../licensing/util/Base64DecoderException.java | 32 + .../vending/expansion/downloader/Constants.java | 236 ++++ .../expansion/downloader/DownloadProgressInfo.java | 80 ++ .../downloader/DownloaderClientMarshaller.java | 277 ++++ .../downloader/DownloaderServiceMarshaller.java | 181 +++ .../vending/expansion/downloader/Helpers.java | 306 +++++ .../expansion/downloader/IDownloaderClient.java | 126 ++ .../expansion/downloader/IDownloaderService.java | 83 ++ .../vending/expansion/downloader/IStub.java | 41 + .../vending/expansion/downloader/SystemFacade.java | 123 ++ .../downloader/impl/AndroidHttpClient.java | 536 ++++++++ .../downloader/impl/CustomIntentService.java | 112 ++ .../downloader/impl/CustomNotificationFactory.java | 30 + .../expansion/downloader/impl/DownloadInfo.java | 92 ++ .../downloader/impl/DownloadNotification.java | 231 ++++ .../expansion/downloader/impl/DownloadThread.java | 963 ++++++++++++++ .../downloader/impl/DownloaderService.java | 1341 ++++++++++++++++++++ .../expansion/downloader/impl/DownloadsDB.java | 510 ++++++++ .../expansion/downloader/impl/HttpDateTime.java | 200 +++ .../downloader/impl/V14CustomNotification.java | 101 ++ .../downloader/impl/V3CustomNotification.java | 116 ++ .../android/libs/apk_expansion/AndroidManifest.xml | 9 - platform/android/libs/apk_expansion/build.xml | 92 -- .../libs/apk_expansion/proguard-project.txt | 20 - .../android/libs/apk_expansion/project.properties | 13 - .../notify_panel_notification_icon_bg.png | Bin 1027 -> 0 bytes .../notify_panel_notification_icon_bg.png | Bin 1125 -> 0 bytes .../status_bar_ongoing_event_progress_bar.xml | 104 -- .../libs/apk_expansion/res/values-v11/styles.xml | 6 - .../libs/apk_expansion/res/values-v9/styles.xml | 5 - .../libs/apk_expansion/res/values/strings.xml | 41 - .../libs/apk_expansion/res/values/styles.xml | 25 - .../vending/expansion/downloader/Constants.java | 236 ---- .../expansion/downloader/DownloadProgressInfo.java | 80 -- .../downloader/DownloaderClientMarshaller.java | 277 ---- .../downloader/DownloaderServiceMarshaller.java | 181 --- .../vending/expansion/downloader/Helpers.java | 306 ----- .../expansion/downloader/IDownloaderClient.java | 126 -- .../expansion/downloader/IDownloaderService.java | 83 -- .../vending/expansion/downloader/IStub.java | 41 - .../vending/expansion/downloader/SystemFacade.java | 123 -- .../downloader/impl/AndroidHttpClient.java | 536 -------- .../downloader/impl/CustomIntentService.java | 112 -- .../downloader/impl/CustomNotificationFactory.java | 30 - .../expansion/downloader/impl/DownloadInfo.java | 92 -- .../downloader/impl/DownloadNotification.java | 231 ---- .../expansion/downloader/impl/DownloadThread.java | 963 -------------- .../downloader/impl/DownloaderService.java | 1341 -------------------- .../expansion/downloader/impl/DownloadsDB.java | 510 -------- .../expansion/downloader/impl/HttpDateTime.java | 200 --- .../downloader/impl/V14CustomNotification.java | 101 -- .../downloader/impl/V3CustomNotification.java | 116 -- .../android/libs/downloader_library/.classpath | 9 - .../.settings/org.eclipse.jdt.core.prefs | 4 - .../libs/downloader_library/AndroidManifest.xml | 9 - platform/android/libs/downloader_library/build.xml | 92 -- .../libs/downloader_library/proguard-project.txt | 20 - .../libs/downloader_library/project.properties | 13 - .../notify_panel_notification_icon_bg.png | Bin 1027 -> 0 bytes .../notify_panel_notification_icon_bg.png | Bin 1125 -> 0 bytes .../status_bar_ongoing_event_progress_bar.xml | 104 -- .../downloader_library/res/values-v11/styles.xml | 6 - .../downloader_library/res/values-v9/styles.xml | 5 - .../libs/downloader_library/res/values/strings.xml | 41 - .../libs/downloader_library/res/values/styles.xml | 25 - .../vending/expansion/downloader/Constants.java | 236 ---- .../expansion/downloader/DownloadProgressInfo.java | 80 -- .../downloader/DownloaderClientMarshaller.java | 277 ---- .../downloader/DownloaderServiceMarshaller.java | 181 --- .../vending/expansion/downloader/Helpers.java | 306 ----- .../expansion/downloader/IDownloaderClient.java | 126 -- .../expansion/downloader/IDownloaderService.java | 83 -- .../vending/expansion/downloader/IStub.java | 41 - .../vending/expansion/downloader/SystemFacade.java | 123 -- .../downloader/impl/AndroidHttpClient.java | 536 -------- .../downloader/impl/CustomIntentService.java | 112 -- .../downloader/impl/CustomNotificationFactory.java | 30 - .../expansion/downloader/impl/DownloadInfo.java | 92 -- .../downloader/impl/DownloadNotification.java | 231 ---- .../expansion/downloader/impl/DownloadThread.java | 963 -------------- .../downloader/impl/DownloaderService.java | 1341 -------------------- .../expansion/downloader/impl/DownloadsDB.java | 510 -------- .../expansion/downloader/impl/HttpDateTime.java | 200 --- .../downloader/impl/V14CustomNotification.java | 101 -- .../downloader/impl/V3CustomNotification.java | 116 -- .../android/libs/google_play_services/.classpath | 9 - .../libs/google_play_services/AndroidManifest.xml | 9 - .../android/libs/google_play_services/README.txt | 17 - .../android/libs/google_play_services/build.xml | 92 -- .../libs/google-play-services.jar | Bin 1881748 -> 0 bytes .../libs/google-play-services.jar.properties | 1 - .../libs/google_play_services/proguard-project.txt | 20 - .../android/libs/google_play_services/proguard.txt | 20 - .../libs/google_play_services/project.properties | 15 - .../res/color/common_signin_btn_text_dark.xml | 18 - .../res/color/common_signin_btn_text_light.xml | 18 - .../common_signin_btn_icon_disabled_dark.9.png | Bin 1811 -> 0 bytes ...ommon_signin_btn_icon_disabled_focus_dark.9.png | Bin 1846 -> 0 bytes ...mmon_signin_btn_icon_disabled_focus_light.9.png | Bin 1846 -> 0 bytes .../common_signin_btn_icon_disabled_light.9.png | Bin 1811 -> 0 bytes .../common_signin_btn_icon_focus_dark.9.png | Bin 2100 -> 0 bytes .../common_signin_btn_icon_focus_light.9.png | Bin 2075 -> 0 bytes .../common_signin_btn_icon_normal_dark.9.png | Bin 2050 -> 0 bytes .../common_signin_btn_icon_normal_light.9.png | Bin 2049 -> 0 bytes .../common_signin_btn_icon_pressed_dark.9.png | Bin 2224 -> 0 bytes .../common_signin_btn_icon_pressed_light.9.png | Bin 2331 -> 0 bytes .../common_signin_btn_text_disabled_dark.9.png | Bin 1927 -> 0 bytes ...ommon_signin_btn_text_disabled_focus_dark.9.png | Bin 1957 -> 0 bytes ...mmon_signin_btn_text_disabled_focus_light.9.png | Bin 1957 -> 0 bytes .../common_signin_btn_text_disabled_light.9.png | Bin 1927 -> 0 bytes .../common_signin_btn_text_focus_dark.9.png | Bin 2206 -> 0 bytes .../common_signin_btn_text_focus_light.9.png | Bin 2182 -> 0 bytes .../common_signin_btn_text_normal_dark.9.png | Bin 2185 -> 0 bytes .../common_signin_btn_text_normal_light.9.png | Bin 2158 -> 0 bytes .../common_signin_btn_text_pressed_dark.9.png | Bin 2374 -> 0 bytes .../common_signin_btn_text_pressed_light.9.png | Bin 2483 -> 0 bytes .../drawable-hdpi/ic_plusone_medium_off_client.png | Bin 1187 -> 0 bytes .../drawable-hdpi/ic_plusone_small_off_client.png | Bin 841 -> 0 bytes .../ic_plusone_standard_off_client.png | Bin 1498 -> 0 bytes .../drawable-hdpi/ic_plusone_tall_off_client.png | Bin 1140 -> 0 bytes .../common_signin_btn_icon_disabled_dark.9.png | Bin 1202 -> 0 bytes ...ommon_signin_btn_icon_disabled_focus_dark.9.png | Bin 1236 -> 0 bytes ...mmon_signin_btn_icon_disabled_focus_light.9.png | Bin 1236 -> 0 bytes .../common_signin_btn_icon_disabled_light.9.png | Bin 1202 -> 0 bytes .../common_signin_btn_icon_focus_dark.9.png | Bin 1389 -> 0 bytes .../common_signin_btn_icon_focus_light.9.png | Bin 1362 -> 0 bytes .../common_signin_btn_icon_normal_dark.9.png | Bin 1369 -> 0 bytes .../common_signin_btn_icon_normal_light.9.png | Bin 1345 -> 0 bytes .../common_signin_btn_icon_pressed_dark.9.png | Bin 1465 -> 0 bytes .../common_signin_btn_icon_pressed_light.9.png | Bin 1521 -> 0 bytes .../common_signin_btn_text_disabled_dark.9.png | Bin 1309 -> 0 bytes ...ommon_signin_btn_text_disabled_focus_dark.9.png | Bin 1316 -> 0 bytes ...mmon_signin_btn_text_disabled_focus_light.9.png | Bin 1316 -> 0 bytes .../common_signin_btn_text_disabled_light.9.png | Bin 1309 -> 0 bytes .../common_signin_btn_text_focus_dark.9.png | Bin 1461 -> 0 bytes .../common_signin_btn_text_focus_light.9.png | Bin 1463 -> 0 bytes .../common_signin_btn_text_normal_dark.9.png | Bin 1463 -> 0 bytes .../common_signin_btn_text_normal_light.9.png | Bin 1455 -> 0 bytes .../common_signin_btn_text_pressed_dark.9.png | Bin 1556 -> 0 bytes .../common_signin_btn_text_pressed_light.9.png | Bin 1623 -> 0 bytes .../drawable-mdpi/ic_plusone_medium_off_client.png | Bin 751 -> 0 bytes .../drawable-mdpi/ic_plusone_small_off_client.png | Bin 581 -> 0 bytes .../ic_plusone_standard_off_client.png | Bin 996 -> 0 bytes .../drawable-mdpi/ic_plusone_tall_off_client.png | Bin 789 -> 0 bytes .../common_signin_btn_icon_disabled_dark.9.png | Bin 2438 -> 0 bytes ...ommon_signin_btn_icon_disabled_focus_dark.9.png | Bin 2457 -> 0 bytes ...mmon_signin_btn_icon_disabled_focus_light.9.png | Bin 2457 -> 0 bytes .../common_signin_btn_icon_disabled_light.9.png | Bin 2438 -> 0 bytes .../common_signin_btn_icon_focus_dark.9.png | Bin 2886 -> 0 bytes .../common_signin_btn_icon_focus_light.9.png | Bin 2908 -> 0 bytes .../common_signin_btn_icon_normal_dark.9.png | Bin 2847 -> 0 bytes .../common_signin_btn_icon_normal_light.9.png | Bin 2888 -> 0 bytes .../common_signin_btn_icon_pressed_dark.9.png | Bin 3133 -> 0 bytes .../common_signin_btn_icon_pressed_light.9.png | Bin 3301 -> 0 bytes .../common_signin_btn_text_disabled_dark.9.png | Bin 2569 -> 0 bytes ...ommon_signin_btn_text_disabled_focus_dark.9.png | Bin 2571 -> 0 bytes ...mmon_signin_btn_text_disabled_focus_light.9.png | Bin 2571 -> 0 bytes .../common_signin_btn_text_disabled_light.9.png | Bin 2569 -> 0 bytes .../common_signin_btn_text_focus_dark.9.png | Bin 2939 -> 0 bytes .../common_signin_btn_text_focus_light.9.png | Bin 2947 -> 0 bytes .../common_signin_btn_text_normal_dark.9.png | Bin 2931 -> 0 bytes .../common_signin_btn_text_normal_light.9.png | Bin 2944 -> 0 bytes .../common_signin_btn_text_pressed_dark.9.png | Bin 3255 -> 0 bytes .../common_signin_btn_text_pressed_light.9.png | Bin 3384 -> 0 bytes .../ic_plusone_medium_off_client.png | Bin 1598 -> 0 bytes .../drawable-xhdpi/ic_plusone_small_off_client.png | Bin 1205 -> 0 bytes .../ic_plusone_standard_off_client.png | Bin 2074 -> 0 bytes .../drawable-xhdpi/ic_plusone_tall_off_client.png | Bin 1741 -> 0 bytes .../common_signin_btn_icon_disabled_dark.9.png | Bin 4988 -> 0 bytes ...ommon_signin_btn_icon_disabled_focus_dark.9.png | Bin 5092 -> 0 bytes ...mmon_signin_btn_icon_disabled_focus_light.9.png | Bin 5092 -> 0 bytes .../common_signin_btn_icon_disabled_light.9.png | Bin 4988 -> 0 bytes .../common_signin_btn_icon_focus_dark.9.png | Bin 6558 -> 0 bytes .../common_signin_btn_icon_focus_light.9.png | Bin 6216 -> 0 bytes .../common_signin_btn_icon_normal_dark.9.png | Bin 6530 -> 0 bytes .../common_signin_btn_icon_normal_light.9.png | Bin 6234 -> 0 bytes .../common_signin_btn_icon_pressed_dark.9.png | Bin 7015 -> 0 bytes .../common_signin_btn_icon_pressed_light.9.png | Bin 7352 -> 0 bytes .../common_signin_btn_text_disabled_dark.9.png | Bin 5637 -> 0 bytes ...ommon_signin_btn_text_disabled_focus_dark.9.png | Bin 5572 -> 0 bytes ...mmon_signin_btn_text_disabled_focus_light.9.png | Bin 5572 -> 0 bytes .../common_signin_btn_text_disabled_light.9.png | Bin 5637 -> 0 bytes .../common_signin_btn_text_focus_dark.9.png | Bin 6958 -> 0 bytes .../common_signin_btn_text_focus_light.9.png | Bin 6647 -> 0 bytes .../common_signin_btn_text_normal_dark.9.png | Bin 6975 -> 0 bytes .../common_signin_btn_text_normal_light.9.png | Bin 6713 -> 0 bytes .../common_signin_btn_text_pressed_dark.9.png | Bin 7569 -> 0 bytes .../common_signin_btn_text_pressed_light.9.png | Bin 7944 -> 0 bytes .../ic_plusone_medium_off_client.png | Bin 5828 -> 0 bytes .../ic_plusone_small_off_client.png | Bin 4279 -> 0 bytes .../ic_plusone_standard_off_client.png | Bin 7439 -> 0 bytes .../drawable-xxhdpi/ic_plusone_tall_off_client.png | Bin 6204 -> 0 bytes .../res/drawable/common_signin_btn_icon_dark.xml | 18 - .../res/drawable/common_signin_btn_icon_light.xml | 18 - .../res/drawable/common_signin_btn_text_dark.xml | 18 - .../res/drawable/common_signin_btn_text_light.xml | 18 - .../google_play_services/res/values-af/strings.xml | 31 - .../google_play_services/res/values-am/strings.xml | 31 - .../google_play_services/res/values-ar/strings.xml | 31 - .../google_play_services/res/values-be/strings.xml | 36 - .../google_play_services/res/values-bg/strings.xml | 31 - .../google_play_services/res/values-ca/strings.xml | 31 - .../google_play_services/res/values-cs/strings.xml | 31 - .../google_play_services/res/values-da/strings.xml | 31 - .../google_play_services/res/values-de/strings.xml | 31 - .../google_play_services/res/values-el/strings.xml | 31 - .../res/values-en-rGB/strings.xml | 31 - .../res/values-en-rIN/strings.xml | 31 - .../res/values-es-rUS/strings.xml | 31 - .../google_play_services/res/values-es/strings.xml | 31 - .../res/values-et-rEE/strings.xml | 31 - .../google_play_services/res/values-fa/strings.xml | 31 - .../google_play_services/res/values-fi/strings.xml | 31 - .../res/values-fr-rCA/strings.xml | 31 - .../google_play_services/res/values-fr/strings.xml | 31 - .../google_play_services/res/values-hi/strings.xml | 31 - .../google_play_services/res/values-hr/strings.xml | 31 - .../google_play_services/res/values-hu/strings.xml | 31 - .../res/values-hy-rAM/strings.xml | 31 - .../google_play_services/res/values-in/strings.xml | 31 - .../google_play_services/res/values-it/strings.xml | 31 - .../google_play_services/res/values-iw/strings.xml | 31 - .../google_play_services/res/values-ja/strings.xml | 31 - .../res/values-ka-rGE/strings.xml | 31 - .../res/values-km-rKH/strings.xml | 31 - .../google_play_services/res/values-ko/strings.xml | 31 - .../res/values-lo-rLA/strings.xml | 31 - .../google_play_services/res/values-lt/strings.xml | 31 - .../google_play_services/res/values-lv/strings.xml | 31 - .../res/values-mn-rMN/strings.xml | 31 - .../res/values-ms-rMY/strings.xml | 31 - .../google_play_services/res/values-nb/strings.xml | 31 - .../google_play_services/res/values-nl/strings.xml | 31 - .../google_play_services/res/values-pl/strings.xml | 31 - .../res/values-pt-rBR/strings.xml | 31 - .../res/values-pt-rPT/strings.xml | 31 - .../google_play_services/res/values-pt/strings.xml | 31 - .../google_play_services/res/values-ro/strings.xml | 31 - .../google_play_services/res/values-ru/strings.xml | 31 - .../google_play_services/res/values-sk/strings.xml | 31 - .../google_play_services/res/values-sl/strings.xml | 31 - .../google_play_services/res/values-sr/strings.xml | 31 - .../google_play_services/res/values-sv/strings.xml | 31 - .../google_play_services/res/values-sw/strings.xml | 31 - .../google_play_services/res/values-th/strings.xml | 31 - .../google_play_services/res/values-tl/strings.xml | 31 - .../google_play_services/res/values-tr/strings.xml | 31 - .../google_play_services/res/values-uk/strings.xml | 31 - .../google_play_services/res/values-vi/strings.xml | 31 - .../res/values-zh-rCN/strings.xml | 31 - .../res/values-zh-rHK/strings.xml | 31 - .../res/values-zh-rTW/strings.xml | 31 - .../google_play_services/res/values-zu/strings.xml | 31 - .../google_play_services/res/values/ads_attrs.xml | 22 - .../google_play_services/res/values/colors.xml | 14 - .../google_play_services/res/values/maps_attrs.xml | 26 - .../google_play_services/res/values/strings.xml | 111 -- .../google_play_services/res/values/version.xml | 4 - .../src/android/UnusedStub.java | 21 - .../libs/play_licensing/AndroidManifest.xml | 24 - .../aidl/ILicenseResultListener.aidl | 23 - .../play_licensing/aidl/ILicensingService.aidl | 25 - platform/android/libs/play_licensing/build.xml | 92 -- .../libs/play_licensing/proguard-project.txt | 20 - .../android/libs/play_licensing/project.properties | 12 - .../android/vending/licensing/AESObfuscator.java | 110 -- .../vending/licensing/APKExpansionPolicy.java | 397 ------ .../android/vending/licensing/DeviceLimiter.java | 47 - .../vending/licensing/ILicenseResultListener.java | 99 -- .../vending/licensing/ILicensingService.java | 99 -- .../android/vending/licensing/LicenseChecker.java | 351 ----- .../vending/licensing/LicenseCheckerCallback.java | 67 - .../vending/licensing/LicenseValidator.java | 224 ---- .../vending/licensing/NullDeviceLimiter.java | 32 - .../android/vending/licensing/Obfuscator.java | 48 - .../google/android/vending/licensing/Policy.java | 59 - .../vending/licensing/PreferenceObfuscator.java | 77 -- .../android/vending/licensing/ResponseData.java | 79 -- .../vending/licensing/ServerManagedPolicy.java | 276 ---- .../android/vending/licensing/StrictPolicy.java | 63 - .../vending/licensing/ValidationException.java | 33 - .../android/vending/licensing/util/Base64.java | 570 --------- .../licensing/util/Base64DecoderException.java | 32 - platform/android/project.properties.template | 15 - 331 files changed, 9104 insertions(+), 17367 deletions(-) create mode 100644 platform/android/build.gradle.template create mode 100644 platform/android/java/aidl/com/android/vending/billing/IInAppBillingService.aidl delete mode 100644 platform/android/java/ant.properties delete mode 100644 platform/android/java/build.properties delete mode 100644 platform/android/java/build.xml delete mode 100644 platform/android/java/default.properties create mode 100644 platform/android/java/gradle/wrapper/gradle-wrapper.jar create mode 100644 platform/android/java/gradle/wrapper/gradle-wrapper.properties create mode 100755 platform/android/java/gradlew create mode 100644 platform/android/java/gradlew.bat delete mode 100644 platform/android/java/my-release-key.keystore delete mode 100644 platform/android/java/proguard-project.txt delete mode 100644 platform/android/java/proguard.cfg create mode 100644 platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png create mode 100644 platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png create mode 100644 platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml create mode 100644 platform/android/java/res/values-v11/styles.xml create mode 100644 platform/android/java/res/values-v9/styles.xml create mode 100644 platform/android/java/res/values/styles.xml create mode 100644 platform/android/java/src/com/android/vending/licensing/AESObfuscator.java create mode 100644 platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java create mode 100644 platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java create mode 100644 platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl create mode 100644 platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.java create mode 100644 platform/android/java/src/com/android/vending/licensing/ILicensingService.aidl create mode 100644 platform/android/java/src/com/android/vending/licensing/ILicensingService.java create mode 100644 platform/android/java/src/com/android/vending/licensing/LicenseChecker.java create mode 100644 platform/android/java/src/com/android/vending/licensing/LicenseCheckerCallback.java create mode 100644 platform/android/java/src/com/android/vending/licensing/LicenseValidator.java create mode 100644 platform/android/java/src/com/android/vending/licensing/NullDeviceLimiter.java create mode 100644 platform/android/java/src/com/android/vending/licensing/Obfuscator.java create mode 100644 platform/android/java/src/com/android/vending/licensing/Policy.java create mode 100644 platform/android/java/src/com/android/vending/licensing/PreferenceObfuscator.java create mode 100644 platform/android/java/src/com/android/vending/licensing/ResponseData.java create mode 100644 platform/android/java/src/com/android/vending/licensing/ServerManagedPolicy.java create mode 100644 platform/android/java/src/com/android/vending/licensing/StrictPolicy.java create mode 100644 platform/android/java/src/com/android/vending/licensing/ValidationException.java create mode 100644 platform/android/java/src/com/android/vending/licensing/util/Base64.java create mode 100644 platform/android/java/src/com/android/vending/licensing/util/Base64DecoderException.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java create mode 100755 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java create mode 100755 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java create mode 100644 platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java delete mode 100644 platform/android/libs/apk_expansion/AndroidManifest.xml delete mode 100644 platform/android/libs/apk_expansion/build.xml delete mode 100644 platform/android/libs/apk_expansion/proguard-project.txt delete mode 100644 platform/android/libs/apk_expansion/project.properties delete mode 100644 platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png delete mode 100644 platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png delete mode 100644 platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml delete mode 100644 platform/android/libs/apk_expansion/res/values-v11/styles.xml delete mode 100644 platform/android/libs/apk_expansion/res/values-v9/styles.xml delete mode 100644 platform/android/libs/apk_expansion/res/values/strings.xml delete mode 100644 platform/android/libs/apk_expansion/res/values/styles.xml delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java delete mode 100755 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java delete mode 100755 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java delete mode 100644 platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java delete mode 100644 platform/android/libs/downloader_library/.classpath delete mode 100644 platform/android/libs/downloader_library/.settings/org.eclipse.jdt.core.prefs delete mode 100644 platform/android/libs/downloader_library/AndroidManifest.xml delete mode 100644 platform/android/libs/downloader_library/build.xml delete mode 100644 platform/android/libs/downloader_library/proguard-project.txt delete mode 100644 platform/android/libs/downloader_library/project.properties delete mode 100644 platform/android/libs/downloader_library/res/drawable-hdpi/notify_panel_notification_icon_bg.png delete mode 100644 platform/android/libs/downloader_library/res/drawable-mdpi/notify_panel_notification_icon_bg.png delete mode 100644 platform/android/libs/downloader_library/res/layout/status_bar_ongoing_event_progress_bar.xml delete mode 100644 platform/android/libs/downloader_library/res/values-v11/styles.xml delete mode 100644 platform/android/libs/downloader_library/res/values-v9/styles.xml delete mode 100644 platform/android/libs/downloader_library/res/values/strings.xml delete mode 100644 platform/android/libs/downloader_library/res/values/styles.xml delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Constants.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Helpers.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderService.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IStub.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/SystemFacade.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java delete mode 100755 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java delete mode 100755 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java delete mode 100644 platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java delete mode 100644 platform/android/libs/google_play_services/.classpath delete mode 100644 platform/android/libs/google_play_services/AndroidManifest.xml delete mode 100644 platform/android/libs/google_play_services/README.txt delete mode 100644 platform/android/libs/google_play_services/build.xml delete mode 100644 platform/android/libs/google_play_services/libs/google-play-services.jar delete mode 100644 platform/android/libs/google_play_services/libs/google-play-services.jar.properties delete mode 100644 platform/android/libs/google_play_services/proguard-project.txt delete mode 100644 platform/android/libs/google_play_services/proguard.txt delete mode 100644 platform/android/libs/google_play_services/project.properties delete mode 100644 platform/android/libs/google_play_services/res/color/common_signin_btn_text_dark.xml delete mode 100644 platform/android/libs/google_play_services/res/color/common_signin_btn_text_light.xml delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_medium_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_small_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_standard_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_tall_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_medium_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_small_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_standard_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_tall_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_medium_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_small_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_standard_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_tall_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_dark.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_light.9.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_medium_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_small_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_standard_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_tall_off_client.png delete mode 100644 platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_dark.xml delete mode 100644 platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_light.xml delete mode 100644 platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_dark.xml delete mode 100644 platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_light.xml delete mode 100644 platform/android/libs/google_play_services/res/values-af/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-am/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ar/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-be/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-bg/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ca/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-cs/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-da/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-de/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-el/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-en-rGB/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-en-rIN/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-es-rUS/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-es/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-et-rEE/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-fa/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-fi/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-fr-rCA/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-fr/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-hi/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-hr/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-hu/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-hy-rAM/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-in/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-it/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-iw/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ja/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ka-rGE/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-km-rKH/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ko/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-lo-rLA/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-lt/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-lv/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-mn-rMN/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ms-rMY/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-nb/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-nl/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-pl/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-pt-rBR/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-pt-rPT/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-pt/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ro/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-ru/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-sk/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-sl/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-sr/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-sv/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-sw/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-th/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-tl/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-tr/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-uk/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-vi/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-zh-rCN/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-zh-rHK/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-zh-rTW/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values-zu/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values/ads_attrs.xml delete mode 100644 platform/android/libs/google_play_services/res/values/colors.xml delete mode 100644 platform/android/libs/google_play_services/res/values/maps_attrs.xml delete mode 100644 platform/android/libs/google_play_services/res/values/strings.xml delete mode 100644 platform/android/libs/google_play_services/res/values/version.xml delete mode 100644 platform/android/libs/google_play_services/src/android/UnusedStub.java delete mode 100644 platform/android/libs/play_licensing/AndroidManifest.xml delete mode 100644 platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl delete mode 100644 platform/android/libs/play_licensing/aidl/ILicensingService.aidl delete mode 100644 platform/android/libs/play_licensing/build.xml delete mode 100644 platform/android/libs/play_licensing/proguard-project.txt delete mode 100644 platform/android/libs/play_licensing/project.properties delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java delete mode 100644 platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java delete mode 100644 platform/android/project.properties.template (limited to 'platform/android') diff --git a/platform/android/AndroidManifest.xml.template b/platform/android/AndroidManifest.xml.template index 02645aff7f..9fd52f6438 100644 --- a/platform/android/AndroidManifest.xml.template +++ b/platform/android/AndroidManifest.xml.template @@ -189,7 +189,7 @@ $$ADD_PERMISSION_CHUNKS$$ - + diff --git a/platform/android/SCsub b/platform/android/SCsub index 834ee58adc..30cb9d9660 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -37,19 +37,56 @@ prog = None abspath=env.Dir(".").abspath -pp_basein = open(abspath+"/project.properties.template","rb") -pp_baseout = open(abspath+"/java/project.properties","wb") -pp_baseout.write( pp_basein.read() ) +gradle_basein = open(abspath+"/build.gradle.template","rb") +gradle_baseout = open(abspath+"/java/build.gradle","wb") -refcount=1 +gradle_text = gradle_basein.read() -for x in env.android_source_modules: - pp_baseout.write("android.library.reference."+str(refcount)+"="+x+"\n") - refcount+=1 +gradle_maven_repos_text="" +for x in env.android_maven_repos: + gradle_maven_repos_text+=x+"\n" -pp_baseout.close() +gradle_maven_dependencies_text="" + +for x in env.android_dependencies: + gradle_maven_dependencies_text+=x+"\n" + +gradle_java_dirs_text="" + +for x in env.android_java_dirs: + gradle_java_dirs_text+=",'"+x+"'" + + +gradle_res_dirs_text="" + +for x in env.android_res_dirs: + gradle_res_dirs_text+=",'"+x+"'" + +gradle_aidl_dirs_text="" + +for x in env.android_aidl_dirs: + gradle_aidl_dirs_text+=",'"+x+"'" + +gradle_jni_dirs_text="" + +for x in env.android_jni_dirs: + gradle_jni_dirs_text+=",'"+x+"'" + +gradle_asset_dirs_text="" + +gradle_text = gradle_text.replace("$$GRADLE_REPOSITORY_URLS$$",gradle_maven_repos_text) +gradle_text = gradle_text.replace("$$GRADLE_DEPENDENCIES$$",gradle_maven_dependencies_text) +gradle_text = gradle_text.replace("$$GRADLE_JAVA_DIRS$$",gradle_java_dirs_text) +gradle_text = gradle_text.replace("$$GRADLE_RES_DIRS$$",gradle_res_dirs_text) +gradle_text = gradle_text.replace("$$GRADLE_ASSET_DIRS$$",gradle_asset_dirs_text) +gradle_text = gradle_text.replace("$$GRADLE_AIDL_DIRS$$",gradle_aidl_dirs_text) +gradle_text = gradle_text.replace("$$GRADLE_JNI_DIRS$$",gradle_jni_dirs_text) + + +gradle_baseout.write( gradle_text ) +gradle_baseout.close() pp_basein = open(abspath+"/AndroidManifest.xml.template","rb") @@ -61,13 +98,6 @@ manifest = manifest.replace("$$ADD_APPATTRIBUTE_CHUNKS$$",env.android_appattribu pp_baseout.write( manifest ) -for x in env.android_source_files: - shutil.copy(x,abspath+"/java/src/com/android/godot") - -for x in env.android_module_libraries: - shutil.copy(x,abspath+"/java/libs") - - env_android.SharedLibrary("#bin/libgodot",[android_objects],SHLIBSUFFIX=env["SHLIBSUFFIX"]) #env.Command('#bin/libgodot_android.so', '#platform/android/libgodot_android.so', Copy('bin/libgodot_android.so', 'platform/android/libgodot_android.so')) diff --git a/platform/android/build.gradle.template b/platform/android/build.gradle.template new file mode 100644 index 0000000000..fdc0e751e3 --- /dev/null +++ b/platform/android/build.gradle.template @@ -0,0 +1,72 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.0' + } +} + +apply plugin: 'com.android.application' + +allprojects { + repositories { + mavenCentral() + maven { + $$GRADLE_REPOSITORY_URLS$$ + } + } +} + +dependencies { + + $$GRADLE_DEPENDENCIES$$ +} + +android { + + lintOptions { + abortOnError false + } + + compileSdkVersion 19 + buildToolsVersion "19.1" + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } + defaultConfig { + minSdkVersion 14 + targetSdkVersion 19 + } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src', + $$GRADLE_JAVA_DIRS$$ + ] + resources.srcDirs = [ + 'res' + $$GRADLE_RES_DIRS$$ + ] + res.srcDirs = ['res'] + // libs.srcDirs = ['libs'] + aidl.srcDirs = [ + 'aidl' + $$GRADLE_AIDL_DIRS$$ + ] + assets.srcDirs = [ + 'assets' + $$GRADLE_ASSET_DIRS$$ + ] + jniLibs.srcDirs = [ + 'libs' + $$GRADLE_JNI_DIRS$$ + ] + } + + } + + +} diff --git a/platform/android/detect.py b/platform/android/detect.py index 66097a5149..ee3dc119c2 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -106,10 +106,6 @@ def configure(env): #env['SPAWN'] = methods.win32_spawn env['SHLIBSUFFIX'] = '.so' - #env.android_source_modules.append("../libs/apk_expansion") - env.android_source_modules.append("../libs/google_play_services") - env.android_source_modules.append("../libs/downloader_library") - env.android_source_modules.append("../libs/play_licensing") neon_text="" if env["android_arch"]=="armv7" and env['android_neon']=='yes': diff --git a/platform/android/java/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/aidl/com/android/vending/billing/IInAppBillingService.aidl new file mode 100644 index 0000000000..2a492f7845 --- /dev/null +++ b/platform/android/java/aidl/com/android/vending/billing/IInAppBillingService.aidl @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.vending.billing; + +import android.os.Bundle; + +/** + * InAppBillingService is the service that provides in-app billing version 3 and beyond. + * This service provides the following features: + * 1. Provides a new API to get details of in-app items published for the app including + * price, type, title and description. + * 2. The purchase flow is synchronous and purchase information is available immediately + * after it completes. + * 3. Purchase information of in-app purchases is maintained within the Google Play system + * till the purchase is consumed. + * 4. An API to consume a purchase of an inapp item. All purchases of one-time + * in-app items are consumable and thereafter can be purchased again. + * 5. An API to get current purchases of the user immediately. This will not contain any + * consumed purchases. + * + * All calls will give a response code with the following possible values + * RESULT_OK = 0 - success + * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog + * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested + * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase + * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API + * RESULT_ERROR = 6 - Fatal error during the API action + * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned + * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned + */ +interface IInAppBillingService { + /** + * Checks support for the requested billing API version, package and in-app type. + * Minimum API version supported by this interface is 3. + * @param apiVersion the billing version which the app is using + * @param packageName the package name of the calling app + * @param type type of the in-app item being purchased "inapp" for one-time purchases + * and "subs" for subscription. + * @return RESULT_OK(0) on success, corresponding result code on failures + */ + int isBillingSupported(int apiVersion, String packageName, String type); + + /** + * Provides details of a list of SKUs + * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle + * with a list JSON strings containing the productId, price, title and description. + * This API can be called with a maximum of 20 SKUs. + * @param apiVersion billing API version that the Third-party is using + * @param packageName the package name of the calling app + * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "DETAILS_LIST" with a StringArrayList containing purchase information + * in JSON format similar to: + * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00", + * "title : "Example Title", "description" : "This is an example description" }' + */ + Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); + + /** + * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, + * the type, a unique purchase token and an optional developer payload. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param sku the SKU of the in-app item as published in the developer console + * @param type the type of the in-app item ("inapp" for one-time purchases + * and "subs" for subscription). + * @param developerPayload optional argument to be sent back with the purchase information + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "BUY_INTENT" - PendingIntent to start the purchase flow + * + * The Pending intent should be launched with startIntentSenderForResult. When purchase flow + * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. + * If the purchase is successful, the result data will contain the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "INAPP_PURCHASE_DATA" - String in JSON format similar to + * '{"orderId":"12999763169054705758.1371079406387615", + * "packageName":"com.example.app", + * "productId":"exampleSku", + * "purchaseTime":1345678900000, + * "purchaseToken" : "122333444455555", + * "developerPayload":"example developer payload" }' + * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that + * was signed with the private key of the developer + * TODO: change this to app-specific keys. + */ + Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, + String developerPayload); + + /** + * Returns the current SKUs owned by the user of the type and package name specified along with + * purchase information and a signature of the data to be validated. + * This will return all SKUs that have been purchased in V3 and managed items purchased using + * V1 and V2 that have not been consumed. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param type the type of the in-app items being requested + * ("inapp" for one-time purchases and "subs" for subscription). + * @param continuationToken to be set as null for the first call, if the number of owned + * skus are too many, a continuationToken is returned in the response bundle. + * This method can be called again with the continuation token to get the next set of + * owned skus. + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs + * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information + * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures + * of the purchase information + * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the + * next set of in-app purchases. Only set if the + * user has more owned skus than the current list. + */ + Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); + + /** + * Consume the last purchase of the given SKU. This will result in this item being removed + * from all subsequent responses to getPurchases() and allow re-purchase of this item. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param purchaseToken token in the purchase information JSON that identifies the purchase + * to be consumed + * @return 0 if consumption succeeded. Appropriate error values for failures. + */ + int consumePurchase(int apiVersion, String packageName, String purchaseToken); +} diff --git a/platform/android/java/ant.properties b/platform/android/java/ant.properties deleted file mode 100644 index b0971e891e..0000000000 --- a/platform/android/java/ant.properties +++ /dev/null @@ -1,17 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked into Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - diff --git a/platform/android/java/build.properties b/platform/android/java/build.properties deleted file mode 100644 index ee52d86d94..0000000000 --- a/platform/android/java/build.properties +++ /dev/null @@ -1,17 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - diff --git a/platform/android/java/build.xml b/platform/android/java/build.xml deleted file mode 100644 index 424e2827dc..0000000000 --- a/platform/android/java/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/java/default.properties b/platform/android/java/default.properties deleted file mode 100644 index e2e8061f26..0000000000 --- a/platform/android/java/default.properties +++ /dev/null @@ -1,11 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "build.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-8 diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.jar b/platform/android/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..8c0fb64a86 Binary files /dev/null and b/platform/android/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..0c71e760dc --- /dev/null +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/platform/android/java/gradlew b/platform/android/java/gradlew new file mode 100755 index 0000000000..91a7e269e1 --- /dev/null +++ b/platform/android/java/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/platform/android/java/gradlew.bat b/platform/android/java/gradlew.bat new file mode 100644 index 0000000000..aec99730b4 --- /dev/null +++ b/platform/android/java/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/platform/android/java/my-release-key.keystore b/platform/android/java/my-release-key.keystore deleted file mode 100644 index 410cccd865..0000000000 Binary files a/platform/android/java/my-release-key.keystore and /dev/null differ diff --git a/platform/android/java/proguard-project.txt b/platform/android/java/proguard-project.txt deleted file mode 100644 index f2fe1559a2..0000000000 --- a/platform/android/java/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/platform/android/java/proguard.cfg b/platform/android/java/proguard.cfg deleted file mode 100644 index 12dd0392c0..0000000000 --- a/platform/android/java/proguard.cfg +++ /dev/null @@ -1,36 +0,0 @@ --optimizationpasses 5 --dontusemixedcaseclassnames --dontskipnonpubliclibraryclasses --dontpreverify --verbose --optimizations !code/simplification/arithmetic,!field/*,!class/merging/* - --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider --keep public class * extends android.app.backup.BackupAgentHelper --keep public class * extends android.preference.Preference --keep public class com.android.vending.licensing.ILicensingService - --keepclasseswithmembernames class * { - native ; -} - --keepclasseswithmembernames class * { - public (android.content.Context, android.util.AttributeSet); -} - --keepclasseswithmembernames class * { - public (android.content.Context, android.util.AttributeSet, int); -} - --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); -} - --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; -} diff --git a/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png new file mode 100644 index 0000000000..f5b762ecf3 Binary files /dev/null and b/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png differ diff --git a/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png new file mode 100644 index 0000000000..9ecb8af06c Binary files /dev/null and b/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png differ diff --git a/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml new file mode 100644 index 0000000000..23bac02294 --- /dev/null +++ b/platform/android/java/res/layout/status_bar_ongoing_event_progress_bar.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/android/java/res/values-v11/styles.xml b/platform/android/java/res/values-v11/styles.xml new file mode 100644 index 0000000000..f2013bc0bf --- /dev/null +++ b/platform/android/java/res/values-v11/styles.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/platform/android/java/res/values-v9/styles.xml b/platform/android/java/res/values-v9/styles.xml new file mode 100644 index 0000000000..736e77a5d6 --- /dev/null +++ b/platform/android/java/res/values-v9/styles.xml @@ -0,0 +1,5 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/platform/android/java/src/com/android/godot/Dictionary.java b/platform/android/java/src/com/android/godot/Dictionary.java index 0536efcd0e..4ed12f5818 100644 --- a/platform/android/java/src/com/android/godot/Dictionary.java +++ b/platform/android/java/src/com/android/godot/Dictionary.java @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/com/android/godot/Godot.java b/platform/android/java/src/com/android/godot/Godot.java index e104b2b8b6..4c5a313576 100644 --- a/platform/android/java/src/com/android/godot/Godot.java +++ b/platform/android/java/src/com/android/godot/Godot.java @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/com/android/godot/GodotIO.java b/platform/android/java/src/com/android/godot/GodotIO.java index 7e26fdce5f..a7dc0c2f75 100644 --- a/platform/android/java/src/com/android/godot/GodotIO.java +++ b/platform/android/java/src/com/android/godot/GodotIO.java @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/com/android/godot/GodotLib.java b/platform/android/java/src/com/android/godot/GodotLib.java index 4633b55021..3d870b3b1f 100644 --- a/platform/android/java/src/com/android/godot/GodotLib.java +++ b/platform/android/java/src/com/android/godot/GodotLib.java @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/com/android/godot/GodotView.java b/platform/android/java/src/com/android/godot/GodotView.java index 24dc83a7d9..bc249d46c6 100644 --- a/platform/android/java/src/com/android/godot/GodotView.java +++ b/platform/android/java/src/com/android/godot/GodotView.java @@ -5,7 +5,7 @@ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ diff --git a/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java b/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java new file mode 100644 index 0000000000..ee12c68deb --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/AESObfuscator.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +import com.google.android.vending.licensing.util.Base64; +import com.google.android.vending.licensing.util.Base64DecoderException; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.spec.KeySpec; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * An Obfuscator that uses AES to encrypt data. + */ +public class AESObfuscator implements Obfuscator { + private static final String UTF8 = "UTF-8"; + private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC"; + private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + private static final byte[] IV = + { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 }; + private static final String header = "com.android.vending.licensing.AESObfuscator-1|"; + + private Cipher mEncryptor; + private Cipher mDecryptor; + + /** + * @param salt an array of random bytes to use for each (un)obfuscation + * @param applicationId application identifier, e.g. the package name + * @param deviceId device identifier. Use as many sources as possible to + * create this unique identifier. + */ + public AESObfuscator(byte[] salt, String applicationId, String deviceId) { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); + KeySpec keySpec = + new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); + SecretKey tmp = factory.generateSecret(keySpec); + SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); + mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM); + mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV)); + mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM); + mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV)); + } catch (GeneralSecurityException e) { + // This can't happen on a compatible Android device. + throw new RuntimeException("Invalid environment", e); + } + } + + public String obfuscate(String original, String key) { + if (original == null) { + return null; + } + try { + // Header is appended as an integrity check + return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Invalid environment", e); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Invalid environment", e); + } + } + + public String unobfuscate(String obfuscated, String key) throws ValidationException { + if (obfuscated == null) { + return null; + } + try { + String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8); + // Check for presence of header. This serves as a final integrity check, for cases + // where the block size is correct during decryption. + int headerIndex = result.indexOf(header+key); + if (headerIndex != 0) { + throw new ValidationException("Header not found (invalid data or key)" + ":" + + obfuscated); + } + return result.substring(header.length()+key.length(), result.length()); + } catch (Base64DecoderException e) { + throw new ValidationException(e.getMessage() + ":" + obfuscated); + } catch (IllegalBlockSizeException e) { + throw new ValidationException(e.getMessage() + ":" + obfuscated); + } catch (BadPaddingException e) { + throw new ValidationException(e.getMessage() + ":" + obfuscated); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Invalid environment", e); + } + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java b/platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java new file mode 100644 index 0000000000..17cc7a7cfd --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/APKExpansionPolicy.java @@ -0,0 +1,397 @@ + +package com.google.android.vending.licensing; + +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +/** + * Default policy. All policy decisions are based off of response data received + * from the licensing service. Specifically, the licensing server sends the + * following information: response validity period, error retry period, and + * error retry count. + *

+ * These values will vary based on the the way the application is configured in + * the Android Market publishing console, such as whether the application is + * marked as free or is within its refund period, as well as how often an + * application is checking with the licensing service. + *

+ * Developers who need more fine grained control over their application's + * licensing policy should implement a custom Policy. + */ +public class APKExpansionPolicy implements Policy { + + private static final String TAG = "APKExpansionPolicy"; + private static final String PREFS_FILE = "com.android.vending.licensing.APKExpansionPolicy"; + private static final String PREF_LAST_RESPONSE = "lastResponse"; + private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; + private static final String PREF_RETRY_UNTIL = "retryUntil"; + private static final String PREF_MAX_RETRIES = "maxRetries"; + private static final String PREF_RETRY_COUNT = "retryCount"; + private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; + private static final String DEFAULT_RETRY_UNTIL = "0"; + private static final String DEFAULT_MAX_RETRIES = "0"; + private static final String DEFAULT_RETRY_COUNT = "0"; + + private static final long MILLIS_PER_MINUTE = 60 * 1000; + + private long mValidityTimestamp; + private long mRetryUntil; + private long mMaxRetries; + private long mRetryCount; + private long mLastResponseTime = 0; + private int mLastResponse; + private PreferenceObfuscator mPreferences; + private Vector mExpansionURLs = new Vector(); + private Vector mExpansionFileNames = new Vector(); + private Vector mExpansionFileSizes = new Vector(); + + /** + * The design of the protocol supports n files. Currently the market can + * only deliver two files. To accommodate this, we have these two constants, + * but the order is the only relevant thing here. + */ + public static final int MAIN_FILE_URL_INDEX = 0; + public static final int PATCH_FILE_URL_INDEX = 1; + + /** + * @param context The context for the current application + * @param obfuscator An obfuscator to be used with preferences. + */ + public APKExpansionPolicy(Context context, Obfuscator obfuscator) { + // Import old values + SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); + mPreferences = new PreferenceObfuscator(sp, obfuscator); + mLastResponse = Integer.parseInt( + mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); + mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, + DEFAULT_VALIDITY_TIMESTAMP)); + mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); + mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); + mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); + } + + /** + * We call this to guarantee that we fetch a fresh policy from the server. + * This is to be used if the URL is invalid. + */ + public void resetPolicy() { + mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)); + setRetryUntil(DEFAULT_RETRY_UNTIL); + setMaxRetries(DEFAULT_MAX_RETRIES); + setRetryCount(Long.parseLong(DEFAULT_RETRY_COUNT)); + setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); + mPreferences.commit(); + } + + /** + * Process a new response from the license server. + *

+ * This data will be used for computing future policy decisions. The + * following parameters are processed: + *

    + *
  • VT: the timestamp that the client should consider the response valid + * until + *
  • GT: the timestamp that the client should ignore retry errors until + *
  • GR: the number of retry errors that the client should ignore + *
+ * + * @param response the result from validating the server response + * @param rawData the raw server response data + */ + public void processServerResponse(int response, + com.google.android.vending.licensing.ResponseData rawData) { + + // Update retry counter + if (response != Policy.RETRY) { + setRetryCount(0); + } else { + setRetryCount(mRetryCount + 1); + } + + if (response == Policy.LICENSED) { + // Update server policy data + Map extras = decodeExtras(rawData.extra); + mLastResponse = response; + setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE)); + Set keys = extras.keySet(); + for (String key : keys) { + if (key.equals("VT")) { + setValidityTimestamp(extras.get(key)); + } else if (key.equals("GT")) { + setRetryUntil(extras.get(key)); + } else if (key.equals("GR")) { + setMaxRetries(extras.get(key)); + } else if (key.startsWith("FILE_URL")) { + int index = Integer.parseInt(key.substring("FILE_URL".length())) - 1; + setExpansionURL(index, extras.get(key)); + } else if (key.startsWith("FILE_NAME")) { + int index = Integer.parseInt(key.substring("FILE_NAME".length())) - 1; + setExpansionFileName(index, extras.get(key)); + } else if (key.startsWith("FILE_SIZE")) { + int index = Integer.parseInt(key.substring("FILE_SIZE".length())) - 1; + setExpansionFileSize(index, Long.parseLong(extras.get(key))); + } + } + } else if (response == Policy.NOT_LICENSED) { + // Clear out stale policy data + setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); + setRetryUntil(DEFAULT_RETRY_UNTIL); + setMaxRetries(DEFAULT_MAX_RETRIES); + } + + setLastResponse(response); + mPreferences.commit(); + } + + /** + * Set the last license response received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param l the response + */ + private void setLastResponse(int l) { + mLastResponseTime = System.currentTimeMillis(); + mLastResponse = l; + mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); + } + + /** + * Set the current retry count and add to preferences. You must manually + * call PreferenceObfuscator.commit() to commit these changes to disk. + * + * @param c the new retry count + */ + private void setRetryCount(long c) { + mRetryCount = c; + mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); + } + + public long getRetryCount() { + return mRetryCount; + } + + /** + * Set the last validity timestamp (VT) received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param validityTimestamp the VT string received + */ + private void setValidityTimestamp(String validityTimestamp) { + Long lValidityTimestamp; + try { + lValidityTimestamp = Long.parseLong(validityTimestamp); + } catch (NumberFormatException e) { + // No response or not parseable, expire in one minute. + Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); + lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; + validityTimestamp = Long.toString(lValidityTimestamp); + } + + mValidityTimestamp = lValidityTimestamp; + mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); + } + + public long getValidityTimestamp() { + return mValidityTimestamp; + } + + /** + * Set the retry until timestamp (GT) received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param retryUntil the GT string received + */ + private void setRetryUntil(String retryUntil) { + Long lRetryUntil; + try { + lRetryUntil = Long.parseLong(retryUntil); + } catch (NumberFormatException e) { + // No response or not parseable, expire immediately + Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); + retryUntil = "0"; + lRetryUntil = 0l; + } + + mRetryUntil = lRetryUntil; + mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); + } + + public long getRetryUntil() { + return mRetryUntil; + } + + /** + * Set the max retries value (GR) as received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param maxRetries the GR string received + */ + private void setMaxRetries(String maxRetries) { + Long lMaxRetries; + try { + lMaxRetries = Long.parseLong(maxRetries); + } catch (NumberFormatException e) { + // No response or not parseable, expire immediately + Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); + maxRetries = "0"; + lMaxRetries = 0l; + } + + mMaxRetries = lMaxRetries; + mPreferences.putString(PREF_MAX_RETRIES, maxRetries); + } + + public long getMaxRetries() { + return mMaxRetries; + } + + /** + * Gets the count of expansion URLs. Since expansionURLs are not committed + * to preferences, this will return zero if there has been no LVL fetch + * in the current session. + * + * @return the number of expansion URLs. (0,1,2) + */ + public int getExpansionURLCount() { + return mExpansionURLs.size(); + } + + /** + * Gets the expansion URL. Since these URLs are not committed to + * preferences, this will always return null if there has not been an LVL + * fetch in the current session. + * + * @param index the index of the URL to fetch. This value will be either + * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX + * @param URL the URL to set + */ + public String getExpansionURL(int index) { + if (index < mExpansionURLs.size()) { + return mExpansionURLs.elementAt(index); + } + return null; + } + + /** + * Sets the expansion URL. Expansion URL's are not committed to preferences, + * but are instead intended to be stored when the license response is + * processed by the front-end. + * + * @param index the index of the expansion URL. This value will be either + * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX + * @param URL the URL to set + */ + public void setExpansionURL(int index, String URL) { + if (index >= mExpansionURLs.size()) { + mExpansionURLs.setSize(index + 1); + } + mExpansionURLs.set(index, URL); + } + + public String getExpansionFileName(int index) { + if (index < mExpansionFileNames.size()) { + return mExpansionFileNames.elementAt(index); + } + return null; + } + + public void setExpansionFileName(int index, String name) { + if (index >= mExpansionFileNames.size()) { + mExpansionFileNames.setSize(index + 1); + } + mExpansionFileNames.set(index, name); + } + + public long getExpansionFileSize(int index) { + if (index < mExpansionFileSizes.size()) { + return mExpansionFileSizes.elementAt(index); + } + return -1; + } + + public void setExpansionFileSize(int index, long size) { + if (index >= mExpansionFileSizes.size()) { + mExpansionFileSizes.setSize(index + 1); + } + mExpansionFileSizes.set(index, size); + } + + /** + * {@inheritDoc} This implementation allows access if either:
+ *
    + *
  1. a LICENSED response was received within the validity period + *
  2. a RETRY response was received in the last minute, and we are under + * the RETRY count or in the RETRY period. + *
+ */ + public boolean allowAccess() { + long ts = System.currentTimeMillis(); + if (mLastResponse == Policy.LICENSED) { + // Check if the LICENSED response occurred within the validity + // timeout. + if (ts <= mValidityTimestamp) { + // Cached LICENSED response is still valid. + return true; + } + } else if (mLastResponse == Policy.RETRY && + ts < mLastResponseTime + MILLIS_PER_MINUTE) { + // Only allow access if we are within the retry period or we haven't + // used up our + // max retries. + return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); + } + return false; + } + + private Map decodeExtras(String extras) { + Map results = new HashMap(); + try { + URI rawExtras = new URI("?" + extras); + List extraList = URLEncodedUtils.parse(rawExtras, "UTF-8"); + for (NameValuePair item : extraList) { + String name = item.getName(); + int i = 0; + while (results.containsKey(name)) { + name = item.getName() + ++i; + } + results.put(name, item.getValue()); + } + } catch (URISyntaxException e) { + Log.w(TAG, "Invalid syntax error while decoding extras data from server."); + } + return results; + } + +} diff --git a/platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java b/platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java new file mode 100644 index 0000000000..e5c5e2d7ca --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/DeviceLimiter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +/** + * Allows the developer to limit the number of devices using a single license. + *

+ * The LICENSED response from the server contains a user identifier unique to + * the <application, user> pair. The developer can send this identifier + * to their own server along with some device identifier (a random number + * generated and stored once per application installation, + * {@link android.telephony.TelephonyManager#getDeviceId getDeviceId}, + * {@link android.provider.Settings.Secure#ANDROID_ID ANDROID_ID}, etc). + * The more sources used to identify the device, the harder it will be for an + * attacker to spoof. + *

+ * The server can look at the <application, user, device id> tuple and + * restrict a user's application license to run on at most 10 different devices + * in a week (for example). We recommend not being too restrictive because a + * user might legitimately have multiple devices or be in the process of + * changing phones. This will catch egregious violations of multiple people + * sharing one license. + */ +public interface DeviceLimiter { + + /** + * Checks if this device is allowed to use the given user's license. + * + * @param userId the user whose license the server responded with + * @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs + */ + int isDeviceAllowed(String userId); +} diff --git a/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl b/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl new file mode 100644 index 0000000000..c816558afc --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.vending.licensing; + +// Android library projects do not yet support AIDL, so this has been +// precompiled into the src directory. +oneway interface ILicenseResultListener { + void verifyLicense(int responseCode, String signedData, String signature); +} diff --git a/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.java b/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.java new file mode 100644 index 0000000000..d90d6eac7b --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/ILicenseResultListener.java @@ -0,0 +1,99 @@ +/* + * This file is auto-generated. DO NOT MODIFY. + * Original file: aidl/ILicenseResultListener.aidl + */ +package com.google.android.vending.licensing; +import java.lang.String; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Binder; +import android.os.Parcel; +public interface ILicenseResultListener extends android.os.IInterface +{ +/** Local-side IPC implementation stub class. */ +public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicenseResultListener +{ +private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicenseResultListener"; +/** Construct the stub at attach it to the interface. */ +public Stub() +{ +this.attachInterface(this, DESCRIPTOR); +} +/** + * Cast an IBinder object into an ILicenseResultListener interface, + * generating a proxy if needed. + */ +public static com.google.android.vending.licensing.ILicenseResultListener asInterface(android.os.IBinder obj) +{ +if ((obj==null)) { +return null; +} +android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); +if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicenseResultListener))) { +return ((com.google.android.vending.licensing.ILicenseResultListener)iin); +} +return new com.google.android.vending.licensing.ILicenseResultListener.Stub.Proxy(obj); +} +public android.os.IBinder asBinder() +{ +return this; +} +public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException +{ +switch (code) +{ +case INTERFACE_TRANSACTION: +{ +reply.writeString(DESCRIPTOR); +return true; +} +case TRANSACTION_verifyLicense: +{ +data.enforceInterface(DESCRIPTOR); +int _arg0; +_arg0 = data.readInt(); +java.lang.String _arg1; +_arg1 = data.readString(); +java.lang.String _arg2; +_arg2 = data.readString(); +this.verifyLicense(_arg0, _arg1, _arg2); +return true; +} +} +return super.onTransact(code, data, reply, flags); +} +private static class Proxy implements com.google.android.vending.licensing.ILicenseResultListener +{ +private android.os.IBinder mRemote; +Proxy(android.os.IBinder remote) +{ +mRemote = remote; +} +public android.os.IBinder asBinder() +{ +return mRemote; +} +public java.lang.String getInterfaceDescriptor() +{ +return DESCRIPTOR; +} +public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException +{ +android.os.Parcel _data = android.os.Parcel.obtain(); +try { +_data.writeInterfaceToken(DESCRIPTOR); +_data.writeInt(responseCode); +_data.writeString(signedData); +_data.writeString(signature); +mRemote.transact(Stub.TRANSACTION_verifyLicense, _data, null, IBinder.FLAG_ONEWAY); +} +finally { +_data.recycle(); +} +} +} +static final int TRANSACTION_verifyLicense = (IBinder.FIRST_CALL_TRANSACTION + 0); +} +public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException; +} diff --git a/platform/android/java/src/com/android/vending/licensing/ILicensingService.aidl b/platform/android/java/src/com/android/vending/licensing/ILicensingService.aidl new file mode 100644 index 0000000000..664510ce0c --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/ILicensingService.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.vending.licensing; + +import com.android.vending.licensing.ILicenseResultListener; + +// Android library projects do not yet support AIDL, so this has been +// precompiled into the src directory. +oneway interface ILicensingService { + void checkLicense(long nonce, String packageName, in ILicenseResultListener listener); +} diff --git a/platform/android/java/src/com/android/vending/licensing/ILicensingService.java b/platform/android/java/src/com/android/vending/licensing/ILicensingService.java new file mode 100644 index 0000000000..95599544e4 --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/ILicensingService.java @@ -0,0 +1,99 @@ +/* + * This file is auto-generated. DO NOT MODIFY. + * Original file: aidl/ILicensingService.aidl + */ +package com.google.android.vending.licensing; +import java.lang.String; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Binder; +import android.os.Parcel; +public interface ILicensingService extends android.os.IInterface +{ +/** Local-side IPC implementation stub class. */ +public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicensingService +{ +private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicensingService"; +/** Construct the stub at attach it to the interface. */ +public Stub() +{ +this.attachInterface(this, DESCRIPTOR); +} +/** + * Cast an IBinder object into an ILicensingService interface, + * generating a proxy if needed. + */ +public static com.google.android.vending.licensing.ILicensingService asInterface(android.os.IBinder obj) +{ +if ((obj==null)) { +return null; +} +android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); +if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicensingService))) { +return ((com.google.android.vending.licensing.ILicensingService)iin); +} +return new com.google.android.vending.licensing.ILicensingService.Stub.Proxy(obj); +} +public android.os.IBinder asBinder() +{ +return this; +} +public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException +{ +switch (code) +{ +case INTERFACE_TRANSACTION: +{ +reply.writeString(DESCRIPTOR); +return true; +} +case TRANSACTION_checkLicense: +{ +data.enforceInterface(DESCRIPTOR); +long _arg0; +_arg0 = data.readLong(); +java.lang.String _arg1; +_arg1 = data.readString(); +com.google.android.vending.licensing.ILicenseResultListener _arg2; +_arg2 = com.google.android.vending.licensing.ILicenseResultListener.Stub.asInterface(data.readStrongBinder()); +this.checkLicense(_arg0, _arg1, _arg2); +return true; +} +} +return super.onTransact(code, data, reply, flags); +} +private static class Proxy implements com.google.android.vending.licensing.ILicensingService +{ +private android.os.IBinder mRemote; +Proxy(android.os.IBinder remote) +{ +mRemote = remote; +} +public android.os.IBinder asBinder() +{ +return mRemote; +} +public java.lang.String getInterfaceDescriptor() +{ +return DESCRIPTOR; +} +public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException +{ +android.os.Parcel _data = android.os.Parcel.obtain(); +try { +_data.writeInterfaceToken(DESCRIPTOR); +_data.writeLong(nonce); +_data.writeString(packageName); +_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null))); +mRemote.transact(Stub.TRANSACTION_checkLicense, _data, null, IBinder.FLAG_ONEWAY); +} +finally { +_data.recycle(); +} +} +} +static final int TRANSACTION_checkLicense = (IBinder.FIRST_CALL_TRANSACTION + 0); +} +public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException; +} diff --git a/platform/android/java/src/com/android/vending/licensing/LicenseChecker.java b/platform/android/java/src/com/android/vending/licensing/LicenseChecker.java new file mode 100644 index 0000000000..0b1c4b6cca --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/LicenseChecker.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +import com.google.android.vending.licensing.util.Base64; +import com.google.android.vending.licensing.util.Base64DecoderException; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.RemoteException; +import android.provider.Settings.Secure; +import android.util.Log; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Set; + +/** + * Client library for Android Market license verifications. + *

+ * The LicenseChecker is configured via a {@link Policy} which contains the + * logic to determine whether a user should have access to the application. For + * example, the Policy can define a threshold for allowable number of server or + * client failures before the library reports the user as not having access. + *

+ * Must also provide the Base64-encoded RSA public key associated with your + * developer account. The public key is obtainable from the publisher site. + */ +public class LicenseChecker implements ServiceConnection { + private static final String TAG = "LicenseChecker"; + + private static final String KEY_FACTORY_ALGORITHM = "RSA"; + + // Timeout value (in milliseconds) for calls to service. + private static final int TIMEOUT_MS = 10 * 1000; + + private static final SecureRandom RANDOM = new SecureRandom(); + private static final boolean DEBUG_LICENSE_ERROR = false; + + private ILicensingService mService; + + private PublicKey mPublicKey; + private final Context mContext; + private final Policy mPolicy; + /** + * A handler for running tasks on a background thread. We don't want license + * processing to block the UI thread. + */ + private Handler mHandler; + private final String mPackageName; + private final String mVersionCode; + private final Set mChecksInProgress = new HashSet(); + private final Queue mPendingChecks = new LinkedList(); + + /** + * @param context a Context + * @param policy implementation of Policy + * @param encodedPublicKey Base64-encoded RSA public key + * @throws IllegalArgumentException if encodedPublicKey is invalid + */ + public LicenseChecker(Context context, Policy policy, String encodedPublicKey) { + mContext = context; + mPolicy = policy; + mPublicKey = generatePublicKey(encodedPublicKey); + mPackageName = mContext.getPackageName(); + mVersionCode = getVersionCode(context, mPackageName); + HandlerThread handlerThread = new HandlerThread("background thread"); + handlerThread.start(); + mHandler = new Handler(handlerThread.getLooper()); + } + + /** + * Generates a PublicKey instance from a string containing the + * Base64-encoded public key. + * + * @param encodedPublicKey Base64-encoded public key + * @throws IllegalArgumentException if encodedPublicKey is invalid + */ + private static PublicKey generatePublicKey(String encodedPublicKey) { + try { + byte[] decodedKey = Base64.decode(encodedPublicKey); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); + + return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); + } catch (NoSuchAlgorithmException e) { + // This won't happen in an Android-compatible environment. + throw new RuntimeException(e); + } catch (Base64DecoderException e) { + Log.e(TAG, "Could not decode from Base64."); + throw new IllegalArgumentException(e); + } catch (InvalidKeySpecException e) { + Log.e(TAG, "Invalid key specification."); + throw new IllegalArgumentException(e); + } + } + + /** + * Checks if the user should have access to the app. Binds the service if necessary. + *

+ * NOTE: This call uses a trivially obfuscated string (base64-encoded). For best security, + * we recommend obfuscating the string that is passed into bindService using another method + * of your own devising. + *

+ * source string: "com.android.vending.licensing.ILicensingService" + *

+ * @param callback + */ + public synchronized void checkAccess(LicenseCheckerCallback callback) { + // If we have a valid recent LICENSED response, we can skip asking + // Market. + if (mPolicy.allowAccess()) { + Log.i(TAG, "Using cached license response"); + callback.allow(Policy.LICENSED); + } else { + LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(), + callback, generateNonce(), mPackageName, mVersionCode); + + if (mService == null) { + Log.i(TAG, "Binding to licensing service."); + try { + boolean bindResult = mContext + .bindService( + new Intent( + new String( + Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))), + this, // ServiceConnection. + Context.BIND_AUTO_CREATE); + + if (bindResult) { + mPendingChecks.offer(validator); + } else { + Log.e(TAG, "Could not bind to service."); + handleServiceConnectionError(validator); + } + } catch (SecurityException e) { + callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION); + } catch (Base64DecoderException e) { + e.printStackTrace(); + } + } else { + mPendingChecks.offer(validator); + runChecks(); + } + } + } + + private void runChecks() { + LicenseValidator validator; + while ((validator = mPendingChecks.poll()) != null) { + try { + Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName()); + mService.checkLicense( + validator.getNonce(), validator.getPackageName(), + new ResultListener(validator)); + mChecksInProgress.add(validator); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in checkLicense call.", e); + handleServiceConnectionError(validator); + } + } + } + + private synchronized void finishCheck(LicenseValidator validator) { + mChecksInProgress.remove(validator); + if (mChecksInProgress.isEmpty()) { + cleanupService(); + } + } + + private class ResultListener extends ILicenseResultListener.Stub { + private final LicenseValidator mValidator; + private Runnable mOnTimeout; + + public ResultListener(LicenseValidator validator) { + mValidator = validator; + mOnTimeout = new Runnable() { + public void run() { + Log.i(TAG, "Check timed out."); + handleServiceConnectionError(mValidator); + finishCheck(mValidator); + } + }; + startTimeout(); + } + + private static final int ERROR_CONTACTING_SERVER = 0x101; + private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; + private static final int ERROR_NON_MATCHING_UID = 0x103; + + // Runs in IPC thread pool. Post it to the Handler, so we can guarantee + // either this or the timeout runs. + public void verifyLicense(final int responseCode, final String signedData, + final String signature) { + mHandler.post(new Runnable() { + public void run() { + Log.i(TAG, "Received response."); + // Make sure it hasn't already timed out. + if (mChecksInProgress.contains(mValidator)) { + clearTimeout(); + mValidator.verify(mPublicKey, responseCode, signedData, signature); + finishCheck(mValidator); + } + if (DEBUG_LICENSE_ERROR) { + boolean logResponse; + String stringError = null; + switch (responseCode) { + case ERROR_CONTACTING_SERVER: + logResponse = true; + stringError = "ERROR_CONTACTING_SERVER"; + break; + case ERROR_INVALID_PACKAGE_NAME: + logResponse = true; + stringError = "ERROR_INVALID_PACKAGE_NAME"; + break; + case ERROR_NON_MATCHING_UID: + logResponse = true; + stringError = "ERROR_NON_MATCHING_UID"; + break; + default: + logResponse = false; + } + + if (logResponse) { + String android_id = Secure.getString(mContext.getContentResolver(), + Secure.ANDROID_ID); + Date date = new Date(); + Log.d(TAG, "Server Failure: " + stringError); + Log.d(TAG, "Android ID: " + android_id); + Log.d(TAG, "Time: " + date.toGMTString()); + } + } + + } + }); + } + + private void startTimeout() { + Log.i(TAG, "Start monitoring timeout."); + mHandler.postDelayed(mOnTimeout, TIMEOUT_MS); + } + + private void clearTimeout() { + Log.i(TAG, "Clearing timeout."); + mHandler.removeCallbacks(mOnTimeout); + } + } + + public synchronized void onServiceConnected(ComponentName name, IBinder service) { + mService = ILicensingService.Stub.asInterface(service); + runChecks(); + } + + public synchronized void onServiceDisconnected(ComponentName name) { + // Called when the connection with the service has been + // unexpectedly disconnected. That is, Market crashed. + // If there are any checks in progress, the timeouts will handle them. + Log.w(TAG, "Service unexpectedly disconnected."); + mService = null; + } + + /** + * Generates policy response for service connection errors, as a result of + * disconnections or timeouts. + */ + private synchronized void handleServiceConnectionError(LicenseValidator validator) { + mPolicy.processServerResponse(Policy.RETRY, null); + + if (mPolicy.allowAccess()) { + validator.getCallback().allow(Policy.RETRY); + } else { + validator.getCallback().dontAllow(Policy.RETRY); + } + } + + /** Unbinds service if necessary and removes reference to it. */ + private void cleanupService() { + if (mService != null) { + try { + mContext.unbindService(this); + } catch (IllegalArgumentException e) { + // Somehow we've already been unbound. This is a non-fatal + // error. + Log.e(TAG, "Unable to unbind from licensing service (already unbound)"); + } + mService = null; + } + } + + /** + * Inform the library that the context is about to be destroyed, so that any + * open connections can be cleaned up. + *

+ * Failure to call this method can result in a crash under certain + * circumstances, such as during screen rotation if an Activity requests the + * license check or when the user exits the application. + */ + public synchronized void onDestroy() { + cleanupService(); + mHandler.getLooper().quit(); + } + + /** Generates a nonce (number used once). */ + private int generateNonce() { + return RANDOM.nextInt(); + } + + /** + * Get version code for the application package name. + * + * @param context + * @param packageName application package name + * @return the version code or empty string if package not found + */ + private static String getVersionCode(Context context, String packageName) { + try { + return String.valueOf(context.getPackageManager().getPackageInfo(packageName, 0). + versionCode); + } catch (NameNotFoundException e) { + Log.e(TAG, "Package not found. could not get version code."); + return ""; + } + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/LicenseCheckerCallback.java b/platform/android/java/src/com/android/vending/licensing/LicenseCheckerCallback.java new file mode 100644 index 0000000000..b250a7147b --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/LicenseCheckerCallback.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +/** + * Callback for the license checker library. + *

+ * Upon checking with the Market server and conferring with the {@link Policy}, + * the library calls the appropriate callback method to communicate the result. + *

+ * The callback does not occur in the original checking thread. Your + * application should post to the appropriate handling thread or lock + * accordingly. + *

+ * The reason that is passed back with allow/dontAllow is the base status handed + * to the policy for allowed/disallowing the license. Policy.RETRY will call + * allow or dontAllow depending on other statistics associated with the policy, + * while in most cases Policy.NOT_LICENSED will call dontAllow and + * Policy.LICENSED will Allow. + */ +public interface LicenseCheckerCallback { + + /** + * Allow use. App should proceed as normal. + * + * @param reason Policy.LICENSED or Policy.RETRY typically. (although in + * theory the policy can return Policy.NOT_LICENSED here as well) + */ + public void allow(int reason); + + /** + * Don't allow use. App should inform user and take appropriate action. + * + * @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory + * the policy can return Policy.LICENSED here as well --- + * perhaps the call to the LVL took too long, for example) + */ + public void dontAllow(int reason); + + /** Application error codes. */ + public static final int ERROR_INVALID_PACKAGE_NAME = 1; + public static final int ERROR_NON_MATCHING_UID = 2; + public static final int ERROR_NOT_MARKET_MANAGED = 3; + public static final int ERROR_CHECK_IN_PROGRESS = 4; + public static final int ERROR_INVALID_PUBLIC_KEY = 5; + public static final int ERROR_MISSING_PERMISSION = 6; + + /** + * Error in application code. Caller did not call or set up license checker + * correctly. Should be considered fatal. + */ + public void applicationError(int errorCode); +} diff --git a/platform/android/java/src/com/android/vending/licensing/LicenseValidator.java b/platform/android/java/src/com/android/vending/licensing/LicenseValidator.java new file mode 100644 index 0000000000..61d3c7e79e --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/LicenseValidator.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +import com.google.android.vending.licensing.util.Base64; +import com.google.android.vending.licensing.util.Base64DecoderException; + +import android.text.TextUtils; +import android.util.Log; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; + +/** + * Contains data related to a licensing request and methods to verify + * and process the response. + */ +class LicenseValidator { + private static final String TAG = "LicenseValidator"; + + // Server response codes. + private static final int LICENSED = 0x0; + private static final int NOT_LICENSED = 0x1; + private static final int LICENSED_OLD_KEY = 0x2; + private static final int ERROR_NOT_MARKET_MANAGED = 0x3; + private static final int ERROR_SERVER_FAILURE = 0x4; + private static final int ERROR_OVER_QUOTA = 0x5; + + private static final int ERROR_CONTACTING_SERVER = 0x101; + private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; + private static final int ERROR_NON_MATCHING_UID = 0x103; + + private final Policy mPolicy; + private final LicenseCheckerCallback mCallback; + private final int mNonce; + private final String mPackageName; + private final String mVersionCode; + private final DeviceLimiter mDeviceLimiter; + + LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback, + int nonce, String packageName, String versionCode) { + mPolicy = policy; + mDeviceLimiter = deviceLimiter; + mCallback = callback; + mNonce = nonce; + mPackageName = packageName; + mVersionCode = versionCode; + } + + public LicenseCheckerCallback getCallback() { + return mCallback; + } + + public int getNonce() { + return mNonce; + } + + public String getPackageName() { + return mPackageName; + } + + private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; + + /** + * Verifies the response from server and calls appropriate callback method. + * + * @param publicKey public key associated with the developer account + * @param responseCode server response code + * @param signedData signed data from server + * @param signature server signature + */ + public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) { + String userId = null; + // Skip signature check for unsuccessful requests + ResponseData data = null; + if (responseCode == LICENSED || responseCode == NOT_LICENSED || + responseCode == LICENSED_OLD_KEY) { + // Verify signature. + try { + Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); + sig.initVerify(publicKey); + sig.update(signedData.getBytes()); + + if (!sig.verify(Base64.decode(signature))) { + Log.e(TAG, "Signature verification failed."); + handleInvalidResponse(); + return; + } + } catch (NoSuchAlgorithmException e) { + // This can't happen on an Android compatible device. + throw new RuntimeException(e); + } catch (InvalidKeyException e) { + handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY); + return; + } catch (SignatureException e) { + throw new RuntimeException(e); + } catch (Base64DecoderException e) { + Log.e(TAG, "Could not Base64-decode signature."); + handleInvalidResponse(); + return; + } + + // Parse and validate response. + try { + data = ResponseData.parse(signedData); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Could not parse response."); + handleInvalidResponse(); + return; + } + + if (data.responseCode != responseCode) { + Log.e(TAG, "Response codes don't match."); + handleInvalidResponse(); + return; + } + + if (data.nonce != mNonce) { + Log.e(TAG, "Nonce doesn't match."); + handleInvalidResponse(); + return; + } + + if (!data.packageName.equals(mPackageName)) { + Log.e(TAG, "Package name doesn't match."); + handleInvalidResponse(); + return; + } + + if (!data.versionCode.equals(mVersionCode)) { + Log.e(TAG, "Version codes don't match."); + handleInvalidResponse(); + return; + } + + // Application-specific user identifier. + userId = data.userId; + if (TextUtils.isEmpty(userId)) { + Log.e(TAG, "User identifier is empty."); + handleInvalidResponse(); + return; + } + } + + switch (responseCode) { + case LICENSED: + case LICENSED_OLD_KEY: + int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId); + handleResponse(limiterResponse, data); + break; + case NOT_LICENSED: + handleResponse(Policy.NOT_LICENSED, data); + break; + case ERROR_CONTACTING_SERVER: + Log.w(TAG, "Error contacting licensing server."); + handleResponse(Policy.RETRY, data); + break; + case ERROR_SERVER_FAILURE: + Log.w(TAG, "An error has occurred on the licensing server."); + handleResponse(Policy.RETRY, data); + break; + case ERROR_OVER_QUOTA: + Log.w(TAG, "Licensing server is refusing to talk to this device, over quota."); + handleResponse(Policy.RETRY, data); + break; + case ERROR_INVALID_PACKAGE_NAME: + handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME); + break; + case ERROR_NON_MATCHING_UID: + handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID); + break; + case ERROR_NOT_MARKET_MANAGED: + handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED); + break; + default: + Log.e(TAG, "Unknown response code for license check."); + handleInvalidResponse(); + } + } + + /** + * Confers with policy and calls appropriate callback method. + * + * @param response + * @param rawData + */ + private void handleResponse(int response, ResponseData rawData) { + // Update policy data and increment retry counter (if needed) + mPolicy.processServerResponse(response, rawData); + + // Given everything we know, including cached data, ask the policy if we should grant + // access. + if (mPolicy.allowAccess()) { + mCallback.allow(response); + } else { + mCallback.dontAllow(response); + } + } + + private void handleApplicationError(int code) { + mCallback.applicationError(code); + } + + private void handleInvalidResponse() { + mCallback.dontAllow(Policy.NOT_LICENSED); + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/NullDeviceLimiter.java b/platform/android/java/src/com/android/vending/licensing/NullDeviceLimiter.java new file mode 100644 index 0000000000..d87af3153f --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/NullDeviceLimiter.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +/** + * A DeviceLimiter that doesn't limit the number of devices that can use a + * given user's license. + *

+ * Unless you have reason to believe that your application is being pirated + * by multiple users using the same license (signing in to Market as the same + * user), we recommend you use this implementation. + */ +public class NullDeviceLimiter implements DeviceLimiter { + + public int isDeviceAllowed(String userId) { + return Policy.LICENSED; + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/Obfuscator.java b/platform/android/java/src/com/android/vending/licensing/Obfuscator.java new file mode 100644 index 0000000000..b5d510d72d --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/Obfuscator.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +/** + * Interface used as part of a {@link Policy} to allow application authors to obfuscate + * licensing data that will be stored into a SharedPreferences file. + *

+ * Any transformation scheme must be reversable. Implementing classes may optionally implement an + * integrity check to further prevent modification to preference data. Implementing classes + * should use device-specific information as a key in the obfuscation algorithm to prevent + * obfuscated preferences from being shared among devices. + */ +public interface Obfuscator { + + /** + * Obfuscate a string that is being stored into shared preferences. + * + * @param original The data that is to be obfuscated. + * @param key The key for the data that is to be obfuscated. + * @return A transformed version of the original data. + */ + String obfuscate(String original, String key); + + /** + * Undo the transformation applied to data by the obfuscate() method. + * + * @param original The data that is to be obfuscated. + * @param key The key for the data that is to be obfuscated. + * @return A transformed version of the original data. + * @throws ValidationException Optionally thrown if a data integrity check fails. + */ + String unobfuscate(String obfuscated, String key) throws ValidationException; +} diff --git a/platform/android/java/src/com/android/vending/licensing/Policy.java b/platform/android/java/src/com/android/vending/licensing/Policy.java new file mode 100644 index 0000000000..fa267fc71a --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/Policy.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +/** + * Policy used by {@link LicenseChecker} to determine whether a user should have + * access to the application. + */ +public interface Policy { + + /** + * Change these values to make it more difficult for tools to automatically + * strip LVL protection from your APK. + */ + + /** + * LICENSED means that the server returned back a valid license response + */ + public static final int LICENSED = 0x0100; + /** + * NOT_LICENSED means that the server returned back a valid license response + * that indicated that the user definitively is not licensed + */ + public static final int NOT_LICENSED = 0x0231; + /** + * RETRY means that the license response was unable to be determined --- + * perhaps as a result of faulty networking + */ + public static final int RETRY = 0x0123; + + /** + * Provide results from contact with the license server. Retry counts are + * incremented if the current value of response is RETRY. Results will be + * used for any future policy decisions. + * + * @param response the result from validating the server response + * @param rawData the raw server response data, can be null for RETRY + */ + void processServerResponse(int response, ResponseData rawData); + + /** + * Check if the user should be allowed access to the application. + */ + boolean allowAccess(); +} diff --git a/platform/android/java/src/com/android/vending/licensing/PreferenceObfuscator.java b/platform/android/java/src/com/android/vending/licensing/PreferenceObfuscator.java new file mode 100644 index 0000000000..7c42bfc28a --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/PreferenceObfuscator.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +import android.content.SharedPreferences; +import android.util.Log; + +/** + * An wrapper for SharedPreferences that transparently performs data obfuscation. + */ +public class PreferenceObfuscator { + + private static final String TAG = "PreferenceObfuscator"; + + private final SharedPreferences mPreferences; + private final Obfuscator mObfuscator; + private SharedPreferences.Editor mEditor; + + /** + * Constructor. + * + * @param sp A SharedPreferences instance provided by the system. + * @param o The Obfuscator to use when reading or writing data. + */ + public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) { + mPreferences = sp; + mObfuscator = o; + mEditor = null; + } + + public void putString(String key, String value) { + if (mEditor == null) { + mEditor = mPreferences.edit(); + } + String obfuscatedValue = mObfuscator.obfuscate(value, key); + mEditor.putString(key, obfuscatedValue); + } + + public String getString(String key, String defValue) { + String result; + String value = mPreferences.getString(key, null); + if (value != null) { + try { + result = mObfuscator.unobfuscate(value, key); + } catch (ValidationException e) { + // Unable to unobfuscate, data corrupt or tampered + Log.w(TAG, "Validation error while reading preference: " + key); + result = defValue; + } + } else { + // Preference not found + result = defValue; + } + return result; + } + + public void commit() { + if (mEditor != null) { + mEditor.commit(); + mEditor = null; + } + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/ResponseData.java b/platform/android/java/src/com/android/vending/licensing/ResponseData.java new file mode 100644 index 0000000000..2adef3709e --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/ResponseData.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +import java.util.regex.Pattern; + +import android.text.TextUtils; + +/** + * ResponseData from licensing server. + */ +public class ResponseData { + + public int responseCode; + public int nonce; + public String packageName; + public String versionCode; + public String userId; + public long timestamp; + /** Response-specific data. */ + public String extra; + + /** + * Parses response string into ResponseData. + * + * @param responseData response data string + * @throws IllegalArgumentException upon parsing error + * @return ResponseData object + */ + public static ResponseData parse(String responseData) { + // Must parse out main response data and response-specific data. + int index = responseData.indexOf(':'); + String mainData, extraData; + if ( -1 == index ) { + mainData = responseData; + extraData = ""; + } else { + mainData = responseData.substring(0, index); + extraData = index >= responseData.length() ? "" : responseData.substring(index+1); + } + + String [] fields = TextUtils.split(mainData, Pattern.quote("|")); + if (fields.length < 6) { + throw new IllegalArgumentException("Wrong number of fields."); + } + + ResponseData data = new ResponseData(); + data.extra = extraData; + data.responseCode = Integer.parseInt(fields[0]); + data.nonce = Integer.parseInt(fields[1]); + data.packageName = fields[2]; + data.versionCode = fields[3]; + // Application-specific user identifier. + data.userId = fields[4]; + data.timestamp = Long.parseLong(fields[5]); + + return data; + } + + @Override + public String toString() { + return TextUtils.join("|", new Object [] { responseCode, nonce, packageName, versionCode, + userId, timestamp }); + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/ServerManagedPolicy.java b/platform/android/java/src/com/android/vending/licensing/ServerManagedPolicy.java new file mode 100644 index 0000000000..fbf8cf6d00 --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/ServerManagedPolicy.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +/** + * Default policy. All policy decisions are based off of response data received + * from the licensing service. Specifically, the licensing server sends the + * following information: response validity period, error retry period, and + * error retry count. + *

+ * These values will vary based on the the way the application is configured in + * the Android Market publishing console, such as whether the application is + * marked as free or is within its refund period, as well as how often an + * application is checking with the licensing service. + *

+ * Developers who need more fine grained control over their application's + * licensing policy should implement a custom Policy. + */ +public class ServerManagedPolicy implements Policy { + + private static final String TAG = "ServerManagedPolicy"; + private static final String PREFS_FILE = "com.android.vending.licensing.ServerManagedPolicy"; + private static final String PREF_LAST_RESPONSE = "lastResponse"; + private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; + private static final String PREF_RETRY_UNTIL = "retryUntil"; + private static final String PREF_MAX_RETRIES = "maxRetries"; + private static final String PREF_RETRY_COUNT = "retryCount"; + private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; + private static final String DEFAULT_RETRY_UNTIL = "0"; + private static final String DEFAULT_MAX_RETRIES = "0"; + private static final String DEFAULT_RETRY_COUNT = "0"; + + private static final long MILLIS_PER_MINUTE = 60 * 1000; + + private long mValidityTimestamp; + private long mRetryUntil; + private long mMaxRetries; + private long mRetryCount; + private long mLastResponseTime = 0; + private int mLastResponse; + private PreferenceObfuscator mPreferences; + + /** + * @param context The context for the current application + * @param obfuscator An obfuscator to be used with preferences. + */ + public ServerManagedPolicy(Context context, Obfuscator obfuscator) { + // Import old values + SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); + mPreferences = new PreferenceObfuscator(sp, obfuscator); + mLastResponse = Integer.parseInt( + mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); + mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, + DEFAULT_VALIDITY_TIMESTAMP)); + mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); + mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); + mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); + } + + /** + * Process a new response from the license server. + *

+ * This data will be used for computing future policy decisions. The + * following parameters are processed: + *

    + *
  • VT: the timestamp that the client should consider the response + * valid until + *
  • GT: the timestamp that the client should ignore retry errors until + *
  • GR: the number of retry errors that the client should ignore + *
+ * + * @param response the result from validating the server response + * @param rawData the raw server response data + */ + public void processServerResponse(int response, ResponseData rawData) { + + // Update retry counter + if (response != Policy.RETRY) { + setRetryCount(0); + } else { + setRetryCount(mRetryCount + 1); + } + + if (response == Policy.LICENSED) { + // Update server policy data + Map extras = decodeExtras(rawData.extra); + mLastResponse = response; + setValidityTimestamp(extras.get("VT")); + setRetryUntil(extras.get("GT")); + setMaxRetries(extras.get("GR")); + } else if (response == Policy.NOT_LICENSED) { + // Clear out stale policy data + setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); + setRetryUntil(DEFAULT_RETRY_UNTIL); + setMaxRetries(DEFAULT_MAX_RETRIES); + } + + setLastResponse(response); + mPreferences.commit(); + } + + /** + * Set the last license response received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param l the response + */ + private void setLastResponse(int l) { + mLastResponseTime = System.currentTimeMillis(); + mLastResponse = l; + mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); + } + + /** + * Set the current retry count and add to preferences. You must manually + * call PreferenceObfuscator.commit() to commit these changes to disk. + * + * @param c the new retry count + */ + private void setRetryCount(long c) { + mRetryCount = c; + mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); + } + + public long getRetryCount() { + return mRetryCount; + } + + /** + * Set the last validity timestamp (VT) received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param validityTimestamp the VT string received + */ + private void setValidityTimestamp(String validityTimestamp) { + Long lValidityTimestamp; + try { + lValidityTimestamp = Long.parseLong(validityTimestamp); + } catch (NumberFormatException e) { + // No response or not parsable, expire in one minute. + Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); + lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; + validityTimestamp = Long.toString(lValidityTimestamp); + } + + mValidityTimestamp = lValidityTimestamp; + mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); + } + + public long getValidityTimestamp() { + return mValidityTimestamp; + } + + /** + * Set the retry until timestamp (GT) received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param retryUntil the GT string received + */ + private void setRetryUntil(String retryUntil) { + Long lRetryUntil; + try { + lRetryUntil = Long.parseLong(retryUntil); + } catch (NumberFormatException e) { + // No response or not parsable, expire immediately + Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); + retryUntil = "0"; + lRetryUntil = 0l; + } + + mRetryUntil = lRetryUntil; + mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); + } + + public long getRetryUntil() { + return mRetryUntil; + } + + /** + * Set the max retries value (GR) as received from the server and add to + * preferences. You must manually call PreferenceObfuscator.commit() to + * commit these changes to disk. + * + * @param maxRetries the GR string received + */ + private void setMaxRetries(String maxRetries) { + Long lMaxRetries; + try { + lMaxRetries = Long.parseLong(maxRetries); + } catch (NumberFormatException e) { + // No response or not parsable, expire immediately + Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); + maxRetries = "0"; + lMaxRetries = 0l; + } + + mMaxRetries = lMaxRetries; + mPreferences.putString(PREF_MAX_RETRIES, maxRetries); + } + + public long getMaxRetries() { + return mMaxRetries; + } + + /** + * {@inheritDoc} + * + * This implementation allows access if either:
+ *
    + *
  1. a LICENSED response was received within the validity period + *
  2. a RETRY response was received in the last minute, and we are under + * the RETRY count or in the RETRY period. + *
+ */ + public boolean allowAccess() { + long ts = System.currentTimeMillis(); + if (mLastResponse == Policy.LICENSED) { + // Check if the LICENSED response occurred within the validity timeout. + if (ts <= mValidityTimestamp) { + // Cached LICENSED response is still valid. + return true; + } + } else if (mLastResponse == Policy.RETRY && + ts < mLastResponseTime + MILLIS_PER_MINUTE) { + // Only allow access if we are within the retry period or we haven't used up our + // max retries. + return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); + } + return false; + } + + private Map decodeExtras(String extras) { + Map results = new HashMap(); + try { + URI rawExtras = new URI("?" + extras); + List extraList = URLEncodedUtils.parse(rawExtras, "UTF-8"); + for (NameValuePair item : extraList) { + results.put(item.getName(), item.getValue()); + } + } catch (URISyntaxException e) { + Log.w(TAG, "Invalid syntax error while decoding extras data from server."); + } + return results; + } + +} diff --git a/platform/android/java/src/com/android/vending/licensing/StrictPolicy.java b/platform/android/java/src/com/android/vending/licensing/StrictPolicy.java new file mode 100644 index 0000000000..d8d83b4e4b --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/StrictPolicy.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +/** + * Non-caching policy. All requests will be sent to the licensing service, + * and no local caching is performed. + *

+ * Using a non-caching policy ensures that there is no local preference data + * for malicious users to tamper with. As a side effect, applications + * will not be permitted to run while offline. Developers should carefully + * weigh the risks of using this Policy over one which implements caching, + * such as ServerManagedPolicy. + *

+ * Access to the application is only allowed if a LICESNED response is. + * received. All other responses (including RETRY) will deny access. + */ +public class StrictPolicy implements Policy { + + private int mLastResponse; + + public StrictPolicy() { + // Set default policy. This will force the application to check the policy on launch. + mLastResponse = Policy.RETRY; + } + + /** + * Process a new response from the license server. Since we aren't + * performing any caching, this equates to reading the LicenseResponse. + * Any ResponseData provided is ignored. + * + * @param response the result from validating the server response + * @param rawData the raw server response data + */ + public void processServerResponse(int response, ResponseData rawData) { + mLastResponse = response; + } + + /** + * {@inheritDoc} + * + * This implementation allows access if and only if a LICENSED response + * was received the last time the server was contacted. + */ + public boolean allowAccess() { + return (mLastResponse == Policy.LICENSED); + } + +} diff --git a/platform/android/java/src/com/android/vending/licensing/ValidationException.java b/platform/android/java/src/com/android/vending/licensing/ValidationException.java new file mode 100644 index 0000000000..ee4df47c68 --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/ValidationException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.licensing; + +/** + * Indicates that an error occurred while validating the integrity of data managed by an + * {@link Obfuscator}.} + */ +public class ValidationException extends Exception { + public ValidationException() { + super(); + } + + public ValidationException(String s) { + super(s); + } + + private static final long serialVersionUID = 1L; +} diff --git a/platform/android/java/src/com/android/vending/licensing/util/Base64.java b/platform/android/java/src/com/android/vending/licensing/util/Base64.java new file mode 100644 index 0000000000..a0d2779af2 --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/util/Base64.java @@ -0,0 +1,570 @@ +// Portions copyright 2002, Google, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.android.vending.licensing.util; + +// This code was converted from code at http://iharder.sourceforge.net/base64/ +// Lots of extraneous features were removed. +/* The original code said: + *

+ * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit + * http://iharder.net/xmlizable + * periodically to check for updates or to contribute improvements. + *

+ * + * @author Robert Harder + * @author rharder@usa.net + * @version 1.3 + */ + +/** + * Base64 converter class. This code is not a full-blown MIME encoder; + * it simply converts binary data to base64 data and back. + * + *

Note {@link CharBase64} is a GWT-compatible implementation of this + * class. + */ +public class Base64 { + /** Specify encoding (value is {@code true}). */ + public final static boolean ENCODE = true; + + /** Specify decoding (value is {@code false}). */ + public final static boolean DECODE = false; + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte) '\n'; + + /** + * The 64 valid Base64 values. + */ + private final static byte[] ALPHABET = + {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', + (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', + (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', + (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', + (byte) '9', (byte) '+', (byte) '/'}; + + /** + * The 64 valid web safe Base64 values. + */ + private final static byte[] WEBSAFE_ALPHABET = + {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', + (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', + (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', + (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', + (byte) '9', (byte) '-', (byte) '_'}; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + /** The web safe decodabet */ + private final static byte[] WEBSAFE_DECODABET = + {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 + 62, // Dash '-' sign at decimal 45 + -9, -9, // Decimal 46-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, // Decimal 91-94 + 63, // Underscore '_' at decimal 95 + -9, // Decimal 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + // Indicates white space in encoding + private final static byte WHITE_SPACE_ENC = -5; + // Indicates equals sign in encoding + private final static byte EQUALS_SIGN_ENC = -1; + + /** Defeats instantiation. */ + private Base64() { + } + + /* ******** E N C O D I N G M E T H O D S ******** */ + + /** + * Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param alphabet is the encoding alphabet + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, + int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index alphabet + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = + (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; + return destination; + case 2: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + case 1: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + default: + return destination; + } // end switch + } // end encode3to4 + + /** + * Encodes a byte array into Base64 notation. + * Equivalent to calling + * {@code encodeBytes(source, 0, source.length)} + * + * @param source The data to convert + * @since 1.4 + */ + public static String encode(byte[] source) { + return encode(source, 0, source.length, ALPHABET, true); + } + + /** + * Encodes a byte array into web safe Base64 notation. + * + * @param source The data to convert + * @param doPadding is {@code true} to pad result with '=' chars + * if it does not fall on 3 byte boundaries + */ + public static String encodeWebSafe(byte[] source, boolean doPadding) { + return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param alphabet is the encoding alphabet + * @param doPadding is {@code true} to pad result with '=' chars + * if it does not fall on 3 byte boundaries + * @since 1.4 + */ + public static String encode(byte[] source, int off, int len, byte[] alphabet, + boolean doPadding) { + byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); + int outLen = outBuff.length; + + // If doPadding is false, set length to truncate '=' + // padding characters + while (doPadding == false && outLen > 0) { + if (outBuff[outLen - 1] != '=') { + break; + } + outLen -= 1; + } + + return new String(outBuff, 0, outLen); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param alphabet is the encoding alphabet + * @param maxLineLength maximum length of one line. + * @return the BASE64-encoded byte array + */ + public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, + int maxLineLength) { + int lenDiv3 = (len + 2) / 3; // ceil(len / 3) + int len43 = lenDiv3 * 4; + byte[] outBuff = new byte[len43 // Main 4:3 + + (len43 / maxLineLength)]; // New lines + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for (; d < len2; d += 3, e += 4) { + + // The following block of code is the same as + // encode3to4( source, d + off, 3, outBuff, e, alphabet ); + // but inlined for faster encoding (~20% improvement) + int inBuff = + ((source[d + off] << 24) >>> 8) + | ((source[d + 1 + off] << 24) >>> 16) + | ((source[d + 2 + off] << 24) >>> 24); + outBuff[e] = alphabet[(inBuff >>> 18)]; + outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; + + lineLength += 4; + if (lineLength == maxLineLength) { + outBuff[e + 4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // end for: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e, alphabet); + + lineLength += 4; + if (lineLength == maxLineLength) { + // Add a last newline + outBuff[e + 4] = NEW_LINE; + e++; + } + e += 4; + } + + assert (e == outBuff.length); + return outBuff; + } + + + /* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param decodabet the decodabet for decoding Base64 content + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3(byte[] source, int srcOffset, + byte[] destination, int destOffset, byte[] decodabet) { + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); + + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; + } else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Example: DkL= + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) + | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); + + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; + } else { + // Example: DkLE + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) + | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) + | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + return 3; + } + } // end decodeToBytes + + + /** + * Decodes data from Base64 notation. + * + * @param s the string to decode (decoded in default encoding) + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode(String s) throws Base64DecoderException { + byte[] bytes = s.getBytes(); + return decode(bytes, 0, bytes.length); + } + + /** + * Decodes data from web safe Base64 notation. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param s the string to decode (decoded in default encoding) + * @return the decoded data + */ + public static byte[] decodeWebSafe(String s) throws Base64DecoderException { + byte[] bytes = s.getBytes(); + return decodeWebSafe(bytes, 0, bytes.length); + } + + /** + * Decodes Base64 content in byte array format and returns + * the decoded byte array. + * + * @param source The Base64 encoded data + * @return decoded data + * @since 1.3 + * @throws Base64DecoderException + */ + public static byte[] decode(byte[] source) throws Base64DecoderException { + return decode(source, 0, source.length); + } + + /** + * Decodes web safe Base64 content in byte array format and returns + * the decoded data. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param source the string to decode (decoded in default encoding) + * @return the decoded data + */ + public static byte[] decodeWebSafe(byte[] source) + throws Base64DecoderException { + return decodeWebSafe(source, 0, source.length); + } + + /** + * Decodes Base64 content in byte array format and returns + * the decoded byte array. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + * @since 1.3 + * @throws Base64DecoderException + */ + public static byte[] decode(byte[] source, int off, int len) + throws Base64DecoderException { + return decode(source, off, len, DECODABET); + } + + /** + * Decodes web safe Base64 content in byte array format and returns + * the decoded byte array. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + */ + public static byte[] decodeWebSafe(byte[] source, int off, int len) + throws Base64DecoderException { + return decode(source, off, len, WEBSAFE_DECODABET); + } + + /** + * Decodes Base64 content using the supplied decodabet and returns + * the decoded byte array. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @param decodabet the decodabet for decoding Base64 content + * @return decoded data + */ + public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) + throws Base64DecoderException { + int len34 = len * 3 / 4; + byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for (i = 0; i < len; i++) { + sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits + sbiDecode = decodabet[sbiCrop]; + + if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better + if (sbiDecode >= EQUALS_SIGN_ENC) { + // An equals sign (for padding) must not occur at position 0 or 1 + // and must be the last byte[s] in the encoded value + if (sbiCrop == EQUALS_SIGN) { + int bytesLeft = len - i; + byte lastByte = (byte) (source[len - 1 + off] & 0x7f); + if (b4Posn == 0 || b4Posn == 1) { + throw new Base64DecoderException( + "invalid padding byte '=' at byte offset " + i); + } else if ((b4Posn == 3 && bytesLeft > 2) + || (b4Posn == 4 && bytesLeft > 1)) { + throw new Base64DecoderException( + "padding byte '=' falsely signals end of encoded value " + + "at offset " + i); + } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { + throw new Base64DecoderException( + "encoded value has invalid trailing byte"); + } + break; + } + + b4[b4Posn++] = sbiCrop; + if (b4Posn == 4) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); + b4Posn = 0; + } + } + } else { + throw new Base64DecoderException("Bad Base64 input character at " + i + + ": " + source[i + off] + "(decimal)"); + } + } + + // Because web safe encoding allows non padding base64 encodes, we + // need to pad the rest of the b4 buffer with equal signs when + // b4Posn != 0. There can be at most 2 equal signs at the end of + // four characters, so the b4 buffer must have two or three + // characters. This also catches the case where the input is + // padded with EQUALS_SIGN + if (b4Posn != 0) { + if (b4Posn == 1) { + throw new Base64DecoderException("single trailing character at offset " + + (len - 1)); + } + b4[b4Posn++] = EQUALS_SIGN; + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); + } + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } +} diff --git a/platform/android/java/src/com/android/vending/licensing/util/Base64DecoderException.java b/platform/android/java/src/com/android/vending/licensing/util/Base64DecoderException.java new file mode 100644 index 0000000000..1aef1b54b8 --- /dev/null +++ b/platform/android/java/src/com/android/vending/licensing/util/Base64DecoderException.java @@ -0,0 +1,32 @@ +// Copyright 2002, Google, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.android.vending.licensing.util; + +/** + * Exception thrown when encountering an invalid Base64 input character. + * + * @author nelson + */ +public class Base64DecoderException extends Exception { + public Base64DecoderException() { + super(); + } + + public Base64DecoderException(String s) { + super(s); + } + + private static final long serialVersionUID = 1L; +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java new file mode 100644 index 0000000000..ff2c6f535a --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/Constants.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import java.io.File; + + +/** + * Contains the internal constants that are used in the download manager. + * As a general rule, modifying these constants should be done with care. + */ +public class Constants { + /** Tag used for debugging/logging */ + public static final String TAG = "LVLDL"; + + /** + * Expansion path where we store obb files + */ + public static final String EXP_PATH = File.separator + "Android" + + File.separator + "obb" + File.separator; + + /** The intent that gets sent when the service must wake up for a retry */ + public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; + + /** the intent that gets sent when clicking a successful download */ + public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; + + /** the intent that gets sent when clicking an incomplete/failed download */ + public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; + + /** the intent that gets sent when deleting the notification of a completed download */ + public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; + + /** + * When a number has to be appended to the filename, this string is used to separate the + * base filename from the sequence number + */ + public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; + + /** The default user agent used for downloads */ + public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; + + /** The buffer size used to stream the data */ + public static final int BUFFER_SIZE = 4096; + + /** The minimum amount of progress that has to be done before the progress bar gets updated */ + public static final int MIN_PROGRESS_STEP = 4096; + + /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */ + public static final long MIN_PROGRESS_TIME = 1000; + + /** The maximum number of rows in the database (FIFO) */ + public static final int MAX_DOWNLOADS = 1000; + + /** + * The number of times that the download manager will retry its network + * operations when no progress is happening before it gives up. + */ + public static final int MAX_RETRIES = 5; + + /** + * The minimum amount of time that the download manager accepts for + * a Retry-After response header with a parameter in delta-seconds. + */ + public static final int MIN_RETRY_AFTER = 30; // 30s + + /** + * The maximum amount of time that the download manager accepts for + * a Retry-After response header with a parameter in delta-seconds. + */ + public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h + + /** + * The maximum number of redirects. + */ + public static final int MAX_REDIRECTS = 5; // can't be more than 7. + + /** + * The time between a failure and the first retry after an IOException. + * Each subsequent retry grows exponentially, doubling each time. + * The time is in seconds. + */ + public static final int RETRY_FIRST_DELAY = 30; + + /** Enable separate connectivity logging */ + public static final boolean LOGX = true; + + /** Enable verbose logging */ + public static final boolean LOGV = false; + + /** Enable super-verbose logging */ + private static final boolean LOCAL_LOGVV = false; + public static final boolean LOGVV = LOCAL_LOGVV && LOGV; + + /** + * This download has successfully completed. + * Warning: there might be other status values that indicate success + * in the future. + * Use isSucccess() to capture the entire category. + */ + public static final int STATUS_SUCCESS = 200; + + /** + * This request couldn't be parsed. This is also used when processing + * requests with unknown/unsupported URI schemes. + */ + public static final int STATUS_BAD_REQUEST = 400; + + /** + * This download can't be performed because the content type cannot be + * handled. + */ + public static final int STATUS_NOT_ACCEPTABLE = 406; + + /** + * This download cannot be performed because the length cannot be + * determined accurately. This is the code for the HTTP error "Length + * Required", which is typically used when making requests that require + * a content length but don't have one, and it is also used in the + * client when a response is received whose length cannot be determined + * accurately (therefore making it impossible to know when a download + * completes). + */ + public static final int STATUS_LENGTH_REQUIRED = 411; + + /** + * This download was interrupted and cannot be resumed. + * This is the code for the HTTP error "Precondition Failed", and it is + * also used in situations where the client doesn't have an ETag at all. + */ + public static final int STATUS_PRECONDITION_FAILED = 412; + + /** + * The lowest-valued error status that is not an actual HTTP status code. + */ + public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; + + /** + * The requested destination file already exists. + */ + public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; + + /** + * Some possibly transient error occurred, but we can't resume the download. + */ + public static final int STATUS_CANNOT_RESUME = 489; + + /** + * This download was canceled + */ + public static final int STATUS_CANCELED = 490; + + /** + * This download has completed with an error. + * Warning: there will be other status values that indicate errors in + * the future. Use isStatusError() to capture the entire category. + */ + public static final int STATUS_UNKNOWN_ERROR = 491; + + /** + * This download couldn't be completed because of a storage issue. + * Typically, that's because the filesystem is missing or full. + * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} + * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. + */ + public static final int STATUS_FILE_ERROR = 492; + + /** + * This download couldn't be completed because of an HTTP + * redirect response that the download manager couldn't + * handle. + */ + public static final int STATUS_UNHANDLED_REDIRECT = 493; + + /** + * This download couldn't be completed because of an + * unspecified unhandled HTTP code. + */ + public static final int STATUS_UNHANDLED_HTTP_CODE = 494; + + /** + * This download couldn't be completed because of an + * error receiving or processing data at the HTTP level. + */ + public static final int STATUS_HTTP_DATA_ERROR = 495; + + /** + * This download couldn't be completed because of an + * HttpException while setting up the request. + */ + public static final int STATUS_HTTP_EXCEPTION = 496; + + /** + * This download couldn't be completed because there were + * too many redirects. + */ + public static final int STATUS_TOO_MANY_REDIRECTS = 497; + + /** + * This download couldn't be completed due to insufficient storage + * space. Typically, this is because the SD card is full. + */ + public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; + + /** + * This download couldn't be completed because no external storage + * device was found. Typically, this is because the SD card is not + * mounted. + */ + public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; + + /** + * The wake duration to check to see if a download is possible. + */ + public static final long WATCHDOG_WAKE_TIMER = 60*1000; + + /** + * The wake duration to check to see if the process was killed. + */ + public static final long ACTIVE_THREAD_WATCHDOG = 5*1000; + +} \ No newline at end of file diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java new file mode 100644 index 0000000000..9cb294d721 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import android.os.Parcel; +import android.os.Parcelable; + + +/** + * This class contains progress information about the active download(s). + * + * When you build the Activity that initiates a download and tracks the + * progress by implementing the {@link IDownloaderClient} interface, you'll + * receive a DownloadProgressInfo object in each call to the {@link + * IDownloaderClient#onDownloadProgress} method. This allows you to update + * your activity's UI with information about the download progress, such + * as the progress so far, time remaining and current speed. + */ +public class DownloadProgressInfo implements Parcelable { + public long mOverallTotal; + public long mOverallProgress; + public long mTimeRemaining; // time remaining + public float mCurrentSpeed; // speed in KB/S + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel p, int i) { + p.writeLong(mOverallTotal); + p.writeLong(mOverallProgress); + p.writeLong(mTimeRemaining); + p.writeFloat(mCurrentSpeed); + } + + public DownloadProgressInfo(Parcel p) { + mOverallTotal = p.readLong(); + mOverallProgress = p.readLong(); + mTimeRemaining = p.readLong(); + mCurrentSpeed = p.readFloat(); + } + + public DownloadProgressInfo(long overallTotal, long overallProgress, + long timeRemaining, + float currentSpeed) { + this.mOverallTotal = overallTotal; + this.mOverallProgress = overallProgress; + this.mTimeRemaining = timeRemaining; + this.mCurrentSpeed = currentSpeed; + } + + public static final Creator CREATOR = new Creator() { + @Override + public DownloadProgressInfo createFromParcel(Parcel parcel) { + return new DownloadProgressInfo(parcel); + } + + @Override + public DownloadProgressInfo[] newArray(int i) { + return new DownloadProgressInfo[i]; + } + }; + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java new file mode 100644 index 0000000000..2201751254 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import com.google.android.vending.expansion.downloader.impl.DownloaderService; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + + + +/** + * This class binds the service API to your application client. It contains the IDownloaderClient proxy, + * which is used to call functions in your client as well as the Stub, which is used to call functions + * in the client implementation of IDownloaderClient. + * + *

The IPC is implemented using an Android Messenger and a service Binder. The connect method + * should be called whenever the client wants to bind to the service. It opens up a service connection + * that ends up calling the onServiceConnected client API that passes the service messenger + * in. If the client wants to be notified by the service, it is responsible for then passing its + * messenger to the service in a separate call. + * + *

Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}. + * + *

When your application first starts, you should first check whether your app's expansion files are + * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which + * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method + * returns a value indicating whether download is required or not. + * + *

If a download is required, {@link #startDownloadServiceIfRequired} begins the download through + * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link + * IStub} object that you need in order to receive calls through your {@link IDownloaderClient} + * interface. + */ +public class DownloaderClientMarshaller { + public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10; + public static final int MSG_ONDOWNLOADPROGRESS = 11; + public static final int MSG_ONSERVICECONNECTED = 12; + + public static final String PARAM_NEW_STATE = "newState"; + public static final String PARAM_PROGRESS = "progress"; + public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; + + public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED; + public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED; + public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED; + + private static class Proxy implements IDownloaderClient { + private Messenger mServiceMessenger; + + @Override + public void onDownloadStateChanged(int newState) { + Bundle params = new Bundle(1); + params.putInt(PARAM_NEW_STATE, newState); + send(MSG_ONDOWNLOADSTATE_CHANGED, params); + } + + @Override + public void onDownloadProgress(DownloadProgressInfo progress) { + Bundle params = new Bundle(1); + params.putParcelable(PARAM_PROGRESS, progress); + send(MSG_ONDOWNLOADPROGRESS, params); + } + + private void send(int method, Bundle params) { + Message m = Message.obtain(null, method); + m.setData(params); + try { + mServiceMessenger.send(m); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public Proxy(Messenger msg) { + mServiceMessenger = msg; + } + + @Override + public void onServiceConnected(Messenger m) { + /** + * This is never called through the proxy. + */ + } + } + + private static class Stub implements IStub { + private IDownloaderClient mItf = null; + private Class mDownloaderServiceClass; + private boolean mBound; + private Messenger mServiceMessenger; + private Context mContext; + /** + * Target we publish for clients to send messages to IncomingHandler. + */ + final Messenger mMessenger = new Messenger(new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ONDOWNLOADPROGRESS: + Bundle bun = msg.getData(); + if ( null != mContext ) { + bun.setClassLoader(mContext.getClassLoader()); + DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData() + .getParcelable(PARAM_PROGRESS); + mItf.onDownloadProgress(dpi); + } + break; + case MSG_ONDOWNLOADSTATE_CHANGED: + mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE)); + break; + case MSG_ONSERVICECONNECTED: + mItf.onServiceConnected( + (Messenger) msg.getData().getParcelable(PARAM_MESSENGER)); + break; + } + } + }); + + public Stub(IDownloaderClient itf, Class downloaderService) { + mItf = itf; + mDownloaderServiceClass = downloaderService; + } + + /** + * Class for interacting with the main interface of the service. + */ + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the object we can use to + // interact with the service. We are communicating with the + // service using a Messenger, so here we get a client-side + // representation of that from the raw IBinder object. + mServiceMessenger = new Messenger(service); + mItf.onServiceConnected( + mServiceMessenger); + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + mServiceMessenger = null; + } + }; + + @Override + public void connect(Context c) { + mContext = c; + Intent bindIntent = new Intent(c, mDownloaderServiceClass); + bindIntent.putExtra(PARAM_MESSENGER, mMessenger); + if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) { + if ( Constants.LOGVV ) { + Log.d(Constants.TAG, "Service Unbound"); + } + } else { + mBound = true; + } + + } + + @Override + public void disconnect(Context c) { + if (mBound) { + c.unbindService(mConnection); + mBound = false; + } + mContext = null; + } + + @Override + public Messenger getMessenger() { + return mMessenger; + } + } + + /** + * Returns a proxy that will marshal calls to IDownloaderClient methods + * + * @param msg + * @return + */ + public static IDownloaderClient CreateProxy(Messenger msg) { + return new Proxy(msg); + } + + /** + * Returns a stub object that, when connected, will listen for marshaled + * {@link IDownloaderClient} methods and translate them into calls to the supplied + * interface. + * + * @param itf An implementation of IDownloaderClient that will be called + * when remote method calls are unmarshaled. + * @param downloaderService The class for your implementation of {@link + * impl.DownloaderService}. + * @return The {@link IStub} that allows you to connect to the service such that + * your {@link IDownloaderClient} receives status updates. + */ + public static IStub CreateStub(IDownloaderClient itf, Class downloaderService) { + return new Stub(itf, downloaderService); + } + + /** + * Starts the download if necessary. This function starts a flow that does ` + * many things. 1) Checks to see if the APK version has been checked and + * the metadata database updated 2) If the APK version does not match, + * checks the new LVL status to see if a new download is required 3) If the + * APK version does match, then checks to see if the download(s) have been + * completed 4) If the downloads have been completed, returns + * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the + * startup of an application to quickly ascertain if the application needs + * to wait to hear about any updated APK expansion files. Note that this does + * mean that the application MUST be run for the first time with a network + * connection, even if Market delivers all of the files. + * + * @param context Your application Context. + * @param notificationClient A PendingIntent to start the Activity in your application + * that shows the download progress and which will also start the application when download + * completes. + * @param serviceClass the class of your {@link imp.DownloaderService} implementation + * @return whether the service was started and the reason for starting the service. + * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link + * #DOWNLOAD_REQUIRED}. + * @throws NameNotFoundException + */ + public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, + Class serviceClass) + throws NameNotFoundException { + return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, + serviceClass); + } + + /** + * This version assumes that the intent contains the pending intent as a parameter. This + * is used for responding to alarms. + *

The pending intent must be in an extra with the key {@link + * impl.DownloaderService#EXTRA_PENDING_INTENT}. + * + * @param context + * @param notificationClient + * @param serviceClass the class of the service to start + * @return + * @throws NameNotFoundException + */ + public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, + Class serviceClass) + throws NameNotFoundException { + return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, + serviceClass); + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java new file mode 100644 index 0000000000..054eaa9895 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import com.google.android.vending.expansion.downloader.impl.DownloaderService; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; + + + +/** + * This class is used by the client activity to proxy requests to the Downloader + * Service. + * + * Most importantly, you must call {@link #CreateProxy} during the {@link + * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate + * an {@link IDownloaderService} object that you can then use to issue commands to the {@link + * DownloaderService} (such as to pause and resume downloads). + */ +public class DownloaderServiceMarshaller { + + public static final int MSG_REQUEST_ABORT_DOWNLOAD = + 1; + public static final int MSG_REQUEST_PAUSE_DOWNLOAD = + 2; + public static final int MSG_SET_DOWNLOAD_FLAGS = + 3; + public static final int MSG_REQUEST_CONTINUE_DOWNLOAD = + 4; + public static final int MSG_REQUEST_DOWNLOAD_STATE = + 5; + public static final int MSG_REQUEST_CLIENT_UPDATE = + 6; + + public static final String PARAMS_FLAGS = "flags"; + public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; + + private static class Proxy implements IDownloaderService { + private Messenger mMsg; + + private void send(int method, Bundle params) { + Message m = Message.obtain(null, method); + m.setData(params); + try { + mMsg.send(m); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + public Proxy(Messenger msg) { + mMsg = msg; + } + + @Override + public void requestAbortDownload() { + send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle()); + } + + @Override + public void requestPauseDownload() { + send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle()); + } + + @Override + public void setDownloadFlags(int flags) { + Bundle params = new Bundle(); + params.putInt(PARAMS_FLAGS, flags); + send(MSG_SET_DOWNLOAD_FLAGS, params); + } + + @Override + public void requestContinueDownload() { + send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle()); + } + + @Override + public void requestDownloadStatus() { + send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle()); + } + + @Override + public void onClientUpdated(Messenger clientMessenger) { + Bundle bundle = new Bundle(1); + bundle.putParcelable(PARAM_MESSENGER, clientMessenger); + send(MSG_REQUEST_CLIENT_UPDATE, bundle); + } + } + + private static class Stub implements IStub { + private IDownloaderService mItf = null; + final Messenger mMessenger = new Messenger(new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REQUEST_ABORT_DOWNLOAD: + mItf.requestAbortDownload(); + break; + case MSG_REQUEST_CONTINUE_DOWNLOAD: + mItf.requestContinueDownload(); + break; + case MSG_REQUEST_PAUSE_DOWNLOAD: + mItf.requestPauseDownload(); + break; + case MSG_SET_DOWNLOAD_FLAGS: + mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS)); + break; + case MSG_REQUEST_DOWNLOAD_STATE: + mItf.requestDownloadStatus(); + break; + case MSG_REQUEST_CLIENT_UPDATE: + mItf.onClientUpdated((Messenger) msg.getData().getParcelable( + PARAM_MESSENGER)); + break; + } + } + }); + + public Stub(IDownloaderService itf) { + mItf = itf; + } + + @Override + public Messenger getMessenger() { + return mMessenger; + } + + @Override + public void connect(Context c) { + + } + + @Override + public void disconnect(Context c) { + + } + } + + /** + * Returns a proxy that will marshall calls to IDownloaderService methods + * + * @param ctx + * @return + */ + public static IDownloaderService CreateProxy(Messenger msg) { + return new Proxy(msg); + } + + /** + * Returns a stub object that, when connected, will listen for marshalled + * IDownloaderService methods and translate them into calls to the supplied + * interface. + * + * @param itf An implementation of IDownloaderService that will be called + * when remote method calls are unmarshalled. + * @return + */ + public static IStub CreateStub(IDownloaderService itf) { + return new Stub(itf); + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java new file mode 100644 index 0000000000..b4c28d36e7 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/Helpers.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import com.godot.game.R; + +import android.content.Context; +import android.os.Environment; +import android.os.StatFs; +import android.os.SystemClock; +import android.util.Log; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Random; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Some helper functions for the download manager + */ +public class Helpers { + + public static Random sRandom = new Random(SystemClock.uptimeMillis()); + + /** Regex used to parse content-disposition headers */ + private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern + .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); + + private Helpers() { + } + + /* + * Parse the Content-Disposition HTTP Header. The format of the header is + * defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This + * header provides a filename for content that is going to be downloaded to + * the file system. We only support the attachment type. + */ + static String parseContentDisposition(String contentDisposition) { + try { + Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); + if (m.find()) { + return m.group(1); + } + } catch (IllegalStateException ex) { + // This function is defined as returning null when it can't parse + // the header + } + return null; + } + + /** + * @return the root of the filesystem containing the given path + */ + public static File getFilesystemRoot(String path) { + File cache = Environment.getDownloadCacheDirectory(); + if (path.startsWith(cache.getPath())) { + return cache; + } + File external = Environment.getExternalStorageDirectory(); + if (path.startsWith(external.getPath())) { + return external; + } + throw new IllegalArgumentException( + "Cannot determine filesystem root for " + path); + } + + public static boolean isExternalMediaMounted() { + if (!Environment.getExternalStorageState().equals( + Environment.MEDIA_MOUNTED)) { + // No SD card found. + if ( Constants.LOGVV ) { + Log.d(Constants.TAG, "no external storage"); + } + return false; + } + return true; + } + + /** + * @return the number of bytes available on the filesystem rooted at the + * given File + */ + public static long getAvailableBytes(File root) { + StatFs stat = new StatFs(root.getPath()); + // put a bit of margin (in case creating the file grows the system by a + // few blocks) + long availableBlocks = (long) stat.getAvailableBlocks() - 4; + return stat.getBlockSize() * availableBlocks; + } + + /** + * Checks whether the filename looks legitimate + */ + public static boolean isFilenameValid(String filename) { + filename = filename.replaceFirst("/+", "/"); // normalize leading + // slashes + return filename.startsWith(Environment.getDownloadCacheDirectory().toString()) + || filename.startsWith(Environment.getExternalStorageDirectory().toString()); + } + + /* + * Delete the given file from device + */ + /* package */static void deleteFile(String path) { + try { + File file = new File(path); + file.delete(); + } catch (Exception e) { + Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e); + } + } + + /** + * Showing progress in MB here. It would be nice to choose the unit (KB, MB, + * GB) based on total file size, but given what we know about the expected + * ranges of file sizes for APK expansion files, it's probably not necessary. + * + * @param overallProgress + * @param overallTotal + * @return + */ + + static public String getDownloadProgressString(long overallProgress, long overallTotal) { + if (overallTotal == 0) { + if ( Constants.LOGVV ) { + Log.e(Constants.TAG, "Notification called when total is zero"); + } + return ""; + } + return String.format("%.2f", + (float) overallProgress / (1024.0f * 1024.0f)) + + "MB /" + + String.format("%.2f", (float) overallTotal / + (1024.0f * 1024.0f)) + "MB"; + } + + /** + * Adds a percentile to getDownloadProgressString. + * + * @param overallProgress + * @param overallTotal + * @return + */ + static public String getDownloadProgressStringNotification(long overallProgress, + long overallTotal) { + if (overallTotal == 0) { + if ( Constants.LOGVV ) { + Log.e(Constants.TAG, "Notification called when total is zero"); + } + return ""; + } + return getDownloadProgressString(overallProgress, overallTotal) + " (" + + getDownloadProgressPercent(overallProgress, overallTotal) + ")"; + } + + public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { + if (overallTotal == 0) { + if ( Constants.LOGVV ) { + Log.e(Constants.TAG, "Notification called when total is zero"); + } + return ""; + } + return Long.toString(overallProgress * 100 / overallTotal) + "%"; + } + + public static String getSpeedString(float bytesPerMillisecond) { + return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); + } + + public static String getTimeRemaining(long durationInMilliseconds) { + SimpleDateFormat sdf; + if (durationInMilliseconds > 1000 * 60 * 60) { + sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); + } else { + sdf = new SimpleDateFormat("mm:ss", Locale.getDefault()); + } + return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset())); + } + + /** + * Returns the file name (without full path) for an Expansion APK file from + * the given context. + * + * @param c the context + * @param mainFile true for main file, false for patch file + * @param versionCode the version of the file + * @return String the file name of the expansion file + */ + public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) { + return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb"; + } + + /** + * Returns the filename (where the file should be saved) from info about a + * download + */ + static public String generateSaveFileName(Context c, String fileName) { + String path = getSaveFilePath(c) + + File.separator + fileName; + return path; + } + + static public String getSaveFilePath(Context c) { + File root = Environment.getExternalStorageDirectory(); + String path = root.toString() + Constants.EXP_PATH + c.getPackageName(); + return path; + } + + /** + * Helper function to ascertain the existence of a file and return + * true/false appropriately + * + * @param c the app/activity/service context + * @param fileName the name (sans path) of the file to query + * @param fileSize the size that the file must match + * @param deleteFileOnMismatch if the file sizes do not match, delete the + * file + * @return true if it does exist, false otherwise + */ + static public boolean doesFileExist(Context c, String fileName, long fileSize, + boolean deleteFileOnMismatch) { + // the file may have been delivered by Market --- let's make sure + // it's the size we expect + File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); + if (fileForNewFile.exists()) { + if (fileForNewFile.length() == fileSize) { + return true; + } + if (deleteFileOnMismatch) { + // delete the file --- we won't be able to resume + // because we cannot confirm the integrity of the file + fileForNewFile.delete(); + } + } + return false; + } + + /** + * Converts download states that are returned by the {@link + * IDownloaderClient#onDownloadStateChanged} callback into usable strings. + * This is useful if using the state strings built into the library to display user messages. + * @param state One of the STATE_* constants from {@link IDownloaderClient}. + * @return string resource ID for the corresponding string. + */ + static public int getDownloaderStringResourceIDFromState(int state) { + switch (state) { + case IDownloaderClient.STATE_IDLE: + return R.string.state_idle; + case IDownloaderClient.STATE_FETCHING_URL: + return R.string.state_fetching_url; + case IDownloaderClient.STATE_CONNECTING: + return R.string.state_connecting; + case IDownloaderClient.STATE_DOWNLOADING: + return R.string.state_downloading; + case IDownloaderClient.STATE_COMPLETED: + return R.string.state_completed; + case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE: + return R.string.state_paused_network_unavailable; + case IDownloaderClient.STATE_PAUSED_BY_REQUEST: + return R.string.state_paused_by_request; + case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: + return R.string.state_paused_wifi_disabled; + case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: + return R.string.state_paused_wifi_unavailable; + case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED: + return R.string.state_paused_wifi_disabled; + case IDownloaderClient.STATE_PAUSED_NEED_WIFI: + return R.string.state_paused_wifi_unavailable; + case IDownloaderClient.STATE_PAUSED_ROAMING: + return R.string.state_paused_roaming; + case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE: + return R.string.state_paused_network_setup_failure; + case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: + return R.string.state_paused_sdcard_unavailable; + case IDownloaderClient.STATE_FAILED_UNLICENSED: + return R.string.state_failed_unlicensed; + case IDownloaderClient.STATE_FAILED_FETCHING_URL: + return R.string.state_failed_fetching_url; + case IDownloaderClient.STATE_FAILED_SDCARD_FULL: + return R.string.state_failed_sdcard_full; + case IDownloaderClient.STATE_FAILED_CANCELED: + return R.string.state_failed_cancelled; + default: + return R.string.state_unknown; + } + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java new file mode 100644 index 0000000000..b8511a62a0 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import android.os.Messenger; + +/** + * This interface should be implemented by the client activity for the + * downloader. It is used to pass status from the service to the client. + */ +public interface IDownloaderClient { + static final int STATE_IDLE = 1; + static final int STATE_FETCHING_URL = 2; + static final int STATE_CONNECTING = 3; + static final int STATE_DOWNLOADING = 4; + static final int STATE_COMPLETED = 5; + + static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6; + static final int STATE_PAUSED_BY_REQUEST = 7; + + /** + * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and + * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and + * cellular permission will restart the service. Wi-Fi disabled means that + * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the + * other case Wi-Fi is enabled but not available. + */ + static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8; + static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9; + + /** + * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that + * Wi-Fi is unavailable and cellular permission will NOT restart the + * service. Wi-Fi disabled means that the Wi-Fi manager is returning that + * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not + * available. + *

+ * The service does not return these values. We recommend that app + * developers with very large payloads do not allow these payloads to be + * downloaded over cellular connections. + */ + static final int STATE_PAUSED_WIFI_DISABLED = 10; + static final int STATE_PAUSED_NEED_WIFI = 11; + + static final int STATE_PAUSED_ROAMING = 12; + + /** + * Scary case. We were on a network that redirected us to another website + * that delivered us the wrong file. + */ + static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13; + + static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; + + static final int STATE_FAILED_UNLICENSED = 15; + static final int STATE_FAILED_FETCHING_URL = 16; + static final int STATE_FAILED_SDCARD_FULL = 17; + static final int STATE_FAILED_CANCELED = 18; + + static final int STATE_FAILED = 19; + + /** + * Called internally by the stub when the service is bound to the client. + *

+ * Critical implementation detail. In onServiceConnected we create the + * remote service and marshaler. This is how we pass the client information + * back to the service so the client can be properly notified of changes. We + * must do this every time we reconnect to the service. + *

+ * That is, when you receive this callback, you should call + * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member + * instance of {@link IDownloaderService}, then call + * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved + * from your {@link IStub} proxy object. + * + * @param m the service Messenger. This Messenger is used to call the + * service API from the client. + */ + void onServiceConnected(Messenger m); + + /** + * Called when the download state changes. Depending on the state, there may + * be user requests. The service is free to change the download state in the + * middle of a user request, so the client should be able to handle this. + *

+ * The Downloader Library includes a collection of string resources that + * correspond to each of the states, which you can use to provide users a + * useful message based on the state provided in this callback. To fetch the + * appropriate string for a state, call + * {@link Helpers#getDownloaderStringResourceIDFromState}. + *

+ * What this means to the developer: The application has gotten a message + * that the download has paused due to lack of WiFi. The developer should + * then show UI asking the user if they want to enable downloading over + * cellular connections with appropriate warnings. If the application + * suddenly starts downloading, the application should revert to showing the + * progress again, rather than leaving up the download over cellular UI up. + * + * @param newState one of the STATE_* values defined in IDownloaderClient + */ + void onDownloadStateChanged(int newState); + + /** + * Shows the download progress. This is intended to be used to fill out a + * client UI. This progress should only be shown in a few states such as + * STATE_DOWNLOADING. + * + * @param progress the DownloadProgressInfo object containing the current + * progress of all downloads. + */ + void onDownloadProgress(DownloadProgressInfo progress); +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java new file mode 100644 index 0000000000..4789afe19c --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/IDownloaderService.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import com.google.android.vending.expansion.downloader.impl.DownloaderService; +import android.os.Messenger; + +/** + * This interface is implemented by the DownloaderService and by the + * DownloaderServiceMarshaller. It contains functions to control the service. + * When a client binds to the service, it must call the onClientUpdated + * function. + *

+ * You can acquire a proxy that implements this interface for your service by + * calling {@link DownloaderServiceMarshaller#CreateProxy} during the + * {@link IDownloaderClient#onServiceConnected} callback. At which point, you + * should immediately call {@link #onClientUpdated}. + */ +public interface IDownloaderService { + /** + * Set this flag in response to the + * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then + * call RequestContinueDownload to resume a download + */ + public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1; + + /** + * Request that the service abort the current download. The service should + * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}. + */ + void requestAbortDownload(); + + /** + * Request that the service pause the current download. The service should + * respond by changing the state to + * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. + */ + void requestPauseDownload(); + + /** + * Request that the service continue a paused download, when in any paused + * or failed state, including + * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. + */ + void requestContinueDownload(); + + /** + * Set the flags for this download (e.g. + * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). + * + * @param flags + */ + void setDownloadFlags(int flags); + + /** + * Requests that the download status be sent to the client. + */ + void requestDownloadStatus(); + + /** + * Call this when you get {@link + * IDownloaderClient.onServiceConnected(Messenger m)} from the + * DownloaderClient to register the client with the service. It will + * automatically send the current status to the client. + * + * @param clientMessenger + */ + void onClientUpdated(Messenger clientMessenger); +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java new file mode 100644 index 0000000000..d5bc3a843e --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/IStub.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import android.content.Context; +import android.os.Messenger; + +/** + * This is the interface that is used to connect/disconnect from the downloader + * service. + *

+ * You should get a proxy object that implements this interface by calling + * {@link DownloaderClientMarshaller#CreateStub} in your activity when the + * downloader service starts. Then, call {@link #connect} during your activity's + * onResume() and call {@link #disconnect} during onStop(). + *

+ * Then during the {@link IDownloaderClient#onServiceConnected} callback, you + * should call {@link #getMessenger} to pass the stub's Messenger object to + * {@link IDownloaderService#onClientUpdated}. + */ +public interface IStub { + Messenger getMessenger(); + + void connect(Context c); + + void disconnect(Context c); +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java new file mode 100644 index 0000000000..12edd97ab2 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/SystemFacade.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.telephony.TelephonyManager; +import android.util.Log; + +/** + * Contains useful helper functions, typically tied to the application context. + */ +class SystemFacade { + private Context mContext; + private NotificationManager mNotificationManager; + + public SystemFacade(Context context) { + mContext = context; + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + public Integer getActiveNetworkType() { + ConnectivityManager connectivity = + (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + Log.w(Constants.TAG, "couldn't get connectivity manager"); + return null; + } + + NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); + if (activeInfo == null) { + if (Constants.LOGVV) { + Log.v(Constants.TAG, "network is not available"); + } + return null; + } + return activeInfo.getType(); + } + + public boolean isNetworkRoaming() { + ConnectivityManager connectivity = + (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + Log.w(Constants.TAG, "couldn't get connectivity manager"); + return false; + } + + NetworkInfo info = connectivity.getActiveNetworkInfo(); + boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); + TelephonyManager tm = (TelephonyManager) mContext + .getSystemService(Context.TELEPHONY_SERVICE); + if (null == tm) { + Log.w(Constants.TAG, "couldn't get telephony manager"); + return false; + } + boolean isRoaming = isMobile && tm.isNetworkRoaming(); + if (Constants.LOGVV && isRoaming) { + Log.v(Constants.TAG, "network is roaming"); + } + return isRoaming; + } + + public Long getMaxBytesOverMobile() { + return (long) Integer.MAX_VALUE; + } + + public Long getRecommendedMaxBytesOverMobile() { + return 2097152L; + } + + public void sendBroadcast(Intent intent) { + mContext.sendBroadcast(intent); + } + + public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException { + return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid; + } + + public void postNotification(long id, Notification notification) { + /** + * TODO: The system notification manager takes ints, not longs, as IDs, + * but the download manager uses IDs take straight from the database, + * which are longs. This will have to be dealt with at some point. + */ + mNotificationManager.notify((int) id, notification); + } + + public void cancelNotification(long id) { + mNotificationManager.cancel((int) id); + } + + public void cancelAllNotifications() { + mNotificationManager.cancelAll(); + } + + public void startThread(Thread thread) { + thread.start(); + } +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java new file mode 100644 index 0000000000..4667acce67 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This is a port of AndroidHttpClient to pre-Froyo devices, that takes advantage of + * the SSLSessionCache added Froyo devices using reflection. + */ + +package com.google.android.vending.expansion.downloader.impl; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.params.HttpClientParams; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.scheme.SocketFactory; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.entity.AbstractHttpEntity; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.RequestWrapper; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.HttpContext; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.SSLCertificateSocketFactory; +import android.os.Looper; +import android.util.Log; + +/** + * Subclass of the Apache {@link DefaultHttpClient} that is configured with + * reasonable default settings and registered schemes for Android, and + * also lets the user add {@link HttpRequestInterceptor} classes. + * Don't create this directly, use the {@link #newInstance} factory method. + * + *

This client processes cookies but does not retain them by default. + * To retain cookies, simply add a cookie store to the HttpContext:

+ * + *
context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
+ */ +public final class AndroidHttpClient implements HttpClient { + + static Class sSslSessionCacheClass; + static { + // if we are on Froyo+ devices, we can take advantage of the SSLSessionCache + try { + sSslSessionCacheClass = Class.forName("android.net.SSLSessionCache"); + } catch (Exception e) { + + } + } + + // Gzip of data shorter than this probably won't be worthwhile + public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256; + + // Default connection and socket timeout of 60 seconds. Tweak to taste. + private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000; + + private static final String TAG = "AndroidHttpClient"; + + + /** Interceptor throws an exception if the executing thread is blocked */ + private static final HttpRequestInterceptor sThreadCheckInterceptor = + new HttpRequestInterceptor() { + public void process(HttpRequest request, HttpContext context) { + // Prevent the HttpRequest from being sent on the main thread + if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) { + throw new RuntimeException("This thread forbids HTTP requests"); + } + } + }; + + /** + * Create a new HttpClient with reasonable defaults (which you can update). + * + * @param userAgent to report in your HTTP requests + * @param context to use for caching SSL sessions (may be null for no caching) + * @return AndroidHttpClient for you to use for all your requests. + */ + public static AndroidHttpClient newInstance(String userAgent, Context context) { + HttpParams params = new BasicHttpParams(); + + // Turn off stale checking. Our connections break all the time anyway, + // and it's not worth it to pay the penalty of checking every time. + HttpConnectionParams.setStaleCheckingEnabled(params, false); + + HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT); + HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT); + HttpConnectionParams.setSocketBufferSize(params, 8192); + + // Don't handle redirects -- return them to the caller. Our code + // often wants to re-POST after a redirect, which we must do ourselves. + HttpClientParams.setRedirecting(params, false); + + Object sessionCache = null; + // Use a session cache for SSL sockets -- Froyo only + if ( null != context && null != sSslSessionCacheClass ) { + Constructor ct; + try { + ct = sSslSessionCacheClass.getConstructor(Context.class); + sessionCache = ct.newInstance(context); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InstantiationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + // Set the specified user agent and register standard protocols. + HttpProtocolParams.setUserAgent(params, userAgent); + SchemeRegistry schemeRegistry = new SchemeRegistry(); + schemeRegistry.register(new Scheme("http", + PlainSocketFactory.getSocketFactory(), 80)); + SocketFactory sslCertificateSocketFactory = null; + if ( null != sessionCache ) { + Method getHttpSocketFactoryMethod; + try { + getHttpSocketFactoryMethod = SSLCertificateSocketFactory.class.getDeclaredMethod("getHttpSocketFactory",Integer.TYPE, sSslSessionCacheClass); + sslCertificateSocketFactory = (SocketFactory)getHttpSocketFactoryMethod.invoke(null, SOCKET_OPERATION_TIMEOUT, sessionCache); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + if ( null == sslCertificateSocketFactory ) { + sslCertificateSocketFactory = SSLSocketFactory.getSocketFactory(); + } + schemeRegistry.register(new Scheme("https", + sslCertificateSocketFactory, 443)); + + ClientConnectionManager manager = + new ThreadSafeClientConnManager(params, schemeRegistry); + + // We use a factory method to modify superclass initialization + // parameters without the funny call-a-static-method dance. + return new AndroidHttpClient(manager, params); + } + + /** + * Create a new HttpClient with reasonable defaults (which you can update). + * @param userAgent to report in your HTTP requests. + * @return AndroidHttpClient for you to use for all your requests. + */ + public static AndroidHttpClient newInstance(String userAgent) { + return newInstance(userAgent, null /* session cache */); + } + + private final HttpClient delegate; + + private RuntimeException mLeakedException = new IllegalStateException( + "AndroidHttpClient created and never closed"); + + private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) { + this.delegate = new DefaultHttpClient(ccm, params) { + @Override + protected BasicHttpProcessor createHttpProcessor() { + // Add interceptor to prevent making requests from main thread. + BasicHttpProcessor processor = super.createHttpProcessor(); + processor.addRequestInterceptor(sThreadCheckInterceptor); + processor.addRequestInterceptor(new CurlLogger()); + + return processor; + } + + @Override + protected HttpContext createHttpContext() { + // Same as DefaultHttpClient.createHttpContext() minus the + // cookie store. + HttpContext context = new BasicHttpContext(); + context.setAttribute( + ClientContext.AUTHSCHEME_REGISTRY, + getAuthSchemes()); + context.setAttribute( + ClientContext.COOKIESPEC_REGISTRY, + getCookieSpecs()); + context.setAttribute( + ClientContext.CREDS_PROVIDER, + getCredentialsProvider()); + return context; + } + }; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (mLeakedException != null) { + Log.e(TAG, "Leak found", mLeakedException); + mLeakedException = null; + } + } + + /** + * Modifies a request to indicate to the server that we would like a + * gzipped response. (Uses the "Accept-Encoding" HTTP header.) + * @param request the request to modify + * @see #getUngzippedContent + */ + public static void modifyRequestToAcceptGzipResponse(HttpRequest request) { + request.addHeader("Accept-Encoding", "gzip"); + } + + /** + * Gets the input stream from a response entity. If the entity is gzipped + * then this will get a stream over the uncompressed data. + * + * @param entity the entity whose content should be read + * @return the input stream to read from + * @throws IOException + */ + public static InputStream getUngzippedContent(HttpEntity entity) + throws IOException { + InputStream responseStream = entity.getContent(); + if (responseStream == null) return responseStream; + Header header = entity.getContentEncoding(); + if (header == null) return responseStream; + String contentEncoding = header.getValue(); + if (contentEncoding == null) return responseStream; + if (contentEncoding.contains("gzip")) responseStream + = new GZIPInputStream(responseStream); + return responseStream; + } + + /** + * Release resources associated with this client. You must call this, + * or significant resources (sockets and memory) may be leaked. + */ + public void close() { + if (mLeakedException != null) { + getConnectionManager().shutdown(); + mLeakedException = null; + } + } + + public HttpParams getParams() { + return delegate.getParams(); + } + + public ClientConnectionManager getConnectionManager() { + return delegate.getConnectionManager(); + } + + public HttpResponse execute(HttpUriRequest request) throws IOException { + return delegate.execute(request); + } + + public HttpResponse execute(HttpUriRequest request, HttpContext context) + throws IOException { + return delegate.execute(request, context); + } + + public HttpResponse execute(HttpHost target, HttpRequest request) + throws IOException { + return delegate.execute(target, request); + } + + public HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) throws IOException { + return delegate.execute(target, request, context); + } + + public T execute(HttpUriRequest request, + ResponseHandler responseHandler) + throws IOException, ClientProtocolException { + return delegate.execute(request, responseHandler); + } + + public T execute(HttpUriRequest request, + ResponseHandler responseHandler, HttpContext context) + throws IOException, ClientProtocolException { + return delegate.execute(request, responseHandler, context); + } + + public T execute(HttpHost target, HttpRequest request, + ResponseHandler responseHandler) throws IOException, + ClientProtocolException { + return delegate.execute(target, request, responseHandler); + } + + public T execute(HttpHost target, HttpRequest request, + ResponseHandler responseHandler, HttpContext context) + throws IOException, ClientProtocolException { + return delegate.execute(target, request, responseHandler, context); + } + + /** + * Compress data to send to server. + * Creates a Http Entity holding the gzipped data. + * The data will not be compressed if it is too short. + * @param data The bytes to compress + * @return Entity holding the data + */ + public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver) + throws IOException { + AbstractHttpEntity entity; + if (data.length < getMinGzipSize(resolver)) { + entity = new ByteArrayEntity(data); + } else { + ByteArrayOutputStream arr = new ByteArrayOutputStream(); + OutputStream zipper = new GZIPOutputStream(arr); + zipper.write(data); + zipper.close(); + entity = new ByteArrayEntity(arr.toByteArray()); + entity.setContentEncoding("gzip"); + } + return entity; + } + + /** + * Retrieves the minimum size for compressing data. + * Shorter data will not be compressed. + */ + public static long getMinGzipSize(ContentResolver resolver) { + return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant. + } + + /* cURL logging support. */ + + /** + * Logging tag and level. + */ + private static class LoggingConfiguration { + + private final String tag; + private final int level; + + private LoggingConfiguration(String tag, int level) { + this.tag = tag; + this.level = level; + } + + /** + * Returns true if logging is turned on for this configuration. + */ + private boolean isLoggable() { + return Log.isLoggable(tag, level); + } + + /** + * Prints a message using this configuration. + */ + private void println(String message) { + Log.println(level, tag, message); + } + } + + /** cURL logging configuration. */ + private volatile LoggingConfiguration curlConfiguration; + + /** + * Enables cURL request logging for this client. + * + * @param name to log messages with + * @param level at which to log messages (see {@link android.util.Log}) + */ + public void enableCurlLogging(String name, int level) { + if (name == null) { + throw new NullPointerException("name"); + } + if (level < Log.VERBOSE || level > Log.ASSERT) { + throw new IllegalArgumentException("Level is out of range [" + + Log.VERBOSE + ".." + Log.ASSERT + "]"); + } + + curlConfiguration = new LoggingConfiguration(name, level); + } + + /** + * Disables cURL logging for this client. + */ + public void disableCurlLogging() { + curlConfiguration = null; + } + + /** + * Logs cURL commands equivalent to requests. + */ + private class CurlLogger implements HttpRequestInterceptor { + public void process(HttpRequest request, HttpContext context) + throws HttpException, IOException { + LoggingConfiguration configuration = curlConfiguration; + if (configuration != null + && configuration.isLoggable() + && request instanceof HttpUriRequest) { + // Never print auth token -- we used to check ro.secure=0 to + // enable that, but can't do that in unbundled code. + configuration.println(toCurl((HttpUriRequest) request, false)); + } + } + } + + /** + * Generates a cURL command equivalent to the given request. + */ + private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException { + StringBuilder builder = new StringBuilder(); + + builder.append("curl "); + + for (Header header: request.getAllHeaders()) { + if (!logAuthToken + && (header.getName().equals("Authorization") || + header.getName().equals("Cookie"))) { + continue; + } + builder.append("--header \""); + builder.append(header.toString().trim()); + builder.append("\" "); + } + + URI uri = request.getURI(); + + // If this is a wrapped request, use the URI from the original + // request instead. getURI() on the wrapper seems to return a + // relative URI. We want an absolute URI. + if (request instanceof RequestWrapper) { + HttpRequest original = ((RequestWrapper) request).getOriginal(); + if (original instanceof HttpUriRequest) { + uri = ((HttpUriRequest) original).getURI(); + } + } + + builder.append("\""); + builder.append(uri); + builder.append("\""); + + if (request instanceof HttpEntityEnclosingRequest) { + HttpEntityEnclosingRequest entityRequest = + (HttpEntityEnclosingRequest) request; + HttpEntity entity = entityRequest.getEntity(); + if (entity != null && entity.isRepeatable()) { + if (entity.getContentLength() < 1024) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + entity.writeTo(stream); + String entityString = stream.toString(); + + // TODO: Check the content type, too. + builder.append(" --data-ascii \"") + .append(entityString) + .append("\""); + } else { + builder.append(" [TOO MUCH DATA TO INCLUDE]"); + } + } + } + + return builder.toString(); + } + + /** + * Returns the date of the given HTTP date string. This method can identify + * and parse the date formats emitted by common HTTP servers, such as + * RFC 822, + * RFC 850, + * RFC 1036, + * RFC 1123 and + * ANSI + * C's asctime(). + * + * @return the number of milliseconds since Jan. 1, 1970, midnight GMT. + * @throws IllegalArgumentException if {@code dateString} is not a date or + * of an unsupported format. + */ + public static long parseDate(String dateString) { + return HttpDateTime.parse(dateString); + } +} \ No newline at end of file diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java new file mode 100755 index 0000000000..b77af7e085 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +/** + * This service differs from IntentService in a few minor ways/ It will not + * auto-stop itself after the intent is handled unless the target returns "true" + * in should stop. Since the goal of this service is to handle a single kind of + * intent, it does not queue up batches of intents of the same type. + */ +public abstract class CustomIntentService extends Service { + private String mName; + private boolean mRedelivery; + private volatile ServiceHandler mServiceHandler; + private volatile Looper mServiceLooper; + private static final String LOG_TAG = "CancellableIntentService"; + private static final int WHAT_MESSAGE = -10; + + public CustomIntentService(String paramString) { + this.mName = paramString; + } + + @Override + public IBinder onBind(Intent paramIntent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + HandlerThread localHandlerThread = new HandlerThread("IntentService[" + + this.mName + "]"); + localHandlerThread.start(); + this.mServiceLooper = localHandlerThread.getLooper(); + this.mServiceHandler = new ServiceHandler(this.mServiceLooper); + } + + @Override + public void onDestroy() { + Thread localThread = this.mServiceLooper.getThread(); + if ((localThread != null) && (localThread.isAlive())) { + localThread.interrupt(); + } + this.mServiceLooper.quit(); + Log.d(LOG_TAG, "onDestroy"); + } + + protected abstract void onHandleIntent(Intent paramIntent); + + protected abstract boolean shouldStop(); + + @Override + public void onStart(Intent paramIntent, int startId) { + if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) { + Message localMessage = this.mServiceHandler.obtainMessage(); + localMessage.arg1 = startId; + localMessage.obj = paramIntent; + localMessage.what = WHAT_MESSAGE; + this.mServiceHandler.sendMessage(localMessage); + } + } + + @Override + public int onStartCommand(Intent paramIntent, int flags, int startId) { + onStart(paramIntent, startId); + return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; + } + + public void setIntentRedelivery(boolean enabled) { + this.mRedelivery = enabled; + } + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message paramMessage) { + CustomIntentService.this + .onHandleIntent((Intent) paramMessage.obj); + if (shouldStop()) { + Log.d(LOG_TAG, "stopSelf"); + CustomIntentService.this.stopSelf(paramMessage.arg1); + Log.d(LOG_TAG, "afterStopSelf"); + } + } + } +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java new file mode 100644 index 0000000000..9a0ca02122 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +/** + * Uses the class-loader model to utilize the updated notification builders in + * Honeycomb while maintaining a compatible version for older devices. + */ +public class CustomNotificationFactory { + static public DownloadNotification.ICustomNotification createCustomNotification() { + if (android.os.Build.VERSION.SDK_INT > 13) + return new V14CustomNotification(); + else + return new V3CustomNotification(); + } +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java new file mode 100644 index 0000000000..45111b16a3 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import com.google.android.vending.expansion.downloader.Constants; +import com.google.android.vending.expansion.downloader.Helpers; + +import android.util.Log; + +/** + * Representation of information about an individual download from the database. + */ +public class DownloadInfo { + public String mUri; + public final int mIndex; + public final String mFileName; + public String mETag; + public long mTotalBytes; + public long mCurrentBytes; + public long mLastMod; + public int mStatus; + public int mControl; + public int mNumFailed; + public int mRetryAfter; + public int mRedirectCount; + + boolean mInitialized; + + public int mFuzz; + + public DownloadInfo(int index, String fileName, String pkg) { + mFuzz = Helpers.sRandom.nextInt(1001); + mFileName = fileName; + mIndex = index; + } + + public void resetDownload() { + mCurrentBytes = 0; + mETag = ""; + mLastMod = 0; + mStatus = 0; + mControl = 0; + mNumFailed = 0; + mRetryAfter = 0; + mRedirectCount = 0; + } + + /** + * Returns the time when a download should be restarted. + */ + public long restartTime(long now) { + if (mNumFailed == 0) { + return now; + } + if (mRetryAfter > 0) { + return mLastMod + mRetryAfter; + } + return mLastMod + + Constants.RETRY_FIRST_DELAY * + (1000 + mFuzz) * (1 << (mNumFailed - 1)); + } + + public void logVerboseInfo() { + Log.v(Constants.TAG, "Service adding new entry"); + Log.v(Constants.TAG, "FILENAME: " + mFileName); + Log.v(Constants.TAG, "URI : " + mUri); + Log.v(Constants.TAG, "FILENAME: " + mFileName); + Log.v(Constants.TAG, "CONTROL : " + mControl); + Log.v(Constants.TAG, "STATUS : " + mStatus); + Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); + Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); + Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); + Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); + Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); + Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); + Log.v(Constants.TAG, "ETAG : " + mETag); + } +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java new file mode 100644 index 0000000000..d82b658bc3 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import com.godot.game.R; +import com.google.android.vending.expansion.downloader.DownloadProgressInfo; +import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; +import com.google.android.vending.expansion.downloader.Helpers; +import com.google.android.vending.expansion.downloader.IDownloaderClient; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.os.Messenger; + +/** + * This class handles displaying the notification associated with the download + * queue going on in the download manager. It handles multiple status types; + * Some require user interaction and some do not. Some of the user interactions + * may be transient. (for example: the user is queried to continue the download + * on 3G when it started on WiFi, but then the phone locks onto WiFi again so + * the prompt automatically goes away) + *

+ * The application interface for the downloader also needs to understand and + * handle these transient states. + */ +public class DownloadNotification implements IDownloaderClient { + + private int mState; + private final Context mContext; + private final NotificationManager mNotificationManager; + private String mCurrentTitle; + + private IDownloaderClient mClientProxy; + final ICustomNotification mCustomNotification; + private Notification mNotification; + private Notification mCurrentNotification; + private CharSequence mLabel; + private String mCurrentText; + private PendingIntent mContentIntent; + private DownloadProgressInfo mProgressInfo; + + static final String LOGTAG = "DownloadNotification"; + static final int NOTIFICATION_ID = LOGTAG.hashCode(); + + public PendingIntent getClientIntent() { + return mContentIntent; + } + + public void setClientIntent(PendingIntent mClientIntent) { + this.mContentIntent = mClientIntent; + } + + public void resendState() { + if (null != mClientProxy) { + mClientProxy.onDownloadStateChanged(mState); + } + } + + @Override + public void onDownloadStateChanged(int newState) { + if (null != mClientProxy) { + mClientProxy.onDownloadStateChanged(newState); + } + if (newState != mState) { + mState = newState; + if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) { + return; + } + int stringDownloadID; + int iconResource; + boolean ongoingEvent; + + // get the new title string and paused text + switch (newState) { + case 0: + iconResource = android.R.drawable.stat_sys_warning; + stringDownloadID = R.string.state_unknown; + ongoingEvent = false; + break; + + case IDownloaderClient.STATE_DOWNLOADING: + iconResource = android.R.drawable.stat_sys_download; + stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); + ongoingEvent = true; + break; + + case IDownloaderClient.STATE_FETCHING_URL: + case IDownloaderClient.STATE_CONNECTING: + iconResource = android.R.drawable.stat_sys_download_done; + stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); + ongoingEvent = true; + break; + + case IDownloaderClient.STATE_COMPLETED: + case IDownloaderClient.STATE_PAUSED_BY_REQUEST: + iconResource = android.R.drawable.stat_sys_download_done; + stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); + ongoingEvent = false; + break; + + case IDownloaderClient.STATE_FAILED: + case IDownloaderClient.STATE_FAILED_CANCELED: + case IDownloaderClient.STATE_FAILED_FETCHING_URL: + case IDownloaderClient.STATE_FAILED_SDCARD_FULL: + case IDownloaderClient.STATE_FAILED_UNLICENSED: + iconResource = android.R.drawable.stat_sys_warning; + stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); + ongoingEvent = false; + break; + + default: + iconResource = android.R.drawable.stat_sys_warning; + stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); + ongoingEvent = true; + break; + } + mCurrentText = mContext.getString(stringDownloadID); + mCurrentTitle = mLabel.toString(); + mCurrentNotification.tickerText = mLabel + ": " + mCurrentText; + mCurrentNotification.icon = iconResource; + mCurrentNotification.setLatestEventInfo(mContext, mCurrentTitle, mCurrentText, + mContentIntent); + if (ongoingEvent) { + mCurrentNotification.flags |= Notification.FLAG_ONGOING_EVENT; + } else { + mCurrentNotification.flags &= ~Notification.FLAG_ONGOING_EVENT; + mCurrentNotification.flags |= Notification.FLAG_AUTO_CANCEL; + } + mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification); + } + } + + @Override + public void onDownloadProgress(DownloadProgressInfo progress) { + mProgressInfo = progress; + if (null != mClientProxy) { + mClientProxy.onDownloadProgress(progress); + } + if (progress.mOverallTotal <= 0) { + // we just show the text + mNotification.tickerText = mCurrentTitle; + mNotification.icon = android.R.drawable.stat_sys_download; + mNotification.setLatestEventInfo(mContext, mLabel, mCurrentText, mContentIntent); + mCurrentNotification = mNotification; + } else { + mCustomNotification.setCurrentBytes(progress.mOverallProgress); + mCustomNotification.setTotalBytes(progress.mOverallTotal); + mCustomNotification.setIcon(android.R.drawable.stat_sys_download); + mCustomNotification.setPendingIntent(mContentIntent); + mCustomNotification.setTicker(mLabel + ": " + mCurrentText); + mCustomNotification.setTitle(mLabel); + mCustomNotification.setTimeRemaining(progress.mTimeRemaining); + mCurrentNotification = mCustomNotification.updateNotification(mContext); + } + mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification); + } + + public interface ICustomNotification { + void setTitle(CharSequence title); + + void setTicker(CharSequence ticker); + + void setPendingIntent(PendingIntent mContentIntent); + + void setTotalBytes(long totalBytes); + + void setCurrentBytes(long currentBytes); + + void setIcon(int iconResource); + + void setTimeRemaining(long timeRemaining); + + Notification updateNotification(Context c); + } + + /** + * Called in response to onClientUpdated. Creates a new proxy and notifies + * it of the current state. + * + * @param msg the client Messenger to notify + */ + public void setMessenger(Messenger msg) { + mClientProxy = DownloaderClientMarshaller.CreateProxy(msg); + if (null != mProgressInfo) { + mClientProxy.onDownloadProgress(mProgressInfo); + } + if (mState != -1) { + mClientProxy.onDownloadStateChanged(mState); + } + } + + /** + * Constructor + * + * @param ctx The context to use to obtain access to the Notification + * Service + */ + DownloadNotification(Context ctx, CharSequence applicationLabel) { + mState = -1; + mContext = ctx; + mLabel = applicationLabel; + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mCustomNotification = CustomNotificationFactory + .createCustomNotification(); + mNotification = new Notification(); + mCurrentNotification = mNotification; + + } + + @Override + public void onServiceConnected(Messenger m) { + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java new file mode 100644 index 0000000000..056d1eca0b --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java @@ -0,0 +1,963 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import com.google.android.vending.expansion.downloader.Constants; +import com.google.android.vending.expansion.downloader.Helpers; +import com.google.android.vending.expansion.downloader.IDownloaderClient; + +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.params.ConnRouteParams; + +import android.content.Context; +import android.net.Proxy; +import android.os.PowerManager; +import android.os.Process; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.SyncFailedException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; + +/** + * Runs an actual download + */ +public class DownloadThread { + + private Context mContext; + private DownloadInfo mInfo; + private DownloaderService mService; + private final DownloadsDB mDB; + private final DownloadNotification mNotification; + private String mUserAgent; + + public DownloadThread(DownloadInfo info, DownloaderService service, + DownloadNotification notification) { + mContext = service; + mInfo = info; + mService = service; + mNotification = notification; + mDB = DownloadsDB.getDB(service); + mUserAgent = "APKXDL (Linux; U; Android " + android.os.Build.VERSION.RELEASE + ";" + + Locale.getDefault().toString() + "; " + android.os.Build.DEVICE + "/" + + android.os.Build.ID + ")" + + service.getPackageName(); + } + + /** + * Returns the default user agent + */ + private String userAgent() { + return mUserAgent; + } + + /** + * State for the entire run() method. + */ + private static class State { + public String mFilename; + public FileOutputStream mStream; + public boolean mCountRetry = false; + public int mRetryAfter = 0; + public int mRedirectCount = 0; + public String mNewUri; + public boolean mGotData = false; + public String mRequestUri; + + public State(DownloadInfo info, DownloaderService service) { + mRedirectCount = info.mRedirectCount; + mRequestUri = info.mUri; + mFilename = service.generateTempSaveFileName(info.mFileName); + } + } + + /** + * State within executeDownload() + */ + private static class InnerState { + public int mBytesSoFar = 0; + public int mBytesThisSession = 0; + public String mHeaderETag; + public boolean mContinuingDownload = false; + public String mHeaderContentLength; + public String mHeaderContentDisposition; + public String mHeaderContentLocation; + public int mBytesNotified = 0; + public long mTimeLastNotification = 0; + } + + /** + * Raised from methods called by run() to indicate that the current request + * should be stopped immediately. Note the message passed to this exception + * will be logged and therefore must be guaranteed not to contain any PII, + * meaning it generally can't include any information about the request URI, + * headers, or destination filename. + */ + private class StopRequest extends Throwable { + /** + * + */ + private static final long serialVersionUID = 6338592678988347973L; + public int mFinalStatus; + + public StopRequest(int finalStatus, String message) { + super(message); + mFinalStatus = finalStatus; + } + + public StopRequest(int finalStatus, String message, Throwable throwable) { + super(message, throwable); + mFinalStatus = finalStatus; + } + } + + /** + * Raised from methods called by executeDownload() to indicate that the + * download should be retried immediately. + */ + private class RetryDownload extends Throwable { + + /** + * + */ + private static final long serialVersionUID = 6196036036517540229L; + } + + /** + * Returns the preferred proxy to be used by clients. This is a wrapper + * around {@link android.net.Proxy#getHost()}. Currently no proxy will be + * returned for localhost or if the active network is Wi-Fi. + * + * @param context the context which will be passed to + * {@link android.net.Proxy#getHost()} + * @param url the target URL for the request + * @note Calling this method requires permission + * android.permission.ACCESS_NETWORK_STATE + * @return The preferred proxy to be used by clients, or null if there is no + * proxy. + */ + public HttpHost getPreferredHttpHost(Context context, + String url) { + if (!isLocalHost(url) && !mService.isWiFi()) { + final String proxyHost = Proxy.getHost(context); + if (proxyHost != null) { + return new HttpHost(proxyHost, Proxy.getPort(context), "http"); + } + } + + return null; + } + + static final private boolean isLocalHost(String url) { + if (url == null) { + return false; + } + + try { + final URI uri = URI.create(url); + final String host = uri.getHost(); + if (host != null) { + // TODO: InetAddress.isLoopbackAddress should be used to check + // for localhost. However no public factory methods exist which + // can be used without triggering DNS lookup if host is not + // localhost. + if (host.equalsIgnoreCase("localhost") || + host.equals("127.0.0.1") || + host.equals("[::1]")) { + return true; + } + } + } catch (IllegalArgumentException iex) { + // Ignore (URI.create) + } + + return false; + } + + /** + * Executes the download in a separate thread + */ + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + + State state = new State(mInfo, mService); + AndroidHttpClient client = null; + PowerManager.WakeLock wakeLock = null; + int finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; + + try { + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); + wakeLock.acquire(); + + if (Constants.LOGV) { + Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); + Log.v(Constants.TAG, " at " + mInfo.mUri); + } + + client = AndroidHttpClient.newInstance(userAgent(), mContext); + + boolean finished = false; + while (!finished) { + if (Constants.LOGV) { + Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); + Log.v(Constants.TAG, " at " + mInfo.mUri); + } + // Set or unset proxy, which may have changed since last GET + // request. + // setDefaultProxy() supports null as proxy parameter. + ConnRouteParams.setDefaultProxy(client.getParams(), + getPreferredHttpHost(mContext, state.mRequestUri)); + HttpGet request = new HttpGet(state.mRequestUri); + try { + executeDownload(state, client, request); + finished = true; + } catch (RetryDownload exc) { + // fall through + } finally { + request.abort(); + request = null; + } + } + + if (Constants.LOGV) { + Log.v(Constants.TAG, "download completed for " + mInfo.mFileName); + Log.v(Constants.TAG, " at " + mInfo.mUri); + } + finalizeDestinationFile(state); + finalStatus = DownloaderService.STATUS_SUCCESS; + } catch (StopRequest error) { + // remove the cause before printing, in case it contains PII + Log.w(Constants.TAG, + "Aborting request for download " + mInfo.mFileName + ": " + error.getMessage()); + error.printStackTrace(); + finalStatus = error.mFinalStatus; + // fall through to finally block + } catch (Throwable ex) { // sometimes the socket code throws unchecked + // exceptions + Log.w(Constants.TAG, "Exception for " + mInfo.mFileName + ": " + ex); + finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; + // falls through to the code that reports an error + } finally { + if (wakeLock != null) { + wakeLock.release(); + wakeLock = null; + } + if (client != null) { + client.close(); + client = null; + } + cleanupDestination(state, finalStatus); + notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter, + state.mRedirectCount, state.mGotData, state.mFilename); + } + } + + /** + * Fully execute a single download request - setup and send the request, + * handle the response, and transfer the data to the destination file. + */ + private void executeDownload(State state, AndroidHttpClient client, HttpGet request) + throws StopRequest, RetryDownload { + InnerState innerState = new InnerState(); + byte data[] = new byte[Constants.BUFFER_SIZE]; + + checkPausedOrCanceled(state); + + setupDestinationFile(state, innerState); + addRequestHeaders(innerState, request); + + // check just before sending the request to avoid using an invalid + // connection at all + checkConnectivity(state); + + mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING); + HttpResponse response = sendRequest(state, client, request); + handleExceptionalStatus(state, innerState, response); + + if (Constants.LOGV) { + Log.v(Constants.TAG, "received response for " + mInfo.mUri); + } + + processResponseHeaders(state, innerState, response); + InputStream entityStream = openResponseEntity(state, response); + mNotification.onDownloadStateChanged(IDownloaderClient.STATE_DOWNLOADING); + transferData(state, innerState, data, entityStream); + } + + /** + * Check if current connectivity is valid for this request. + */ + private void checkConnectivity(State state) throws StopRequest { + switch (mService.getNetworkAvailabilityState(mDB)) { + case DownloaderService.NETWORK_OK: + return; + case DownloaderService.NETWORK_NO_CONNECTION: + throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, + "waiting for network to return"); + case DownloaderService.NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: + throw new StopRequest( + DownloaderService.STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION, + "waiting for wifi or for download over cellular to be authorized"); + case DownloaderService.NETWORK_CANNOT_USE_ROAMING: + throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, + "roaming is not allowed"); + case DownloaderService.NETWORK_UNUSABLE_DUE_TO_SIZE: + throw new StopRequest(DownloaderService.STATUS_QUEUED_FOR_WIFI, "waiting for wifi"); + } + } + + /** + * Transfer as much data as possible from the HTTP response to the + * destination file. + * + * @param data buffer to use to read data + * @param entityStream stream for reading the HTTP response entity + */ + private void transferData(State state, InnerState innerState, byte[] data, + InputStream entityStream) throws StopRequest { + for (;;) { + int bytesRead = readFromResponse(state, innerState, data, entityStream); + if (bytesRead == -1) { // success, end of stream already reached + handleEndOfStream(state, innerState); + return; + } + + state.mGotData = true; + writeDataToDestination(state, data, bytesRead); + innerState.mBytesSoFar += bytesRead; + innerState.mBytesThisSession += bytesRead; + reportProgress(state, innerState); + + checkPausedOrCanceled(state); + } + } + + /** + * Called after a successful completion to take any necessary action on the + * downloaded file. + */ + private void finalizeDestinationFile(State state) throws StopRequest { + syncDestination(state); + String tempFilename = state.mFilename; + String finalFilename = Helpers.generateSaveFileName(mService, mInfo.mFileName); + if (!state.mFilename.equals(finalFilename)) { + File startFile = new File(tempFilename); + File destFile = new File(finalFilename); + if (mInfo.mTotalBytes != -1 && mInfo.mCurrentBytes == mInfo.mTotalBytes) { + if (!startFile.renameTo(destFile)) { + throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, + "unable to finalize destination file"); + } + } else { + throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, + "file delivered with incorrect size. probably due to network not browser configured"); + } + } + } + + /** + * Called just before the thread finishes, regardless of status, to take any + * necessary action on the downloaded file. + */ + private void cleanupDestination(State state, int finalStatus) { + closeDestination(state); + if (state.mFilename != null && DownloaderService.isStatusError(finalStatus)) { + new File(state.mFilename).delete(); + state.mFilename = null; + } + } + + /** + * Sync the destination file to storage. + */ + private void syncDestination(State state) { + FileOutputStream downloadedFileStream = null; + try { + downloadedFileStream = new FileOutputStream(state.mFilename, true); + downloadedFileStream.getFD().sync(); + } catch (FileNotFoundException ex) { + Log.w(Constants.TAG, "file " + state.mFilename + " not found: " + ex); + } catch (SyncFailedException ex) { + Log.w(Constants.TAG, "file " + state.mFilename + " sync failed: " + ex); + } catch (IOException ex) { + Log.w(Constants.TAG, "IOException trying to sync " + state.mFilename + ": " + ex); + } catch (RuntimeException ex) { + Log.w(Constants.TAG, "exception while syncing file: ", ex); + } finally { + if (downloadedFileStream != null) { + try { + downloadedFileStream.close(); + } catch (IOException ex) { + Log.w(Constants.TAG, "IOException while closing synced file: ", ex); + } catch (RuntimeException ex) { + Log.w(Constants.TAG, "exception while closing file: ", ex); + } + } + } + } + + /** + * Close the destination output stream. + */ + private void closeDestination(State state) { + try { + // close the file + if (state.mStream != null) { + state.mStream.close(); + state.mStream = null; + } + } catch (IOException ex) { + if (Constants.LOGV) { + Log.v(Constants.TAG, "exception when closing the file after download : " + ex); + } + // nothing can really be done if the file can't be closed + } + } + + /** + * Check if the download has been paused or canceled, stopping the request + * appropriately if it has been. + */ + private void checkPausedOrCanceled(State state) throws StopRequest { + if (mService.getControl() == DownloaderService.CONTROL_PAUSED) { + int status = mService.getStatus(); + switch (status) { + case DownloaderService.STATUS_PAUSED_BY_APP: + throw new StopRequest(mService.getStatus(), + "download paused"); + } + } + } + + /** + * Report download progress through the database if necessary. + */ + private void reportProgress(State state, InnerState innerState) { + long now = System.currentTimeMillis(); + if (innerState.mBytesSoFar - innerState.mBytesNotified + > Constants.MIN_PROGRESS_STEP + && now - innerState.mTimeLastNotification + > Constants.MIN_PROGRESS_TIME) { + // we store progress updates to the database here + mInfo.mCurrentBytes = innerState.mBytesSoFar; + mDB.updateDownloadCurrentBytes(mInfo); + + innerState.mBytesNotified = innerState.mBytesSoFar; + innerState.mTimeLastNotification = now; + + long totalBytesSoFar = innerState.mBytesThisSession + mService.mBytesSoFar; + + if (Constants.LOGVV) { + Log.v(Constants.TAG, "downloaded " + mInfo.mCurrentBytes + " out of " + + mInfo.mTotalBytes); + Log.v(Constants.TAG, " total " + totalBytesSoFar + " out of " + + mService.mTotalLength); + } + + mService.notifyUpdateBytes(totalBytesSoFar); + } + } + + /** + * Write a data buffer to the destination file. + * + * @param data buffer containing the data to write + * @param bytesRead how many bytes to write from the buffer + */ + private void writeDataToDestination(State state, byte[] data, int bytesRead) + throws StopRequest { + for (;;) { + try { + if (state.mStream == null) { + state.mStream = new FileOutputStream(state.mFilename, true); + } + state.mStream.write(data, 0, bytesRead); + // we close after every write --- this may be too inefficient + closeDestination(state); + return; + } catch (IOException ex) { + if (!Helpers.isExternalMediaMounted()) { + throw new StopRequest(DownloaderService.STATUS_DEVICE_NOT_FOUND_ERROR, + "external media not mounted while writing destination file"); + } + + long availableBytes = + Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename)); + if (availableBytes < bytesRead) { + throw new StopRequest(DownloaderService.STATUS_INSUFFICIENT_SPACE_ERROR, + "insufficient space while writing destination file", ex); + } + throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, + "while writing destination file: " + ex.toString(), ex); + } + } + } + + /** + * Called when we've reached the end of the HTTP response stream, to update + * the database and check for consistency. + */ + private void handleEndOfStream(State state, InnerState innerState) throws StopRequest { + mInfo.mCurrentBytes = innerState.mBytesSoFar; + // this should always be set from the market + // if ( innerState.mHeaderContentLength == null ) { + // mInfo.mTotalBytes = innerState.mBytesSoFar; + // } + mDB.updateDownload(mInfo); + + boolean lengthMismatched = (innerState.mHeaderContentLength != null) + && (innerState.mBytesSoFar != Integer.parseInt(innerState.mHeaderContentLength)); + if (lengthMismatched) { + if (cannotResume(innerState)) { + throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, + "mismatched content length"); + } else { + throw new StopRequest(getFinalStatusForHttpError(state), + "closed socket before end of file"); + } + } + } + + private boolean cannotResume(InnerState innerState) { + return innerState.mBytesSoFar > 0 && innerState.mHeaderETag == null; + } + + /** + * Read some data from the HTTP response stream, handling I/O errors. + * + * @param data buffer to use to read data + * @param entityStream stream for reading the HTTP response entity + * @return the number of bytes actually read or -1 if the end of the stream + * has been reached + */ + private int readFromResponse(State state, InnerState innerState, byte[] data, + InputStream entityStream) throws StopRequest { + try { + return entityStream.read(data); + } catch (IOException ex) { + logNetworkState(); + mInfo.mCurrentBytes = innerState.mBytesSoFar; + mDB.updateDownload(mInfo); + if (cannotResume(innerState)) { + String message = "while reading response: " + ex.toString() + + ", can't resume interrupted download with no ETag"; + throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, + message, ex); + } else { + throw new StopRequest(getFinalStatusForHttpError(state), + "while reading response: " + ex.toString(), ex); + } + } + } + + /** + * Open a stream for the HTTP response entity, handling I/O errors. + * + * @return an InputStream to read the response entity + */ + private InputStream openResponseEntity(State state, HttpResponse response) + throws StopRequest { + try { + return response.getEntity().getContent(); + } catch (IOException ex) { + logNetworkState(); + throw new StopRequest(getFinalStatusForHttpError(state), + "while getting entity: " + ex.toString(), ex); + } + } + + private void logNetworkState() { + if (Constants.LOGX) { + Log.i(Constants.TAG, + "Net " + + (mService.getNetworkAvailabilityState(mDB) == DownloaderService.NETWORK_OK ? "Up" + : "Down")); + } + } + + /** + * Read HTTP response headers and take appropriate action, including setting + * up the destination file and updating the database. + */ + private void processResponseHeaders(State state, InnerState innerState, HttpResponse response) + throws StopRequest { + if (innerState.mContinuingDownload) { + // ignore response headers on resume requests + return; + } + + readResponseHeaders(state, innerState, response); + + try { + state.mFilename = mService.generateSaveFile(mInfo.mFileName, mInfo.mTotalBytes); + } catch (DownloaderService.GenerateSaveFileError exc) { + throw new StopRequest(exc.mStatus, exc.mMessage); + } + try { + state.mStream = new FileOutputStream(state.mFilename); + } catch (FileNotFoundException exc) { + // make sure the directory exists + File pathFile = new File(Helpers.getSaveFilePath(mService)); + try { + if (pathFile.mkdirs()) { + state.mStream = new FileOutputStream(state.mFilename); + } + } catch (Exception ex) { + throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, + "while opening destination file: " + exc.toString(), exc); + } + } + if (Constants.LOGV) { + Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + state.mFilename); + } + + updateDatabaseFromHeaders(state, innerState); + // check connectivity again now that we know the total size + checkConnectivity(state); + } + + /** + * Update necessary database fields based on values of HTTP response headers + * that have been read. + */ + private void updateDatabaseFromHeaders(State state, InnerState innerState) { + mInfo.mETag = innerState.mHeaderETag; + mDB.updateDownload(mInfo); + } + + /** + * Read headers from the HTTP response and store them into local state. + */ + private void readResponseHeaders(State state, InnerState innerState, HttpResponse response) + throws StopRequest { + Header header = response.getFirstHeader("Content-Disposition"); + if (header != null) { + innerState.mHeaderContentDisposition = header.getValue(); + } + header = response.getFirstHeader("Content-Location"); + if (header != null) { + innerState.mHeaderContentLocation = header.getValue(); + } + header = response.getFirstHeader("ETag"); + if (header != null) { + innerState.mHeaderETag = header.getValue(); + } + String headerTransferEncoding = null; + header = response.getFirstHeader("Transfer-Encoding"); + if (header != null) { + headerTransferEncoding = header.getValue(); + } + String headerContentType = null; + header = response.getFirstHeader("Content-Type"); + if (header != null) { + headerContentType = header.getValue(); + if (!headerContentType.equals("application/vnd.android.obb")) { + throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, + "file delivered with incorrect Mime type"); + } + } + + if (headerTransferEncoding == null) { + header = response.getFirstHeader("Content-Length"); + if (header != null) { + innerState.mHeaderContentLength = header.getValue(); + // this is always set from Market + long contentLength = Long.parseLong(innerState.mHeaderContentLength); + if (contentLength != -1 && contentLength != mInfo.mTotalBytes) { + // we're most likely on a bad wifi connection -- we should + // probably + // also look at the mime type --- but the size mismatch is + // enough + // to tell us that something is wrong here + Log.e(Constants.TAG, "Incorrect file size delivered."); + } + } + } else { + // Ignore content-length with transfer-encoding - 2616 4.4 3 + if (Constants.LOGVV) { + Log.v(Constants.TAG, + "ignoring content-length because of xfer-encoding"); + } + } + if (Constants.LOGVV) { + Log.v(Constants.TAG, "Content-Disposition: " + + innerState.mHeaderContentDisposition); + Log.v(Constants.TAG, "Content-Length: " + innerState.mHeaderContentLength); + Log.v(Constants.TAG, "Content-Location: " + innerState.mHeaderContentLocation); + Log.v(Constants.TAG, "ETag: " + innerState.mHeaderETag); + Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding); + } + + boolean noSizeInfo = innerState.mHeaderContentLength == null + && (headerTransferEncoding == null + || !headerTransferEncoding.equalsIgnoreCase("chunked")); + if (noSizeInfo) { + throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, + "can't know size of download, giving up"); + } + } + + /** + * Check the HTTP response status and handle anything unusual (e.g. not + * 200/206). + */ + private void handleExceptionalStatus(State state, InnerState innerState, HttpResponse response) + throws StopRequest, RetryDownload { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) { + handleServiceUnavailable(state, response); + } + if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307) { + handleRedirect(state, response, statusCode); + } + + int expectedStatus = innerState.mContinuingDownload ? 206 + : DownloaderService.STATUS_SUCCESS; + if (statusCode != expectedStatus) { + handleOtherStatus(state, innerState, statusCode); + } else { + // no longer redirected + state.mRedirectCount = 0; + } + } + + /** + * Handle a status that we don't know how to deal with properly. + */ + private void handleOtherStatus(State state, InnerState innerState, int statusCode) + throws StopRequest { + int finalStatus; + if (DownloaderService.isStatusError(statusCode)) { + finalStatus = statusCode; + } else if (statusCode >= 300 && statusCode < 400) { + finalStatus = DownloaderService.STATUS_UNHANDLED_REDIRECT; + } else if (innerState.mContinuingDownload && statusCode == DownloaderService.STATUS_SUCCESS) { + finalStatus = DownloaderService.STATUS_CANNOT_RESUME; + } else { + finalStatus = DownloaderService.STATUS_UNHANDLED_HTTP_CODE; + } + throw new StopRequest(finalStatus, "http error " + statusCode); + } + + /** + * Handle a 3xx redirect status. + */ + private void handleRedirect(State state, HttpResponse response, int statusCode) + throws StopRequest, RetryDownload { + if (Constants.LOGVV) { + Log.v(Constants.TAG, "got HTTP redirect " + statusCode); + } + if (state.mRedirectCount >= Constants.MAX_REDIRECTS) { + throw new StopRequest(DownloaderService.STATUS_TOO_MANY_REDIRECTS, "too many redirects"); + } + Header header = response.getFirstHeader("Location"); + if (header == null) { + return; + } + if (Constants.LOGVV) { + Log.v(Constants.TAG, "Location :" + header.getValue()); + } + + String newUri; + try { + newUri = new URI(mInfo.mUri).resolve(new URI(header.getValue())).toString(); + } catch (URISyntaxException ex) { + if (Constants.LOGV) { + Log.d(Constants.TAG, "Couldn't resolve redirect URI " + header.getValue() + + " for " + mInfo.mUri); + } + throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, + "Couldn't resolve redirect URI"); + } + ++state.mRedirectCount; + state.mRequestUri = newUri; + if (statusCode == 301 || statusCode == 303) { + // use the new URI for all future requests (should a retry/resume be + // necessary) + state.mNewUri = newUri; + } + throw new RetryDownload(); + } + + /** + * Add headers for this download to the HTTP request to allow for resume. + */ + private void addRequestHeaders(InnerState innerState, HttpGet request) { + if (innerState.mContinuingDownload) { + if (innerState.mHeaderETag != null) { + request.addHeader("If-Match", innerState.mHeaderETag); + } + request.addHeader("Range", "bytes=" + innerState.mBytesSoFar + "-"); + } + } + + /** + * Handle a 503 Service Unavailable status by processing the Retry-After + * header. + */ + private void handleServiceUnavailable(State state, HttpResponse response) throws StopRequest { + if (Constants.LOGVV) { + Log.v(Constants.TAG, "got HTTP response code 503"); + } + state.mCountRetry = true; + Header header = response.getFirstHeader("Retry-After"); + if (header != null) { + try { + if (Constants.LOGVV) { + Log.v(Constants.TAG, "Retry-After :" + header.getValue()); + } + state.mRetryAfter = Integer.parseInt(header.getValue()); + if (state.mRetryAfter < 0) { + state.mRetryAfter = 0; + } else { + if (state.mRetryAfter < Constants.MIN_RETRY_AFTER) { + state.mRetryAfter = Constants.MIN_RETRY_AFTER; + } else if (state.mRetryAfter > Constants.MAX_RETRY_AFTER) { + state.mRetryAfter = Constants.MAX_RETRY_AFTER; + } + state.mRetryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1); + state.mRetryAfter *= 1000; + } + } catch (NumberFormatException ex) { + // ignored - retryAfter stays 0 in this case. + } + } + throw new StopRequest(DownloaderService.STATUS_WAITING_TO_RETRY, + "got 503 Service Unavailable, will retry later"); + } + + /** + * Send the request to the server, handling any I/O exceptions. + */ + private HttpResponse sendRequest(State state, AndroidHttpClient client, HttpGet request) + throws StopRequest { + try { + return client.execute(request); + } catch (IllegalArgumentException ex) { + throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, + "while trying to execute request: " + ex.toString(), ex); + } catch (IOException ex) { + logNetworkState(); + throw new StopRequest(getFinalStatusForHttpError(state), + "while trying to execute request: " + ex.toString(), ex); + } + } + + private int getFinalStatusForHttpError(State state) { + if (mService.getNetworkAvailabilityState(mDB) != DownloaderService.NETWORK_OK) { + return DownloaderService.STATUS_WAITING_FOR_NETWORK; + } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) { + state.mCountRetry = true; + return DownloaderService.STATUS_WAITING_TO_RETRY; + } else { + Log.w(Constants.TAG, "reached max retries for " + mInfo.mNumFailed); + return DownloaderService.STATUS_HTTP_DATA_ERROR; + } + } + + /** + * Prepare the destination file to receive data. If the file already exists, + * we'll set up appropriately for resumption. + */ + private void setupDestinationFile(State state, InnerState innerState) + throws StopRequest { + if (state.mFilename != null) { // only true if we've already run a + // thread for this download + if (!Helpers.isFilenameValid(state.mFilename)) { + // this should never happen + throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, + "found invalid internal destination filename"); + } + // We're resuming a download that got interrupted + File f = new File(state.mFilename); + if (f.exists()) { + long fileLength = f.length(); + if (fileLength == 0) { + // The download hadn't actually started, we can restart from + // scratch + f.delete(); + state.mFilename = null; + } else if (mInfo.mETag == null) { + // This should've been caught upon failure + f.delete(); + throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, + "Trying to resume a download that can't be resumed"); + } else { + // All right, we'll be able to resume this download + try { + state.mStream = new FileOutputStream(state.mFilename, true); + } catch (FileNotFoundException exc) { + throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, + "while opening destination for resuming: " + exc.toString(), exc); + } + innerState.mBytesSoFar = (int) fileLength; + if (mInfo.mTotalBytes != -1) { + innerState.mHeaderContentLength = Long.toString(mInfo.mTotalBytes); + } + innerState.mHeaderETag = mInfo.mETag; + innerState.mContinuingDownload = true; + } + } + } + + if (state.mStream != null) { + closeDestination(state); + } + } + + /** + * Stores information about the completed download, and notifies the + * initiating application. + */ + private void notifyDownloadCompleted( + int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, + String filename) { + updateDownloadDatabase( + status, countRetry, retryAfter, redirectCount, gotData, filename); + if (DownloaderService.isStatusCompleted(status)) { + // TBD: send status update? + } + } + + private void updateDownloadDatabase( + int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, + String filename) { + mInfo.mStatus = status; + mInfo.mRetryAfter = retryAfter; + mInfo.mRedirectCount = redirectCount; + mInfo.mLastMod = System.currentTimeMillis(); + if (!countRetry) { + mInfo.mNumFailed = 0; + } else if (gotData) { + mInfo.mNumFailed = 1; + } else { + mInfo.mNumFailed++; + } + mDB.updateDownload(mInfo); + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java new file mode 100644 index 0000000000..627bf3eedd --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java @@ -0,0 +1,1341 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import com.google.android.vending.expansion.downloader.Constants; +import com.google.android.vending.expansion.downloader.DownloadProgressInfo; +import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; +import com.google.android.vending.expansion.downloader.Helpers; +import com.google.android.vending.expansion.downloader.IDownloaderClient; +import com.google.android.vending.expansion.downloader.IDownloaderService; +import com.google.android.vending.expansion.downloader.IStub; +import com.google.android.vending.licensing.AESObfuscator; +import com.google.android.vending.licensing.APKExpansionPolicy; +import com.google.android.vending.licensing.LicenseChecker; +import com.google.android.vending.licensing.LicenseCheckerCallback; +import com.google.android.vending.licensing.Policy; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.Messenger; +import android.os.SystemClock; +import android.provider.Settings.Secure; +import android.telephony.TelephonyManager; +import android.util.Log; + +import java.io.File; + +/** + * Performs the background downloads requested by applications that use the + * Downloads provider. This service does not run as a foreground task, so + * Android may kill it off at will, but it will try to restart itself if it can. + * Note that Android by default will kill off any process that has an open file + * handle on the shared (SD Card) partition if the partition is unmounted. + */ +public abstract class DownloaderService extends CustomIntentService implements IDownloaderService { + + public DownloaderService() { + super("LVLDownloadService"); + } + + private static final String LOG_TAG = "LVLDL"; + + // the following NETWORK_* constants are used to indicates specific reasons + // for disallowing a + // download from using a network, since specific causes can require special + // handling + + /** + * The network is usable for the given download. + */ + public static final int NETWORK_OK = 1; + + /** + * There is no network connectivity. + */ + public static final int NETWORK_NO_CONNECTION = 2; + + /** + * The download exceeds the maximum size for this network. + */ + public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3; + + /** + * The download exceeds the recommended maximum size for this network, the + * user must confirm for this download to proceed without WiFi. + */ + public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4; + + /** + * The current connection is roaming, and the download can't proceed over a + * roaming connection. + */ + public static final int NETWORK_CANNOT_USE_ROAMING = 5; + + /** + * The app requesting the download specific that it can't use the current + * network connection. + */ + public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6; + + /** + * For intents used to notify the user that a download exceeds a size + * threshold, if this extra is true, WiFi is required for this download + * size; otherwise, it is only recommended. + */ + public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired"; + public static final String EXTRA_FILE_NAME = "downloadId"; + + /** + * Used with DOWNLOAD_STATUS + */ + public static final String EXTRA_STATUS_STATE = "ESS"; + public static final String EXTRA_STATUS_TOTAL_SIZE = "ETS"; + public static final String EXTRA_STATUS_CURRENT_FILE_SIZE = "CFS"; + public static final String EXTRA_STATUS_TOTAL_PROGRESS = "TFP"; + public static final String EXTRA_STATUS_CURRENT_PROGRESS = "CFP"; + + public static final String ACTION_DOWNLOADS_CHANGED = "downloadsChanged"; + + /** + * Broadcast intent action sent by the download manager when a download + * completes. + */ + public final static String ACTION_DOWNLOAD_COMPLETE = "lvldownloader.intent.action.DOWNLOAD_COMPLETE"; + + /** + * Broadcast intent action sent by the download manager when download status + * changes. + */ + public final static String ACTION_DOWNLOAD_STATUS = "lvldownloader.intent.action.DOWNLOAD_STATUS"; + + /* + * Lists the states that the download manager can set on a download to + * notify applications of the download progress. The codes follow the HTTP + * families:
1xx: informational
2xx: success
3xx: redirects (not + * used by the download manager)
4xx: client errors
5xx: server + * errors + */ + + /** + * Returns whether the status is informational (i.e. 1xx). + */ + public static boolean isStatusInformational(int status) { + return (status >= 100 && status < 200); + } + + /** + * Returns whether the status is a success (i.e. 2xx). + */ + public static boolean isStatusSuccess(int status) { + return (status >= 200 && status < 300); + } + + /** + * Returns whether the status is an error (i.e. 4xx or 5xx). + */ + public static boolean isStatusError(int status) { + return (status >= 400 && status < 600); + } + + /** + * Returns whether the status is a client error (i.e. 4xx). + */ + public static boolean isStatusClientError(int status) { + return (status >= 400 && status < 500); + } + + /** + * Returns whether the status is a server error (i.e. 5xx). + */ + public static boolean isStatusServerError(int status) { + return (status >= 500 && status < 600); + } + + /** + * Returns whether the download has completed (either with success or + * error). + */ + public static boolean isStatusCompleted(int status) { + return (status >= 200 && status < 300) + || (status >= 400 && status < 600); + } + + /** + * This download hasn't stated yet + */ + public static final int STATUS_PENDING = 190; + + /** + * This download has started + */ + public static final int STATUS_RUNNING = 192; + + /** + * This download has been paused by the owning app. + */ + public static final int STATUS_PAUSED_BY_APP = 193; + + /** + * This download encountered some network error and is waiting before + * retrying the request. + */ + public static final int STATUS_WAITING_TO_RETRY = 194; + + /** + * This download is waiting for network connectivity to proceed. + */ + public static final int STATUS_WAITING_FOR_NETWORK = 195; + + /** + * This download is waiting for a Wi-Fi connection to proceed or for + * permission to download over cellular. + */ + public static final int STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION = 196; + + /** + * This download is waiting for a Wi-Fi connection to proceed. + */ + public static final int STATUS_QUEUED_FOR_WIFI = 197; + + /** + * This download has successfully completed. Warning: there might be other + * status values that indicate success in the future. Use isSucccess() to + * capture the entire category. + * + * @hide + */ + public static final int STATUS_SUCCESS = 200; + + /** + * The requested URL is no longer available + */ + public static final int STATUS_FORBIDDEN = 403; + + /** + * The file was delivered incorrectly + */ + public static final int STATUS_FILE_DELIVERED_INCORRECTLY = 487; + + /** + * The requested destination file already exists. + */ + public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; + + /** + * Some possibly transient error occurred, but we can't resume the download. + */ + public static final int STATUS_CANNOT_RESUME = 489; + + /** + * This download was canceled + * + * @hide + */ + public static final int STATUS_CANCELED = 490; + + /** + * This download has completed with an error. Warning: there will be other + * status values that indicate errors in the future. Use isStatusError() to + * capture the entire category. + */ + public static final int STATUS_UNKNOWN_ERROR = 491; + + /** + * This download couldn't be completed because of a storage issue. + * Typically, that's because the filesystem is missing or full. Use the more + * specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} and + * {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. + * + * @hide + */ + public static final int STATUS_FILE_ERROR = 492; + + /** + * This download couldn't be completed because of an HTTP redirect response + * that the download manager couldn't handle. + * + * @hide + */ + public static final int STATUS_UNHANDLED_REDIRECT = 493; + + /** + * This download couldn't be completed because of an unspecified unhandled + * HTTP code. + * + * @hide + */ + public static final int STATUS_UNHANDLED_HTTP_CODE = 494; + + /** + * This download couldn't be completed because of an error receiving or + * processing data at the HTTP level. + * + * @hide + */ + public static final int STATUS_HTTP_DATA_ERROR = 495; + + /** + * This download couldn't be completed because of an HttpException while + * setting up the request. + * + * @hide + */ + public static final int STATUS_HTTP_EXCEPTION = 496; + + /** + * This download couldn't be completed because there were too many + * redirects. + * + * @hide + */ + public static final int STATUS_TOO_MANY_REDIRECTS = 497; + + /** + * This download couldn't be completed due to insufficient storage space. + * Typically, this is because the SD card is full. + * + * @hide + */ + public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; + + /** + * This download couldn't be completed because no external storage device + * was found. Typically, this is because the SD card is not mounted. + * + * @hide + */ + public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; + + /** + * This download is allowed to run. + * + * @hide + */ + public static final int CONTROL_RUN = 0; + + /** + * This download must pause at the first opportunity. + * + * @hide + */ + public static final int CONTROL_PAUSED = 1; + + /** + * This download is visible but only shows in the notifications while it's + * in progress. + * + * @hide + */ + public static final int VISIBILITY_VISIBLE = 0; + + /** + * This download is visible and shows in the notifications while in progress + * and after completion. + * + * @hide + */ + public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; + + /** + * This download doesn't show in the UI or in the notifications. + * + * @hide + */ + public static final int VISIBILITY_HIDDEN = 2; + + /** + * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + * {@link ConnectivityManager#TYPE_MOBILE}. + */ + public static final int NETWORK_MOBILE = 1 << 0; + + /** + * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + * {@link ConnectivityManager#TYPE_WIFI}. + */ + public static final int NETWORK_WIFI = 1 << 1; + + private final static String TEMP_EXT = ".tmp"; + + /** + * Service thread status + */ + private static boolean sIsRunning; + + @Override + public IBinder onBind(Intent paramIntent) { + Log.d(Constants.TAG, "Service Bound"); + return this.mServiceMessenger.getBinder(); + } + + /** + * Network state. + */ + private boolean mIsConnected; + private boolean mIsFailover; + private boolean mIsCellularConnection; + private boolean mIsRoaming; + private boolean mIsAtLeast3G; + private boolean mIsAtLeast4G; + private boolean mStateChanged; + + /** + * Download state + */ + private int mControl; + private int mStatus; + + public boolean isWiFi() { + return mIsConnected && !mIsCellularConnection; + } + + /** + * Bindings to important services + */ + private ConnectivityManager mConnectivityManager; + private WifiManager mWifiManager; + + /** + * Package we are downloading for (defaults to package of application) + */ + private PackageInfo mPackageInfo; + + /** + * Byte counts + */ + long mBytesSoFar; + long mTotalLength; + int mFileCount; + + /** + * Used for calculating time remaining and speed + */ + long mBytesAtSample; + long mMillisecondsAtSample; + float mAverageDownloadSpeed; + + /** + * Our binding to the network state broadcasts + */ + private BroadcastReceiver mConnReceiver; + final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this); + final private Messenger mServiceMessenger = mServiceStub.getMessenger(); + private Messenger mClientMessenger; + private DownloadNotification mNotification; + private PendingIntent mPendingIntent; + private PendingIntent mAlarmIntent; + + /** + * Updates the network type based upon the type and subtype returned from + * the connectivity manager. Subtype is only used for cellular signals. + * + * @param type + * @param subType + */ + private void updateNetworkType(int type, int subType) { + switch (type) { + case ConnectivityManager.TYPE_WIFI: + case ConnectivityManager.TYPE_ETHERNET: + case ConnectivityManager.TYPE_BLUETOOTH: + mIsCellularConnection = false; + mIsAtLeast3G = false; + mIsAtLeast4G = false; + break; + case ConnectivityManager.TYPE_WIMAX: + mIsCellularConnection = true; + mIsAtLeast3G = true; + mIsAtLeast4G = true; + break; + case ConnectivityManager.TYPE_MOBILE: + mIsCellularConnection = true; + switch (subType) { + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_IDEN: + mIsAtLeast3G = false; + mIsAtLeast4G = false; + break; + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_UMTS: + mIsAtLeast3G = true; + mIsAtLeast4G = false; + break; + case TelephonyManager.NETWORK_TYPE_LTE: // 4G + case TelephonyManager.NETWORK_TYPE_EHRPD: // 3G ++ interop + // with 4G + case TelephonyManager.NETWORK_TYPE_HSPAP: // 3G ++ but + // marketed as + // 4G + mIsAtLeast3G = true; + mIsAtLeast4G = true; + break; + default: + mIsCellularConnection = false; + mIsAtLeast3G = false; + mIsAtLeast4G = false; + } + } + } + + private void updateNetworkState(NetworkInfo info) { + boolean isConnected = mIsConnected; + boolean isFailover = mIsFailover; + boolean isCellularConnection = mIsCellularConnection; + boolean isRoaming = mIsRoaming; + boolean isAtLeast3G = mIsAtLeast3G; + if (null != info) { + mIsRoaming = info.isRoaming(); + mIsFailover = info.isFailover(); + mIsConnected = info.isConnected(); + updateNetworkType(info.getType(), info.getSubtype()); + } else { + mIsRoaming = false; + mIsFailover = false; + mIsConnected = false; + updateNetworkType(-1, -1); + } + mStateChanged = (mStateChanged || isConnected != mIsConnected + || isFailover != mIsFailover + || isCellularConnection != mIsCellularConnection + || isRoaming != mIsRoaming || isAtLeast3G != mIsAtLeast3G); + if (Constants.LOGVV) { + if (mStateChanged) { + Log.v(LOG_TAG, "Network state changed: "); + Log.v(LOG_TAG, "Starting State: " + + (isConnected ? "Connected " : "Not Connected ") + + (isCellularConnection ? "Cellular " : "WiFi ") + + (isRoaming ? "Roaming " : "Local ") + + (isAtLeast3G ? "3G+ " : "<3G ")); + Log.v(LOG_TAG, "Ending State: " + + (mIsConnected ? "Connected " : "Not Connected ") + + (mIsCellularConnection ? "Cellular " : "WiFi ") + + (mIsRoaming ? "Roaming " : "Local ") + + (mIsAtLeast3G ? "3G+ " : "<3G ")); + + if (isServiceRunning()) { + if (mIsRoaming) { + mStatus = STATUS_WAITING_FOR_NETWORK; + mControl = CONTROL_PAUSED; + } else if (mIsCellularConnection) { + DownloadsDB db = DownloadsDB.getDB(this); + int flags = db.getFlags(); + if (0 == (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { + mStatus = STATUS_QUEUED_FOR_WIFI; + mControl = CONTROL_PAUSED; + } + } + } + + } + } + } + + /** + * Polls the network state, setting the flags appropriately. + */ + void pollNetworkState() { + if (null == mConnectivityManager) { + mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + } + if (null == mWifiManager) { + mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + } + if (mConnectivityManager == null) { + Log.w(Constants.TAG, + "couldn't get connectivity manager to poll network state"); + } else { + NetworkInfo activeInfo = mConnectivityManager + .getActiveNetworkInfo(); + updateNetworkState(activeInfo); + } + } + + public static final int NO_DOWNLOAD_REQUIRED = 0; + public static final int LVL_CHECK_REQUIRED = 1; + public static final int DOWNLOAD_REQUIRED = 2; + + public static final String EXTRA_PACKAGE_NAME = "EPN"; + public static final String EXTRA_PENDING_INTENT = "EPI"; + public static final String EXTRA_MESSAGE_HANDLER = "EMH"; + + /** + * Returns true if the LVL check is required + * + * @param db a downloads DB synchronized with the latest state + * @param pi the package info for the project + * @return returns true if the filenames need to be returned + */ + private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo pi) { + // we need to update the LVL check and get a successful status to + // proceed + if (db.mVersionCode != pi.versionCode) { + return true; + } + return false; + } + + /** + * Careful! Only use this internally. + * + * @return whether we think the service is running + */ + private static synchronized boolean isServiceRunning() { + return sIsRunning; + } + + private static synchronized void setServiceRunning(boolean isRunning) { + sIsRunning = isRunning; + } + + public static int startDownloadServiceIfRequired(Context context, + Intent intent, Class serviceClass) throws NameNotFoundException { + final PendingIntent pendingIntent = (PendingIntent) intent + .getParcelableExtra(EXTRA_PENDING_INTENT); + return startDownloadServiceIfRequired(context, pendingIntent, + serviceClass); + } + + public static int startDownloadServiceIfRequired(Context context, + PendingIntent pendingIntent, Class serviceClass) + throws NameNotFoundException + { + String packageName = context.getPackageName(); + String className = serviceClass.getName(); + + return startDownloadServiceIfRequired(context, pendingIntent, + packageName, className); + } + + /** + * Starts the download if necessary. This function starts a flow that does ` + * many things. 1) Checks to see if the APK version has been checked and the + * metadata database updated 2) If the APK version does not match, checks + * the new LVL status to see if a new download is required 3) If the APK + * version does match, then checks to see if the download(s) have been + * completed 4) If the downloads have been completed, returns + * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the + * startup of an application to quickly ascertain if the application needs + * to wait to hear about any updated APK expansion files. Note that this + * does mean that the application MUST be run for the first time with a + * network connection, even if Market delivers all of the files. + * + * @param context + * @param thisIntent + * @return true if the app should wait for more guidance from the + * downloader, false if the app can continue + * @throws NameNotFoundException + */ + public static int startDownloadServiceIfRequired(Context context, + PendingIntent pendingIntent, String classPackage, String className) + throws NameNotFoundException { + // first: do we need to do an LVL update? + // we begin by getting our APK version from the package manager + final PackageInfo pi = context.getPackageManager().getPackageInfo( + context.getPackageName(), 0); + + int status = NO_DOWNLOAD_REQUIRED; + + // the database automatically reads the metadata for version code + // and download status when the instance is created + DownloadsDB db = DownloadsDB.getDB(context); + + // we need to update the LVL check and get a successful status to + // proceed + if (isLVLCheckRequired(db, pi)) { + status = LVL_CHECK_REQUIRED; + } + // we don't have to update LVL. do we still have a download to start? + if (db.mStatus == 0) { + DownloadInfo[] infos = db.getDownloads(); + if (null != infos) { + for (DownloadInfo info : infos) { + if (!Helpers.doesFileExist(context, info.mFileName, info.mTotalBytes, true)) { + status = DOWNLOAD_REQUIRED; + db.updateStatus(-1); + break; + } + } + } + } else { + status = DOWNLOAD_REQUIRED; + } + switch (status) { + case DOWNLOAD_REQUIRED: + case LVL_CHECK_REQUIRED: + Intent fileIntent = new Intent(); + fileIntent.setClassName(classPackage, className); + fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent); + context.startService(fileIntent); + break; + } + return status; + } + + @Override + public void requestAbortDownload() { + mControl = CONTROL_PAUSED; + mStatus = STATUS_CANCELED; + } + + @Override + public void requestPauseDownload() { + mControl = CONTROL_PAUSED; + mStatus = STATUS_PAUSED_BY_APP; + } + + @Override + public void setDownloadFlags(int flags) { + DownloadsDB.getDB(this).updateFlags(flags); + } + + @Override + public void requestContinueDownload() { + if (mControl == CONTROL_PAUSED) { + mControl = CONTROL_RUN; + } + Intent fileIntent = new Intent(this, this.getClass()); + fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); + this.startService(fileIntent); + } + + public abstract String getPublicKey(); + + public abstract byte[] getSALT(); + + public abstract String getAlarmReceiverClassName(); + + private class LVLRunnable implements Runnable { + LVLRunnable(Context context, PendingIntent intent) { + mContext = context; + mPendingIntent = intent; + } + + final Context mContext; + + @Override + public void run() { + setServiceRunning(true); + mNotification.onDownloadStateChanged(IDownloaderClient.STATE_FETCHING_URL); + String deviceId = Secure.getString(mContext.getContentResolver(), + Secure.ANDROID_ID); + + final APKExpansionPolicy aep = new APKExpansionPolicy(mContext, + new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId)); + + // reset our policy back to the start of the world to force a + // re-check + aep.resetPolicy(); + + // let's try and get the OBB file from LVL first + // Construct the LicenseChecker with a Policy. + final LicenseChecker checker = new LicenseChecker(mContext, aep, + getPublicKey() // Your public licensing key. + ); + checker.checkAccess(new LicenseCheckerCallback() { + + @Override + public void allow(int reason) { + try { + int count = aep.getExpansionURLCount(); + DownloadsDB db = DownloadsDB.getDB(mContext); + int status = 0; + if (count != 0) { + for (int i = 0; i < count; i++) { + String currentFileName = aep + .getExpansionFileName(i); + if (null != currentFileName) { + DownloadInfo di = new DownloadInfo(i, + currentFileName, mContext.getPackageName()); + + long fileSize = aep.getExpansionFileSize(i); + if (handleFileUpdated(db, i, currentFileName, + fileSize)) { + status |= -1; + di.resetDownload(); + di.mUri = aep.getExpansionURL(i); + di.mTotalBytes = fileSize; + di.mStatus = status; + db.updateDownload(di); + } else { + // we need to read the download + // information + // from + // the database + DownloadInfo dbdi = db + .getDownloadInfoByFileName(di.mFileName); + if (null == dbdi) { + // the file exists already and is + // the + // correct size + // was delivered by Market or + // through + // another mechanism + Log.d(LOG_TAG, "file " + di.mFileName + + " found. Not downloading."); + di.mStatus = STATUS_SUCCESS; + di.mTotalBytes = fileSize; + di.mCurrentBytes = fileSize; + di.mUri = aep.getExpansionURL(i); + db.updateDownload(di); + } else if (dbdi.mStatus != STATUS_SUCCESS) { + // we just update the URL + dbdi.mUri = aep.getExpansionURL(i); + db.updateDownload(dbdi); + status |= -1; + } + } + } + } + } + // first: do we need to do an LVL update? + // we begin by getting our APK version from the package + // manager + PackageInfo pi; + try { + pi = mContext.getPackageManager().getPackageInfo( + mContext.getPackageName(), 0); + db.updateMetadata(pi.versionCode, status); + Class serviceClass = DownloaderService.this.getClass(); + switch (startDownloadServiceIfRequired(mContext, mPendingIntent, + serviceClass)) { + case NO_DOWNLOAD_REQUIRED: + mNotification + .onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); + break; + case LVL_CHECK_REQUIRED: + // DANGER WILL ROBINSON! + Log.e(LOG_TAG, "In LVL checking loop!"); + mNotification + .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); + throw new RuntimeException( + "Error with LVL checking and database integrity"); + case DOWNLOAD_REQUIRED: + // do nothing. the download will notify the + // application + // when things are done + break; + } + } catch (NameNotFoundException e1) { + e1.printStackTrace(); + throw new RuntimeException( + "Error with getting information from package name"); + } + } finally { + setServiceRunning(false); + } + } + + @Override + public void dontAllow(int reason) { + try + { + switch (reason) { + case Policy.NOT_LICENSED: + mNotification + .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); + break; + case Policy.RETRY: + mNotification + .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); + break; + } + } finally { + setServiceRunning(false); + } + + } + + @Override + public void applicationError(int errorCode) { + try { + mNotification + .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); + } finally { + setServiceRunning(false); + } + } + + }); + + } + + }; + + /** + * Updates the LVL information from the server. + * + * @param context + */ + public void updateLVL(final Context context) { + Context c = context.getApplicationContext(); + Handler h = new Handler(c.getMainLooper()); + h.post(new LVLRunnable(c, mPendingIntent)); + } + + /** + * The APK has been updated and a filename has been sent down from the + * Market call. If the file has the same name as the previous file, we do + * nothing as the file is guaranteed to be the same. If the file does not + * have the same name, we download it if it hasn't already been delivered by + * Market. + * + * @param index the index of the file from market (0 = main, 1 = patch) + * @param filename the name of the new file + * @param fileSize the size of the new file + * @return + */ + public boolean handleFileUpdated(DownloadsDB db, int index, + String filename, long fileSize) { + DownloadInfo di = db.getDownloadInfoByFileName(filename); + if (null != di) { + String oldFile = di.mFileName; + // cleanup + if (null != oldFile) { + if (filename.equals(oldFile)) { + return false; + } + + // remove partially downloaded file if it is there + String deleteFile = Helpers.generateSaveFileName(this, oldFile); + File f = new File(deleteFile); + if (f.exists()) + f.delete(); + } + } + return !Helpers.doesFileExist(this, filename, fileSize, true); + } + + private void scheduleAlarm(long wakeUp) { + AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + if (alarms == null) { + Log.e(Constants.TAG, "couldn't get alarm manager"); + return; + } + + if (Constants.LOGV) { + Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); + } + + String className = getAlarmReceiverClassName(); + Intent intent = new Intent(Constants.ACTION_RETRY); + intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); + intent.setClassName(this.getPackageName(), + className); + mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, + PendingIntent.FLAG_ONE_SHOT); + alarms.set( + AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + wakeUp, mAlarmIntent + ); + } + + private void cancelAlarms() { + if (null != mAlarmIntent) { + AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + if (alarms == null) { + Log.e(Constants.TAG, "couldn't get alarm manager"); + return; + } + alarms.cancel(mAlarmIntent); + mAlarmIntent = null; + } + } + + /** + * We use this to track network state, such as when WiFi, Cellular, etc. is + * enabled when downloads are paused or in progress. + */ + private class InnerBroadcastReceiver extends BroadcastReceiver { + final Service mService; + + InnerBroadcastReceiver(Service service) { + mService = service; + } + + @Override + public void onReceive(Context context, Intent intent) { + pollNetworkState(); + if (mStateChanged + && !isServiceRunning()) { + Log.d(Constants.TAG, "InnerBroadcastReceiver Called"); + Intent fileIntent = new Intent(context, mService.getClass()); + fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); + // send a new intent to the service + context.startService(fileIntent); + } + } + }; + + /** + * This is the main thread for the Downloader. This thread is responsible + * for queuing up downloads and other goodness. + */ + @Override + protected void onHandleIntent(Intent intent) { + setServiceRunning(true); + try { + // the database automatically reads the metadata for version code + // and download status when the instance is created + DownloadsDB db = DownloadsDB.getDB(this); + final PendingIntent pendingIntent = (PendingIntent) intent + .getParcelableExtra(EXTRA_PENDING_INTENT); + + if (null != pendingIntent) + { + mNotification.setClientIntent(pendingIntent); + mPendingIntent = pendingIntent; + } else if (null != mPendingIntent) { + mNotification.setClientIntent(mPendingIntent); + } else { + Log.e(LOG_TAG, "Downloader started in bad state without notification intent."); + return; + } + + // when the LVL check completes, a successful response will update + // the service + if (isLVLCheckRequired(db, mPackageInfo)) { + updateLVL(this); + return; + } + + // get each download + DownloadInfo[] infos = db.getDownloads(); + mBytesSoFar = 0; + mTotalLength = 0; + mFileCount = infos.length; + for (DownloadInfo info : infos) { + // We do an (simple) integrity check on each file, just to make + // sure + if (info.mStatus == STATUS_SUCCESS) { + // verify that the file matches the state + if (!Helpers.doesFileExist(this, info.mFileName, info.mTotalBytes, true)) { + info.mStatus = 0; + info.mCurrentBytes = 0; + } + } + // get aggregate data + mTotalLength += info.mTotalBytes; + mBytesSoFar += info.mCurrentBytes; + } + + // loop through all downloads and fetch them + pollNetworkState(); + if (null == mConnReceiver) { + + /** + * We use this to track network state, such as when WiFi, + * Cellular, etc. is enabled when downloads are paused or in + * progress. + */ + mConnReceiver = new InnerBroadcastReceiver(this); + IntentFilter intentFilter = new IntentFilter( + ConnectivityManager.CONNECTIVITY_ACTION); + intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + registerReceiver(mConnReceiver, intentFilter); + } + + for (DownloadInfo info : infos) { + long startingCount = info.mCurrentBytes; + + if (info.mStatus != STATUS_SUCCESS) { + DownloadThread dt = new DownloadThread(info, this, mNotification); + cancelAlarms(); + scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG); + dt.run(); + cancelAlarms(); + } + db.updateFromDb(info); + boolean setWakeWatchdog = false; + int notifyStatus; + switch (info.mStatus) { + case STATUS_FORBIDDEN: + // the URL is out of date + updateLVL(this); + return; + case STATUS_SUCCESS: + mBytesSoFar += info.mCurrentBytes - startingCount; + db.updateMetadata(mPackageInfo.versionCode, 0); + continue; + case STATUS_FILE_DELIVERED_INCORRECTLY: + // we may be on a network that is returning us a web + // page on redirect + notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE; + info.mCurrentBytes = 0; + db.updateDownload(info); + setWakeWatchdog = true; + break; + case STATUS_PAUSED_BY_APP: + notifyStatus = IDownloaderClient.STATE_PAUSED_BY_REQUEST; + break; + case STATUS_WAITING_FOR_NETWORK: + case STATUS_WAITING_TO_RETRY: + notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE; + setWakeWatchdog = true; + break; + case STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION: + case STATUS_QUEUED_FOR_WIFI: + // look for more detail here + if (null != mWifiManager) { + if (!mWifiManager.isWifiEnabled()) { + notifyStatus = IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION; + setWakeWatchdog = true; + break; + } + } + notifyStatus = IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION; + setWakeWatchdog = true; + break; + case STATUS_CANCELED: + notifyStatus = IDownloaderClient.STATE_FAILED_CANCELED; + setWakeWatchdog = true; + break; + + case STATUS_INSUFFICIENT_SPACE_ERROR: + notifyStatus = IDownloaderClient.STATE_FAILED_SDCARD_FULL; + setWakeWatchdog = true; + break; + + case STATUS_DEVICE_NOT_FOUND_ERROR: + notifyStatus = IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE; + setWakeWatchdog = true; + break; + + default: + notifyStatus = IDownloaderClient.STATE_FAILED; + break; + } + if (setWakeWatchdog) { + scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER); + } else { + cancelAlarms(); + } + // failure or pause state + mNotification.onDownloadStateChanged(notifyStatus); + return; + } + + // all downloads complete + mNotification.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); + } finally { + setServiceRunning(false); + } + } + + @Override + public void onDestroy() { + if (null != mConnReceiver) { + unregisterReceiver(mConnReceiver); + mConnReceiver = null; + } + mServiceStub.disconnect(this); + super.onDestroy(); + } + + public int getNetworkAvailabilityState(DownloadsDB db) { + if (mIsConnected) { + if (!mIsCellularConnection) + return NETWORK_OK; + int flags = db.mFlags; + if (mIsRoaming) + return NETWORK_CANNOT_USE_ROAMING; + if (0 != (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { + return NETWORK_OK; + } else { + return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR; + } + } + return NETWORK_NO_CONNECTION; + } + + @Override + public void onCreate() { + super.onCreate(); + try { + mPackageInfo = getPackageManager().getPackageInfo( + getPackageName(), 0); + ApplicationInfo ai = getApplicationInfo(); + CharSequence applicationLabel = getPackageManager().getApplicationLabel(ai); + mNotification = new DownloadNotification(this, applicationLabel); + + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + } + + /** + * Exception thrown from methods called by generateSaveFile() for any fatal + * error. + */ + public static class GenerateSaveFileError extends Exception { + private static final long serialVersionUID = 3465966015408936540L; + int mStatus; + String mMessage; + + public GenerateSaveFileError(int status, String message) { + mStatus = status; + mMessage = message; + } + } + + /** + * Returns the filename (where the file should be saved) from info about a + * download + */ + public String generateTempSaveFileName(String fileName) { + String path = Helpers.getSaveFilePath(this) + + File.separator + fileName + TEMP_EXT; + return path; + } + + /** + * Creates a filename (where the file should be saved) from info about a + * download. + */ + public String generateSaveFile(String filename, long filesize) + throws GenerateSaveFileError { + String path = generateTempSaveFileName(filename); + File expPath = new File(path); + if (!Helpers.isExternalMediaMounted()) { + Log.d(Constants.TAG, "External media not mounted: " + path); + throw new GenerateSaveFileError(STATUS_DEVICE_NOT_FOUND_ERROR, + "external media is not yet mounted"); + + } + if (expPath.exists()) { + Log.d(Constants.TAG, "File already exists: " + path); + throw new GenerateSaveFileError(STATUS_FILE_ALREADY_EXISTS_ERROR, + "requested destination file already exists"); + } + if (Helpers.getAvailableBytes(Helpers.getFilesystemRoot(path)) < filesize) { + throw new GenerateSaveFileError(STATUS_INSUFFICIENT_SPACE_ERROR, + "insufficient space on external storage"); + } + return path; + } + + /** + * @return a non-localized string appropriate for logging corresponding to + * one of the NETWORK_* constants. + */ + public String getLogMessageForNetworkError(int networkError) { + switch (networkError) { + case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE: + return "download size exceeds recommended limit for mobile network"; + + case NETWORK_UNUSABLE_DUE_TO_SIZE: + return "download size exceeds limit for mobile network"; + + case NETWORK_NO_CONNECTION: + return "no network connection available"; + + case NETWORK_CANNOT_USE_ROAMING: + return "download cannot use the current network connection because it is roaming"; + + case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: + return "download was requested to not use the current network type"; + + default: + return "unknown error with network connectivity"; + } + } + + public int getControl() { + return mControl; + } + + public int getStatus() { + return mStatus; + } + + /** + * Calculating a moving average for the speed so we don't get jumpy + * calculations for time etc. + */ + static private final float SMOOTHING_FACTOR = 0.005f; + + public void notifyUpdateBytes(long totalBytesSoFar) { + long timeRemaining; + long currentTime = SystemClock.uptimeMillis(); + if (0 != mMillisecondsAtSample) { + // we have a sample. + long timePassed = currentTime - mMillisecondsAtSample; + long bytesInSample = totalBytesSoFar - mBytesAtSample; + float currentSpeedSample = (float) bytesInSample / (float) timePassed; + if (0 != mAverageDownloadSpeed) { + mAverageDownloadSpeed = SMOOTHING_FACTOR * currentSpeedSample + + (1 - SMOOTHING_FACTOR) * mAverageDownloadSpeed; + } else { + mAverageDownloadSpeed = currentSpeedSample; + } + timeRemaining = (long) ((mTotalLength - totalBytesSoFar) / mAverageDownloadSpeed); + } else { + timeRemaining = -1; + } + mMillisecondsAtSample = currentTime; + mBytesAtSample = totalBytesSoFar; + mNotification.onDownloadProgress( + new DownloadProgressInfo(mTotalLength, + totalBytesSoFar, + timeRemaining, + mAverageDownloadSpeed) + ); + + } + + @Override + protected boolean shouldStop() { + // the database automatically reads the metadata for version code + // and download status when the instance is created + DownloadsDB db = DownloadsDB.getDB(this); + if (db.mStatus == 0) { + return true; + } + return false; + } + + @Override + public void requestDownloadStatus() { + mNotification.resendState(); + } + + @Override + public void onClientUpdated(Messenger clientMessenger) { + this.mClientMessenger = clientMessenger; + mNotification.setMessenger(mClientMessenger); + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java new file mode 100755 index 0000000000..250299c400 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDoneException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.provider.BaseColumns; +import android.util.Log; + +public class DownloadsDB { + private static final String DATABASE_NAME = "DownloadsDB"; + private static final int DATABASE_VERSION = 7; + public static final String LOG_TAG = DownloadsDB.class.getName(); + final SQLiteOpenHelper mHelper; + SQLiteStatement mGetDownloadByIndex; + SQLiteStatement mUpdateCurrentBytes; + private static DownloadsDB mDownloadsDB; + long mMetadataRowID = -1; + int mVersionCode = -1; + int mStatus = -1; + int mFlags; + + static public synchronized DownloadsDB getDB(Context paramContext) { + if (null == mDownloadsDB) { + return new DownloadsDB(paramContext); + } + return mDownloadsDB; + } + + private SQLiteStatement getDownloadByIndexStatement() { + if (null == mGetDownloadByIndex) { + mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement( + "SELECT " + BaseColumns._ID + " FROM " + + DownloadColumns.TABLE_NAME + " WHERE " + + DownloadColumns.INDEX + " = ?"); + } + return mGetDownloadByIndex; + } + + private SQLiteStatement getUpdateCurrentBytesStatement() { + if (null == mUpdateCurrentBytes) { + mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement( + "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES + + " = ?" + + " WHERE " + DownloadColumns.INDEX + " = ?"); + } + return mUpdateCurrentBytes; + } + + private DownloadsDB(Context paramContext) { + this.mHelper = new DownloadsContentDBHelper(paramContext); + final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); + // Query for the version code, the row ID of the metadata (for future + // updating) the status and the flags + Cursor cur = sqldb.rawQuery("SELECT " + + MetadataColumns.APKVERSION + "," + + BaseColumns._ID + "," + + MetadataColumns.DOWNLOAD_STATUS + "," + + MetadataColumns.FLAGS + + " FROM " + + MetadataColumns.TABLE_NAME + " LIMIT 1", null); + if (null != cur && cur.moveToFirst()) { + mVersionCode = cur.getInt(0); + mMetadataRowID = cur.getLong(1); + mStatus = cur.getInt(2); + mFlags = cur.getInt(3); + cur.close(); + } + mDownloadsDB = this; + } + + protected DownloadInfo getDownloadInfoByFileName(String fileName) { + final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); + Cursor itemcur = null; + try { + itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, + DownloadColumns.FILENAME + " = ?", + new String[] { + fileName + }, null, null, null); + if (null != itemcur && itemcur.moveToFirst()) { + return getDownloadInfoFromCursor(itemcur); + } + } finally { + if (null != itemcur) + itemcur.close(); + } + return null; + } + + public long getIDForDownloadInfo(final DownloadInfo di) { + return getIDByIndex(di.mIndex); + } + + public long getIDByIndex(int index) { + SQLiteStatement downloadByIndex = getDownloadByIndexStatement(); + downloadByIndex.clearBindings(); + downloadByIndex.bindLong(1, index); + try { + return downloadByIndex.simpleQueryForLong(); + } catch (SQLiteDoneException e) { + return -1; + } + } + + public void updateDownloadCurrentBytes(final DownloadInfo di) { + SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement(); + downloadCurrentBytes.clearBindings(); + downloadCurrentBytes.bindLong(1, di.mCurrentBytes); + downloadCurrentBytes.bindLong(2, di.mIndex); + downloadCurrentBytes.execute(); + } + + public void close() { + this.mHelper.close(); + } + + protected static class DownloadsContentDBHelper extends SQLiteOpenHelper { + DownloadsContentDBHelper(Context paramContext) { + super(paramContext, DATABASE_NAME, null, DATABASE_VERSION); + } + + private String createTableQueryFromArray(String paramString, + String[][] paramArrayOfString) { + StringBuilder localStringBuilder = new StringBuilder(); + localStringBuilder.append("CREATE TABLE "); + localStringBuilder.append(paramString); + localStringBuilder.append(" ("); + int i = paramArrayOfString.length; + for (int j = 0;; j++) { + if (j >= i) { + localStringBuilder + .setLength(localStringBuilder.length() - 1); + localStringBuilder.append(");"); + return localStringBuilder.toString(); + } + String[] arrayOfString = paramArrayOfString[j]; + localStringBuilder.append(' '); + localStringBuilder.append(arrayOfString[0]); + localStringBuilder.append(' '); + localStringBuilder.append(arrayOfString[1]); + localStringBuilder.append(','); + } + } + + /** + * These two arrays must match and have the same order. For every Schema + * there must be a corresponding table name. + */ + static final private String[][][] sSchemas = { + DownloadColumns.SCHEMA, MetadataColumns.SCHEMA + }; + + static final private String[] sTables = { + DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME + }; + + /** + * Goes through all of the tables in sTables and drops each table if it + * exists. Altered to no longer make use of reflection. + */ + private void dropTables(SQLiteDatabase paramSQLiteDatabase) { + for (String table : sTables) { + try { + paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table); + } catch (Exception localException) { + localException.printStackTrace(); + } + } + } + + /** + * Goes through all of the tables in sTables and creates a database with + * the corresponding schema described in sSchemas. Altered to no longer + * make use of reflection. + */ + public void onCreate(SQLiteDatabase paramSQLiteDatabase) { + int numSchemas = sSchemas.length; + for (int i = 0; i < numSchemas; i++) { + try { + String[][] schema = (String[][]) sSchemas[i]; + paramSQLiteDatabase.execSQL(createTableQueryFromArray( + sTables[i], schema)); + } catch (Exception localException) { + while (true) + localException.printStackTrace(); + } + } + } + + public void onUpgrade(SQLiteDatabase paramSQLiteDatabase, + int paramInt1, int paramInt2) { + Log.w(DownloadsContentDBHelper.class.getName(), + "Upgrading database from version " + paramInt1 + " to " + + paramInt2 + ", which will destroy all old data"); + dropTables(paramSQLiteDatabase); + onCreate(paramSQLiteDatabase); + } + } + + public static class MetadataColumns implements BaseColumns { + public static final String APKVERSION = "APKVERSION"; + public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS"; + public static final String FLAGS = "DOWNLOADFLAGS"; + + public static final String[][] SCHEMA = { + { + BaseColumns._ID, "INTEGER PRIMARY KEY" + }, + { + APKVERSION, "INTEGER" + }, { + DOWNLOAD_STATUS, "INTEGER" + }, + { + FLAGS, "INTEGER" + } + }; + public static final String TABLE_NAME = "MetadataColumns"; + public static final String _ID = "MetadataColumns._id"; + } + + public static class DownloadColumns implements BaseColumns { + public static final String INDEX = "FILEIDX"; + public static final String URI = "URI"; + public static final String FILENAME = "FN"; + public static final String ETAG = "ETAG"; + + public static final String TOTALBYTES = "TOTALBYTES"; + public static final String CURRENTBYTES = "CURRENTBYTES"; + public static final String LASTMOD = "LASTMOD"; + + public static final String STATUS = "STATUS"; + public static final String CONTROL = "CONTROL"; + public static final String NUM_FAILED = "FAILCOUNT"; + public static final String RETRY_AFTER = "RETRYAFTER"; + public static final String REDIRECT_COUNT = "REDIRECTCOUNT"; + + public static final String[][] SCHEMA = { + { + BaseColumns._ID, "INTEGER PRIMARY KEY" + }, + { + INDEX, "INTEGER UNIQUE" + }, { + URI, "TEXT" + }, + { + FILENAME, "TEXT UNIQUE" + }, { + ETAG, "TEXT" + }, + { + TOTALBYTES, "INTEGER" + }, { + CURRENTBYTES, "INTEGER" + }, + { + LASTMOD, "INTEGER" + }, { + STATUS, "INTEGER" + }, + { + CONTROL, "INTEGER" + }, { + NUM_FAILED, "INTEGER" + }, + { + RETRY_AFTER, "INTEGER" + }, { + REDIRECT_COUNT, "INTEGER" + } + }; + public static final String TABLE_NAME = "DownloadColumns"; + public static final String _ID = "DownloadColumns._id"; + } + + private static final String[] DC_PROJECTION = { + DownloadColumns.FILENAME, + DownloadColumns.URI, DownloadColumns.ETAG, + DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES, + DownloadColumns.LASTMOD, DownloadColumns.STATUS, + DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED, + DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT, + DownloadColumns.INDEX + }; + + private static final int FILENAME_IDX = 0; + private static final int URI_IDX = 1; + private static final int ETAG_IDX = 2; + private static final int TOTALBYTES_IDX = 3; + private static final int CURRENTBYTES_IDX = 4; + private static final int LASTMOD_IDX = 5; + private static final int STATUS_IDX = 6; + private static final int CONTROL_IDX = 7; + private static final int NUM_FAILED_IDX = 8; + private static final int RETRY_AFTER_IDX = 9; + private static final int REDIRECT_COUNT_IDX = 10; + private static final int INDEX_IDX = 11; + + /** + * This function will add a new file to the database if it does not exist. + * + * @param di DownloadInfo that we wish to store + * @return the row id of the record to be updated/inserted, or -1 + */ + public boolean updateDownload(DownloadInfo di) { + ContentValues cv = new ContentValues(); + cv.put(DownloadColumns.INDEX, di.mIndex); + cv.put(DownloadColumns.FILENAME, di.mFileName); + cv.put(DownloadColumns.URI, di.mUri); + cv.put(DownloadColumns.ETAG, di.mETag); + cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes); + cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes); + cv.put(DownloadColumns.LASTMOD, di.mLastMod); + cv.put(DownloadColumns.STATUS, di.mStatus); + cv.put(DownloadColumns.CONTROL, di.mControl); + cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed); + cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter); + cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount); + return updateDownload(di, cv); + } + + public boolean updateDownload(DownloadInfo di, ContentValues cv) { + long id = di == null ? -1 : getIDForDownloadInfo(di); + try { + final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); + if (id != -1) { + if (1 != sqldb.update(DownloadColumns.TABLE_NAME, + cv, DownloadColumns._ID + " = " + id, null)) { + return false; + } + } else { + return -1 != sqldb.insert(DownloadColumns.TABLE_NAME, + DownloadColumns.URI, cv); + } + } catch (android.database.sqlite.SQLiteException ex) { + ex.printStackTrace(); + } + return false; + } + + public int getLastCheckedVersionCode() { + return mVersionCode; + } + + public boolean isDownloadRequired() { + final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); + Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM " + + DownloadColumns.TABLE_NAME + " WHERE " + + DownloadColumns.STATUS + " <> 0", null); + try { + if (null != cur && cur.moveToFirst()) { + return 0 == cur.getInt(0); + } + } finally { + if (null != cur) + cur.close(); + } + return true; + } + + public int getFlags() { + return mFlags; + } + + public boolean updateFlags(int flags) { + if (mFlags != flags) { + ContentValues cv = new ContentValues(); + cv.put(MetadataColumns.FLAGS, flags); + if (updateMetadata(cv)) { + mFlags = flags; + return true; + } else { + return false; + } + } else { + return true; + } + }; + + public boolean updateStatus(int status) { + if (mStatus != status) { + ContentValues cv = new ContentValues(); + cv.put(MetadataColumns.DOWNLOAD_STATUS, status); + if (updateMetadata(cv)) { + mStatus = status; + return true; + } else { + return false; + } + } else { + return true; + } + }; + + public boolean updateMetadata(ContentValues cv) { + final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); + if (-1 == this.mMetadataRowID) { + long newID = sqldb.insert(MetadataColumns.TABLE_NAME, + MetadataColumns.APKVERSION, cv); + if (-1 == newID) + return false; + mMetadataRowID = newID; + } else { + if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv, + BaseColumns._ID + " = " + mMetadataRowID, null)) + return false; + } + return true; + } + + public boolean updateMetadata(int apkVersion, int downloadStatus) { + ContentValues cv = new ContentValues(); + cv.put(MetadataColumns.APKVERSION, apkVersion); + cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus); + if (updateMetadata(cv)) { + mVersionCode = apkVersion; + mStatus = downloadStatus; + return true; + } else { + return false; + } + }; + + public boolean updateFromDb(DownloadInfo di) { + final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); + Cursor cur = null; + try { + cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, + DownloadColumns.FILENAME + "= ?", + new String[] { + di.mFileName + }, null, null, null); + if (null != cur && cur.moveToFirst()) { + setDownloadInfoFromCursor(di, cur); + return true; + } + return false; + } finally { + if (null != cur) { + cur.close(); + } + } + } + + public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) { + di.mUri = cur.getString(URI_IDX); + di.mETag = cur.getString(ETAG_IDX); + di.mTotalBytes = cur.getLong(TOTALBYTES_IDX); + di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX); + di.mLastMod = cur.getLong(LASTMOD_IDX); + di.mStatus = cur.getInt(STATUS_IDX); + di.mControl = cur.getInt(CONTROL_IDX); + di.mNumFailed = cur.getInt(NUM_FAILED_IDX); + di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX); + di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX); + } + + public DownloadInfo getDownloadInfoFromCursor(Cursor cur) { + DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX), + cur.getString(FILENAME_IDX), this.getClass().getPackage() + .getName()); + setDownloadInfoFromCursor(di, cur); + return di; + } + + public DownloadInfo[] getDownloads() { + final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); + Cursor cur = null; + try { + cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null, + null, null, null, null); + if (null != cur && cur.moveToFirst()) { + DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()]; + int idx = 0; + do { + DownloadInfo di = getDownloadInfoFromCursor(cur); + retInfos[idx++] = di; + } while (cur.moveToNext()); + return retInfos; + } + return null; + } finally { + if (null != cur) { + cur.close(); + } + } + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java new file mode 100644 index 0000000000..3f440e9893 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import android.text.format.Time; + +import java.util.Calendar; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Helper for parsing an HTTP date. + */ +public final class HttpDateTime { + + /* + * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT + * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850, + * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format + * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon + * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS + * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon + * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first + * digit is zero. Mon can be the full name of the month. + */ + private static final String HTTP_DATE_RFC_REGEXP = + "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" + + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; + + private static final String HTTP_DATE_ANSIC_REGEXP = + "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" + + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; + + /** + * The compiled version of the HTTP-date regular expressions. + */ + private static final Pattern HTTP_DATE_RFC_PATTERN = + Pattern.compile(HTTP_DATE_RFC_REGEXP); + private static final Pattern HTTP_DATE_ANSIC_PATTERN = + Pattern.compile(HTTP_DATE_ANSIC_REGEXP); + + private static class TimeOfDay { + TimeOfDay(int h, int m, int s) { + this.hour = h; + this.minute = m; + this.second = s; + } + + int hour; + int minute; + int second; + } + + public static long parse(String timeString) + throws IllegalArgumentException { + + int date = 1; + int month = Calendar.JANUARY; + int year = 1970; + TimeOfDay timeOfDay; + + Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); + if (rfcMatcher.find()) { + date = getDate(rfcMatcher.group(1)); + month = getMonth(rfcMatcher.group(2)); + year = getYear(rfcMatcher.group(3)); + timeOfDay = getTime(rfcMatcher.group(4)); + } else { + Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); + if (ansicMatcher.find()) { + month = getMonth(ansicMatcher.group(1)); + date = getDate(ansicMatcher.group(2)); + timeOfDay = getTime(ansicMatcher.group(3)); + year = getYear(ansicMatcher.group(4)); + } else { + throw new IllegalArgumentException(); + } + } + + // FIXME: Y2038 BUG! + if (year >= 2038) { + year = 2038; + month = Calendar.JANUARY; + date = 1; + } + + Time time = new Time(Time.TIMEZONE_UTC); + time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, + month, year); + return time.toMillis(false /* use isDst */); + } + + private static int getDate(String dateString) { + if (dateString.length() == 2) { + return (dateString.charAt(0) - '0') * 10 + + (dateString.charAt(1) - '0'); + } else { + return (dateString.charAt(0) - '0'); + } + } + + /* + * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0 + * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20 + * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19 + * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9 + */ + private static int getMonth(String monthString) { + int hash = Character.toLowerCase(monthString.charAt(0)) + + Character.toLowerCase(monthString.charAt(1)) + + Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; + switch (hash) { + case 22: + return Calendar.JANUARY; + case 10: + return Calendar.FEBRUARY; + case 29: + return Calendar.MARCH; + case 32: + return Calendar.APRIL; + case 36: + return Calendar.MAY; + case 42: + return Calendar.JUNE; + case 40: + return Calendar.JULY; + case 26: + return Calendar.AUGUST; + case 37: + return Calendar.SEPTEMBER; + case 35: + return Calendar.OCTOBER; + case 48: + return Calendar.NOVEMBER; + case 9: + return Calendar.DECEMBER; + default: + throw new IllegalArgumentException(); + } + } + + private static int getYear(String yearString) { + if (yearString.length() == 2) { + int year = (yearString.charAt(0) - '0') * 10 + + (yearString.charAt(1) - '0'); + if (year >= 70) { + return year + 1900; + } else { + return year + 2000; + } + } else if (yearString.length() == 3) { + // According to RFC 2822, three digit years should be added to 1900. + int year = (yearString.charAt(0) - '0') * 100 + + (yearString.charAt(1) - '0') * 10 + + (yearString.charAt(2) - '0'); + return year + 1900; + } else if (yearString.length() == 4) { + return (yearString.charAt(0) - '0') * 1000 + + (yearString.charAt(1) - '0') * 100 + + (yearString.charAt(2) - '0') * 10 + + (yearString.charAt(3) - '0'); + } else { + return 1970; + } + } + + private static TimeOfDay getTime(String timeString) { + // HH might be H + int i = 0; + int hour = timeString.charAt(i++) - '0'; + if (timeString.charAt(i) != ':') + hour = hour * 10 + (timeString.charAt(i++) - '0'); + // Skip ':' + i++; + + int minute = (timeString.charAt(i++) - '0') * 10 + + (timeString.charAt(i++) - '0'); + // Skip ':' + i++; + + int second = (timeString.charAt(i++) - '0') * 10 + + (timeString.charAt(i++) - '0'); + + return new TimeOfDay(hour, minute, second); + } +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java new file mode 100644 index 0000000000..2e049a4d47 --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import com.godot.game.R; +import com.google.android.vending.expansion.downloader.Helpers; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; + +public class V14CustomNotification implements DownloadNotification.ICustomNotification { + + CharSequence mTitle; + CharSequence mTicker; + int mIcon; + long mTotalKB = -1; + long mCurrentKB = -1; + long mTimeRemaining; + PendingIntent mPendingIntent; + + @Override + public void setIcon(int icon) { + mIcon = icon; + } + + @Override + public void setTitle(CharSequence title) { + mTitle = title; + } + + @Override + public void setTotalBytes(long totalBytes) { + mTotalKB = totalBytes; + } + + @Override + public void setCurrentBytes(long currentBytes) { + mCurrentKB = currentBytes; + } + + void setProgress(Notification.Builder builder) { + + } + + @Override + public Notification updateNotification(Context c) { + Notification.Builder builder = new Notification.Builder(c); + builder.setContentTitle(mTitle); + if (mTotalKB > 0 && -1 != mCurrentKB) { + builder.setProgress((int) (mTotalKB >> 8), (int) (mCurrentKB >> 8), false); + } else { + builder.setProgress(0, 0, true); + } + builder.setContentText(Helpers.getDownloadProgressString(mCurrentKB, mTotalKB)); + builder.setContentInfo(c.getString(R.string.time_remaining_notification, + Helpers.getTimeRemaining(mTimeRemaining))); + if (mIcon != 0) { + builder.setSmallIcon(mIcon); + } else { + int iconResource = android.R.drawable.stat_sys_download; + builder.setSmallIcon(iconResource); + } + builder.setOngoing(true); + builder.setTicker(mTicker); + builder.setContentIntent(mPendingIntent); + builder.setOnlyAlertOnce(true); + + return builder.getNotification(); + } + + @Override + public void setPendingIntent(PendingIntent contentIntent) { + mPendingIntent = contentIntent; + } + + @Override + public void setTicker(CharSequence ticker) { + mTicker = ticker; + } + + @Override + public void setTimeRemaining(long timeRemaining) { + mTimeRemaining = timeRemaining; + } + +} diff --git a/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java new file mode 100644 index 0000000000..94e21de7ca --- /dev/null +++ b/platform/android/java/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.vending.expansion.downloader.impl; + +import com.godot.game.R; +import com.google.android.vending.expansion.downloader.Helpers; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.graphics.BitmapFactory; +import android.view.View; +import android.widget.RemoteViews; + +public class V3CustomNotification implements DownloadNotification.ICustomNotification { + + CharSequence mTitle; + CharSequence mTicker; + int mIcon; + long mTotalBytes = -1; + long mCurrentBytes = -1; + long mTimeRemaining; + PendingIntent mPendingIntent; + Notification mNotification = new Notification(); + + @Override + public void setIcon(int icon) { + mIcon = icon; + } + + @Override + public void setTitle(CharSequence title) { + mTitle = title; + } + + @Override + public void setTotalBytes(long totalBytes) { + mTotalBytes = totalBytes; + } + + @Override + public void setCurrentBytes(long currentBytes) { + mCurrentBytes = currentBytes; + } + + @Override + public Notification updateNotification(Context c) { + Notification n = mNotification; + + n.icon = mIcon; + + n.flags |= Notification.FLAG_ONGOING_EVENT; + + if (android.os.Build.VERSION.SDK_INT > 10) { + n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // only matters for + // Honeycomb + } + + // Build the RemoteView object + RemoteViews expandedView = new RemoteViews( + c.getPackageName(), + R.layout.status_bar_ongoing_event_progress_bar); + + expandedView.setTextViewText(R.id.title, mTitle); + // look at strings + expandedView.setViewVisibility(R.id.description, View.VISIBLE); + expandedView.setTextViewText(R.id.description, + Helpers.getDownloadProgressString(mCurrentBytes, mTotalBytes)); + expandedView.setViewVisibility(R.id.progress_bar_frame, View.VISIBLE); + expandedView.setProgressBar(R.id.progress_bar, + (int) (mTotalBytes >> 8), + (int) (mCurrentBytes >> 8), + mTotalBytes <= 0); + expandedView.setViewVisibility(R.id.time_remaining, View.VISIBLE); + expandedView.setTextViewText( + R.id.time_remaining, + c.getString(R.string.time_remaining_notification, + Helpers.getTimeRemaining(mTimeRemaining))); + expandedView.setTextViewText(R.id.progress_text, + Helpers.getDownloadProgressPercent(mCurrentBytes, mTotalBytes)); + expandedView.setImageViewResource(R.id.appIcon, mIcon); + n.contentView = expandedView; + n.contentIntent = mPendingIntent; + return n; + } + + @Override + public void setPendingIntent(PendingIntent contentIntent) { + mPendingIntent = contentIntent; + } + + @Override + public void setTicker(CharSequence ticker) { + mTicker = ticker; + } + + @Override + public void setTimeRemaining(long timeRemaining) { + mTimeRemaining = timeRemaining; + } + +} diff --git a/platform/android/libs/apk_expansion/AndroidManifest.xml b/platform/android/libs/apk_expansion/AndroidManifest.xml deleted file mode 100644 index 20b74a2988..0000000000 --- a/platform/android/libs/apk_expansion/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/platform/android/libs/apk_expansion/build.xml b/platform/android/libs/apk_expansion/build.xml deleted file mode 100644 index 5b2f2c590e..0000000000 --- a/platform/android/libs/apk_expansion/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/libs/apk_expansion/proguard-project.txt b/platform/android/libs/apk_expansion/proguard-project.txt deleted file mode 100644 index f2fe1559a2..0000000000 --- a/platform/android/libs/apk_expansion/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/platform/android/libs/apk_expansion/project.properties b/platform/android/libs/apk_expansion/project.properties deleted file mode 100644 index eda83430bf..0000000000 --- a/platform/android/libs/apk_expansion/project.properties +++ /dev/null @@ -1,13 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-15 -android.library=true -android.library.reference.1=../play_licensing diff --git a/platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png deleted file mode 100644 index f5b762ecf3..0000000000 Binary files a/platform/android/libs/apk_expansion/res/drawable-hdpi/notify_panel_notification_icon_bg.png and /dev/null differ diff --git a/platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png deleted file mode 100644 index 9ecb8af06c..0000000000 Binary files a/platform/android/libs/apk_expansion/res/drawable-mdpi/notify_panel_notification_icon_bg.png and /dev/null differ diff --git a/platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml deleted file mode 100644 index 23bac02294..0000000000 --- a/platform/android/libs/apk_expansion/res/layout/status_bar_ongoing_event_progress_bar.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/android/libs/apk_expansion/res/values-v11/styles.xml b/platform/android/libs/apk_expansion/res/values-v11/styles.xml deleted file mode 100644 index f2013bc0bf..0000000000 --- a/platform/android/libs/apk_expansion/res/values-v11/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/platform/android/libs/apk_expansion/res/values-v9/styles.xml b/platform/android/libs/apk_expansion/res/values-v9/styles.xml deleted file mode 100644 index 736e77a5d6..0000000000 --- a/platform/android/libs/apk_expansion/res/values-v9/styles.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java deleted file mode 100644 index ff2c6f535a..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Constants.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import java.io.File; - - -/** - * Contains the internal constants that are used in the download manager. - * As a general rule, modifying these constants should be done with care. - */ -public class Constants { - /** Tag used for debugging/logging */ - public static final String TAG = "LVLDL"; - - /** - * Expansion path where we store obb files - */ - public static final String EXP_PATH = File.separator + "Android" - + File.separator + "obb" + File.separator; - - /** The intent that gets sent when the service must wake up for a retry */ - public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; - - /** the intent that gets sent when clicking a successful download */ - public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; - - /** the intent that gets sent when clicking an incomplete/failed download */ - public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; - - /** the intent that gets sent when deleting the notification of a completed download */ - public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; - - /** - * When a number has to be appended to the filename, this string is used to separate the - * base filename from the sequence number - */ - public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; - - /** The default user agent used for downloads */ - public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; - - /** The buffer size used to stream the data */ - public static final int BUFFER_SIZE = 4096; - - /** The minimum amount of progress that has to be done before the progress bar gets updated */ - public static final int MIN_PROGRESS_STEP = 4096; - - /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */ - public static final long MIN_PROGRESS_TIME = 1000; - - /** The maximum number of rows in the database (FIFO) */ - public static final int MAX_DOWNLOADS = 1000; - - /** - * The number of times that the download manager will retry its network - * operations when no progress is happening before it gives up. - */ - public static final int MAX_RETRIES = 5; - - /** - * The minimum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MIN_RETRY_AFTER = 30; // 30s - - /** - * The maximum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h - - /** - * The maximum number of redirects. - */ - public static final int MAX_REDIRECTS = 5; // can't be more than 7. - - /** - * The time between a failure and the first retry after an IOException. - * Each subsequent retry grows exponentially, doubling each time. - * The time is in seconds. - */ - public static final int RETRY_FIRST_DELAY = 30; - - /** Enable separate connectivity logging */ - public static final boolean LOGX = true; - - /** Enable verbose logging */ - public static final boolean LOGV = false; - - /** Enable super-verbose logging */ - private static final boolean LOCAL_LOGVV = false; - public static final boolean LOGVV = LOCAL_LOGVV && LOGV; - - /** - * This download has successfully completed. - * Warning: there might be other status values that indicate success - * in the future. - * Use isSucccess() to capture the entire category. - */ - public static final int STATUS_SUCCESS = 200; - - /** - * This request couldn't be parsed. This is also used when processing - * requests with unknown/unsupported URI schemes. - */ - public static final int STATUS_BAD_REQUEST = 400; - - /** - * This download can't be performed because the content type cannot be - * handled. - */ - public static final int STATUS_NOT_ACCEPTABLE = 406; - - /** - * This download cannot be performed because the length cannot be - * determined accurately. This is the code for the HTTP error "Length - * Required", which is typically used when making requests that require - * a content length but don't have one, and it is also used in the - * client when a response is received whose length cannot be determined - * accurately (therefore making it impossible to know when a download - * completes). - */ - public static final int STATUS_LENGTH_REQUIRED = 411; - - /** - * This download was interrupted and cannot be resumed. - * This is the code for the HTTP error "Precondition Failed", and it is - * also used in situations where the client doesn't have an ETag at all. - */ - public static final int STATUS_PRECONDITION_FAILED = 412; - - /** - * The lowest-valued error status that is not an actual HTTP status code. - */ - public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; - - /** - * The requested destination file already exists. - */ - public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; - - /** - * Some possibly transient error occurred, but we can't resume the download. - */ - public static final int STATUS_CANNOT_RESUME = 489; - - /** - * This download was canceled - */ - public static final int STATUS_CANCELED = 490; - - /** - * This download has completed with an error. - * Warning: there will be other status values that indicate errors in - * the future. Use isStatusError() to capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of a storage issue. - * Typically, that's because the filesystem is missing or full. - * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} - * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. - */ - public static final int STATUS_FILE_ERROR = 492; - - /** - * This download couldn't be completed because of an HTTP - * redirect response that the download manager couldn't - * handle. - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed because of an - * unspecified unhandled HTTP code. - */ - public static final int STATUS_UNHANDLED_HTTP_CODE = 494; - - /** - * This download couldn't be completed because of an - * error receiving or processing data at the HTTP level. - */ - public static final int STATUS_HTTP_DATA_ERROR = 495; - - /** - * This download couldn't be completed because of an - * HttpException while setting up the request. - */ - public static final int STATUS_HTTP_EXCEPTION = 496; - - /** - * This download couldn't be completed because there were - * too many redirects. - */ - public static final int STATUS_TOO_MANY_REDIRECTS = 497; - - /** - * This download couldn't be completed due to insufficient storage - * space. Typically, this is because the SD card is full. - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; - - /** - * This download couldn't be completed because no external storage - * device was found. Typically, this is because the SD card is not - * mounted. - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - - /** - * The wake duration to check to see if a download is possible. - */ - public static final long WATCHDOG_WAKE_TIMER = 60*1000; - - /** - * The wake duration to check to see if the process was killed. - */ - public static final long ACTIVE_THREAD_WATCHDOG = 5*1000; - -} \ No newline at end of file diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java deleted file mode 100644 index 9cb294d721..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.os.Parcel; -import android.os.Parcelable; - - -/** - * This class contains progress information about the active download(s). - * - * When you build the Activity that initiates a download and tracks the - * progress by implementing the {@link IDownloaderClient} interface, you'll - * receive a DownloadProgressInfo object in each call to the {@link - * IDownloaderClient#onDownloadProgress} method. This allows you to update - * your activity's UI with information about the download progress, such - * as the progress so far, time remaining and current speed. - */ -public class DownloadProgressInfo implements Parcelable { - public long mOverallTotal; - public long mOverallProgress; - public long mTimeRemaining; // time remaining - public float mCurrentSpeed; // speed in KB/S - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel p, int i) { - p.writeLong(mOverallTotal); - p.writeLong(mOverallProgress); - p.writeLong(mTimeRemaining); - p.writeFloat(mCurrentSpeed); - } - - public DownloadProgressInfo(Parcel p) { - mOverallTotal = p.readLong(); - mOverallProgress = p.readLong(); - mTimeRemaining = p.readLong(); - mCurrentSpeed = p.readFloat(); - } - - public DownloadProgressInfo(long overallTotal, long overallProgress, - long timeRemaining, - float currentSpeed) { - this.mOverallTotal = overallTotal; - this.mOverallProgress = overallProgress; - this.mTimeRemaining = timeRemaining; - this.mCurrentSpeed = currentSpeed; - } - - public static final Creator CREATOR = new Creator() { - @Override - public DownloadProgressInfo createFromParcel(Parcel parcel) { - return new DownloadProgressInfo(parcel); - } - - @Override - public DownloadProgressInfo[] newArray(int i) { - return new DownloadProgressInfo[i]; - } - }; - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java deleted file mode 100644 index 2201751254..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.util.Log; - - - -/** - * This class binds the service API to your application client. It contains the IDownloaderClient proxy, - * which is used to call functions in your client as well as the Stub, which is used to call functions - * in the client implementation of IDownloaderClient. - * - *

The IPC is implemented using an Android Messenger and a service Binder. The connect method - * should be called whenever the client wants to bind to the service. It opens up a service connection - * that ends up calling the onServiceConnected client API that passes the service messenger - * in. If the client wants to be notified by the service, it is responsible for then passing its - * messenger to the service in a separate call. - * - *

Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}. - * - *

When your application first starts, you should first check whether your app's expansion files are - * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which - * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method - * returns a value indicating whether download is required or not. - * - *

If a download is required, {@link #startDownloadServiceIfRequired} begins the download through - * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link - * IStub} object that you need in order to receive calls through your {@link IDownloaderClient} - * interface. - */ -public class DownloaderClientMarshaller { - public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10; - public static final int MSG_ONDOWNLOADPROGRESS = 11; - public static final int MSG_ONSERVICECONNECTED = 12; - - public static final String PARAM_NEW_STATE = "newState"; - public static final String PARAM_PROGRESS = "progress"; - public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; - - public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED; - public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED; - public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED; - - private static class Proxy implements IDownloaderClient { - private Messenger mServiceMessenger; - - @Override - public void onDownloadStateChanged(int newState) { - Bundle params = new Bundle(1); - params.putInt(PARAM_NEW_STATE, newState); - send(MSG_ONDOWNLOADSTATE_CHANGED, params); - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - Bundle params = new Bundle(1); - params.putParcelable(PARAM_PROGRESS, progress); - send(MSG_ONDOWNLOADPROGRESS, params); - } - - private void send(int method, Bundle params) { - Message m = Message.obtain(null, method); - m.setData(params); - try { - mServiceMessenger.send(m); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public Proxy(Messenger msg) { - mServiceMessenger = msg; - } - - @Override - public void onServiceConnected(Messenger m) { - /** - * This is never called through the proxy. - */ - } - } - - private static class Stub implements IStub { - private IDownloaderClient mItf = null; - private Class mDownloaderServiceClass; - private boolean mBound; - private Messenger mServiceMessenger; - private Context mContext; - /** - * Target we publish for clients to send messages to IncomingHandler. - */ - final Messenger mMessenger = new Messenger(new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ONDOWNLOADPROGRESS: - Bundle bun = msg.getData(); - if ( null != mContext ) { - bun.setClassLoader(mContext.getClassLoader()); - DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData() - .getParcelable(PARAM_PROGRESS); - mItf.onDownloadProgress(dpi); - } - break; - case MSG_ONDOWNLOADSTATE_CHANGED: - mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE)); - break; - case MSG_ONSERVICECONNECTED: - mItf.onServiceConnected( - (Messenger) msg.getData().getParcelable(PARAM_MESSENGER)); - break; - } - } - }); - - public Stub(IDownloaderClient itf, Class downloaderService) { - mItf = itf; - mDownloaderServiceClass = downloaderService; - } - - /** - * Class for interacting with the main interface of the service. - */ - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - // This is called when the connection with the service has been - // established, giving us the object we can use to - // interact with the service. We are communicating with the - // service using a Messenger, so here we get a client-side - // representation of that from the raw IBinder object. - mServiceMessenger = new Messenger(service); - mItf.onServiceConnected( - mServiceMessenger); - } - - public void onServiceDisconnected(ComponentName className) { - // This is called when the connection with the service has been - // unexpectedly disconnected -- that is, its process crashed. - mServiceMessenger = null; - } - }; - - @Override - public void connect(Context c) { - mContext = c; - Intent bindIntent = new Intent(c, mDownloaderServiceClass); - bindIntent.putExtra(PARAM_MESSENGER, mMessenger); - if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) { - if ( Constants.LOGVV ) { - Log.d(Constants.TAG, "Service Unbound"); - } - } else { - mBound = true; - } - - } - - @Override - public void disconnect(Context c) { - if (mBound) { - c.unbindService(mConnection); - mBound = false; - } - mContext = null; - } - - @Override - public Messenger getMessenger() { - return mMessenger; - } - } - - /** - * Returns a proxy that will marshal calls to IDownloaderClient methods - * - * @param msg - * @return - */ - public static IDownloaderClient CreateProxy(Messenger msg) { - return new Proxy(msg); - } - - /** - * Returns a stub object that, when connected, will listen for marshaled - * {@link IDownloaderClient} methods and translate them into calls to the supplied - * interface. - * - * @param itf An implementation of IDownloaderClient that will be called - * when remote method calls are unmarshaled. - * @param downloaderService The class for your implementation of {@link - * impl.DownloaderService}. - * @return The {@link IStub} that allows you to connect to the service such that - * your {@link IDownloaderClient} receives status updates. - */ - public static IStub CreateStub(IDownloaderClient itf, Class downloaderService) { - return new Stub(itf, downloaderService); - } - - /** - * Starts the download if necessary. This function starts a flow that does ` - * many things. 1) Checks to see if the APK version has been checked and - * the metadata database updated 2) If the APK version does not match, - * checks the new LVL status to see if a new download is required 3) If the - * APK version does match, then checks to see if the download(s) have been - * completed 4) If the downloads have been completed, returns - * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the - * startup of an application to quickly ascertain if the application needs - * to wait to hear about any updated APK expansion files. Note that this does - * mean that the application MUST be run for the first time with a network - * connection, even if Market delivers all of the files. - * - * @param context Your application Context. - * @param notificationClient A PendingIntent to start the Activity in your application - * that shows the download progress and which will also start the application when download - * completes. - * @param serviceClass the class of your {@link imp.DownloaderService} implementation - * @return whether the service was started and the reason for starting the service. - * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link - * #DOWNLOAD_REQUIRED}. - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, - Class serviceClass) - throws NameNotFoundException { - return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, - serviceClass); - } - - /** - * This version assumes that the intent contains the pending intent as a parameter. This - * is used for responding to alarms. - *

The pending intent must be in an extra with the key {@link - * impl.DownloaderService#EXTRA_PENDING_INTENT}. - * - * @param context - * @param notificationClient - * @param serviceClass the class of the service to start - * @return - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, - Class serviceClass) - throws NameNotFoundException { - return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, - serviceClass); - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java deleted file mode 100644 index 054eaa9895..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; - - - -/** - * This class is used by the client activity to proxy requests to the Downloader - * Service. - * - * Most importantly, you must call {@link #CreateProxy} during the {@link - * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate - * an {@link IDownloaderService} object that you can then use to issue commands to the {@link - * DownloaderService} (such as to pause and resume downloads). - */ -public class DownloaderServiceMarshaller { - - public static final int MSG_REQUEST_ABORT_DOWNLOAD = - 1; - public static final int MSG_REQUEST_PAUSE_DOWNLOAD = - 2; - public static final int MSG_SET_DOWNLOAD_FLAGS = - 3; - public static final int MSG_REQUEST_CONTINUE_DOWNLOAD = - 4; - public static final int MSG_REQUEST_DOWNLOAD_STATE = - 5; - public static final int MSG_REQUEST_CLIENT_UPDATE = - 6; - - public static final String PARAMS_FLAGS = "flags"; - public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; - - private static class Proxy implements IDownloaderService { - private Messenger mMsg; - - private void send(int method, Bundle params) { - Message m = Message.obtain(null, method); - m.setData(params); - try { - mMsg.send(m); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public Proxy(Messenger msg) { - mMsg = msg; - } - - @Override - public void requestAbortDownload() { - send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle()); - } - - @Override - public void requestPauseDownload() { - send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle()); - } - - @Override - public void setDownloadFlags(int flags) { - Bundle params = new Bundle(); - params.putInt(PARAMS_FLAGS, flags); - send(MSG_SET_DOWNLOAD_FLAGS, params); - } - - @Override - public void requestContinueDownload() { - send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle()); - } - - @Override - public void requestDownloadStatus() { - send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle()); - } - - @Override - public void onClientUpdated(Messenger clientMessenger) { - Bundle bundle = new Bundle(1); - bundle.putParcelable(PARAM_MESSENGER, clientMessenger); - send(MSG_REQUEST_CLIENT_UPDATE, bundle); - } - } - - private static class Stub implements IStub { - private IDownloaderService mItf = null; - final Messenger mMessenger = new Messenger(new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_REQUEST_ABORT_DOWNLOAD: - mItf.requestAbortDownload(); - break; - case MSG_REQUEST_CONTINUE_DOWNLOAD: - mItf.requestContinueDownload(); - break; - case MSG_REQUEST_PAUSE_DOWNLOAD: - mItf.requestPauseDownload(); - break; - case MSG_SET_DOWNLOAD_FLAGS: - mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS)); - break; - case MSG_REQUEST_DOWNLOAD_STATE: - mItf.requestDownloadStatus(); - break; - case MSG_REQUEST_CLIENT_UPDATE: - mItf.onClientUpdated((Messenger) msg.getData().getParcelable( - PARAM_MESSENGER)); - break; - } - } - }); - - public Stub(IDownloaderService itf) { - mItf = itf; - } - - @Override - public Messenger getMessenger() { - return mMessenger; - } - - @Override - public void connect(Context c) { - - } - - @Override - public void disconnect(Context c) { - - } - } - - /** - * Returns a proxy that will marshall calls to IDownloaderService methods - * - * @param ctx - * @return - */ - public static IDownloaderService CreateProxy(Messenger msg) { - return new Proxy(msg); - } - - /** - * Returns a stub object that, when connected, will listen for marshalled - * IDownloaderService methods and translate them into calls to the supplied - * interface. - * - * @param itf An implementation of IDownloaderService that will be called - * when remote method calls are unmarshalled. - * @return - */ - public static IStub CreateStub(IDownloaderService itf) { - return new Stub(itf); - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java deleted file mode 100644 index 1e84e54a0f..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/Helpers.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.android.vending.expansion.downloader.R; - -import android.content.Context; -import android.os.Environment; -import android.os.StatFs; -import android.os.SystemClock; -import android.util.Log; - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Random; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Some helper functions for the download manager - */ -public class Helpers { - - public static Random sRandom = new Random(SystemClock.uptimeMillis()); - - /** Regex used to parse content-disposition headers */ - private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern - .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); - - private Helpers() { - } - - /* - * Parse the Content-Disposition HTTP Header. The format of the header is - * defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This - * header provides a filename for content that is going to be downloaded to - * the file system. We only support the attachment type. - */ - static String parseContentDisposition(String contentDisposition) { - try { - Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); - if (m.find()) { - return m.group(1); - } - } catch (IllegalStateException ex) { - // This function is defined as returning null when it can't parse - // the header - } - return null; - } - - /** - * @return the root of the filesystem containing the given path - */ - public static File getFilesystemRoot(String path) { - File cache = Environment.getDownloadCacheDirectory(); - if (path.startsWith(cache.getPath())) { - return cache; - } - File external = Environment.getExternalStorageDirectory(); - if (path.startsWith(external.getPath())) { - return external; - } - throw new IllegalArgumentException( - "Cannot determine filesystem root for " + path); - } - - public static boolean isExternalMediaMounted() { - if (!Environment.getExternalStorageState().equals( - Environment.MEDIA_MOUNTED)) { - // No SD card found. - if ( Constants.LOGVV ) { - Log.d(Constants.TAG, "no external storage"); - } - return false; - } - return true; - } - - /** - * @return the number of bytes available on the filesystem rooted at the - * given File - */ - public static long getAvailableBytes(File root) { - StatFs stat = new StatFs(root.getPath()); - // put a bit of margin (in case creating the file grows the system by a - // few blocks) - long availableBlocks = (long) stat.getAvailableBlocks() - 4; - return stat.getBlockSize() * availableBlocks; - } - - /** - * Checks whether the filename looks legitimate - */ - public static boolean isFilenameValid(String filename) { - filename = filename.replaceFirst("/+", "/"); // normalize leading - // slashes - return filename.startsWith(Environment.getDownloadCacheDirectory().toString()) - || filename.startsWith(Environment.getExternalStorageDirectory().toString()); - } - - /* - * Delete the given file from device - */ - /* package */static void deleteFile(String path) { - try { - File file = new File(path); - file.delete(); - } catch (Exception e) { - Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e); - } - } - - /** - * Showing progress in MB here. It would be nice to choose the unit (KB, MB, - * GB) based on total file size, but given what we know about the expected - * ranges of file sizes for APK expansion files, it's probably not necessary. - * - * @param overallProgress - * @param overallTotal - * @return - */ - - static public String getDownloadProgressString(long overallProgress, long overallTotal) { - if (overallTotal == 0) { - if ( Constants.LOGVV ) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return String.format("%.2f", - (float) overallProgress / (1024.0f * 1024.0f)) - + "MB /" + - String.format("%.2f", (float) overallTotal / - (1024.0f * 1024.0f)) + "MB"; - } - - /** - * Adds a percentile to getDownloadProgressString. - * - * @param overallProgress - * @param overallTotal - * @return - */ - static public String getDownloadProgressStringNotification(long overallProgress, - long overallTotal) { - if (overallTotal == 0) { - if ( Constants.LOGVV ) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return getDownloadProgressString(overallProgress, overallTotal) + " (" + - getDownloadProgressPercent(overallProgress, overallTotal) + ")"; - } - - public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { - if (overallTotal == 0) { - if ( Constants.LOGVV ) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return Long.toString(overallProgress * 100 / overallTotal) + "%"; - } - - public static String getSpeedString(float bytesPerMillisecond) { - return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); - } - - public static String getTimeRemaining(long durationInMilliseconds) { - SimpleDateFormat sdf; - if (durationInMilliseconds > 1000 * 60 * 60) { - sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); - } else { - sdf = new SimpleDateFormat("mm:ss", Locale.getDefault()); - } - return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset())); - } - - /** - * Returns the file name (without full path) for an Expansion APK file from - * the given context. - * - * @param c the context - * @param mainFile true for main file, false for patch file - * @param versionCode the version of the file - * @return String the file name of the expansion file - */ - public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) { - return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb"; - } - - /** - * Returns the filename (where the file should be saved) from info about a - * download - */ - static public String generateSaveFileName(Context c, String fileName) { - String path = getSaveFilePath(c) - + File.separator + fileName; - return path; - } - - static public String getSaveFilePath(Context c) { - File root = Environment.getExternalStorageDirectory(); - String path = root.toString() + Constants.EXP_PATH + c.getPackageName(); - return path; - } - - /** - * Helper function to ascertain the existence of a file and return - * true/false appropriately - * - * @param c the app/activity/service context - * @param fileName the name (sans path) of the file to query - * @param fileSize the size that the file must match - * @param deleteFileOnMismatch if the file sizes do not match, delete the - * file - * @return true if it does exist, false otherwise - */ - static public boolean doesFileExist(Context c, String fileName, long fileSize, - boolean deleteFileOnMismatch) { - // the file may have been delivered by Market --- let's make sure - // it's the size we expect - File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); - if (fileForNewFile.exists()) { - if (fileForNewFile.length() == fileSize) { - return true; - } - if (deleteFileOnMismatch) { - // delete the file --- we won't be able to resume - // because we cannot confirm the integrity of the file - fileForNewFile.delete(); - } - } - return false; - } - - /** - * Converts download states that are returned by the {@link - * IDownloaderClient#onDownloadStateChanged} callback into usable strings. - * This is useful if using the state strings built into the library to display user messages. - * @param state One of the STATE_* constants from {@link IDownloaderClient}. - * @return string resource ID for the corresponding string. - */ - static public int getDownloaderStringResourceIDFromState(int state) { - switch (state) { - case IDownloaderClient.STATE_IDLE: - return R.string.state_idle; - case IDownloaderClient.STATE_FETCHING_URL: - return R.string.state_fetching_url; - case IDownloaderClient.STATE_CONNECTING: - return R.string.state_connecting; - case IDownloaderClient.STATE_DOWNLOADING: - return R.string.state_downloading; - case IDownloaderClient.STATE_COMPLETED: - return R.string.state_completed; - case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE: - return R.string.state_paused_network_unavailable; - case IDownloaderClient.STATE_PAUSED_BY_REQUEST: - return R.string.state_paused_by_request; - case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: - return R.string.state_paused_wifi_disabled; - case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: - return R.string.state_paused_wifi_unavailable; - case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED: - return R.string.state_paused_wifi_disabled; - case IDownloaderClient.STATE_PAUSED_NEED_WIFI: - return R.string.state_paused_wifi_unavailable; - case IDownloaderClient.STATE_PAUSED_ROAMING: - return R.string.state_paused_roaming; - case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE: - return R.string.state_paused_network_setup_failure; - case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: - return R.string.state_paused_sdcard_unavailable; - case IDownloaderClient.STATE_FAILED_UNLICENSED: - return R.string.state_failed_unlicensed; - case IDownloaderClient.STATE_FAILED_FETCHING_URL: - return R.string.state_failed_fetching_url; - case IDownloaderClient.STATE_FAILED_SDCARD_FULL: - return R.string.state_failed_sdcard_full; - case IDownloaderClient.STATE_FAILED_CANCELED: - return R.string.state_failed_cancelled; - default: - return R.string.state_unknown; - } - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java deleted file mode 100644 index b8511a62a0..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.os.Messenger; - -/** - * This interface should be implemented by the client activity for the - * downloader. It is used to pass status from the service to the client. - */ -public interface IDownloaderClient { - static final int STATE_IDLE = 1; - static final int STATE_FETCHING_URL = 2; - static final int STATE_CONNECTING = 3; - static final int STATE_DOWNLOADING = 4; - static final int STATE_COMPLETED = 5; - - static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6; - static final int STATE_PAUSED_BY_REQUEST = 7; - - /** - * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and - * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and - * cellular permission will restart the service. Wi-Fi disabled means that - * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the - * other case Wi-Fi is enabled but not available. - */ - static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8; - static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9; - - /** - * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that - * Wi-Fi is unavailable and cellular permission will NOT restart the - * service. Wi-Fi disabled means that the Wi-Fi manager is returning that - * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not - * available. - *

- * The service does not return these values. We recommend that app - * developers with very large payloads do not allow these payloads to be - * downloaded over cellular connections. - */ - static final int STATE_PAUSED_WIFI_DISABLED = 10; - static final int STATE_PAUSED_NEED_WIFI = 11; - - static final int STATE_PAUSED_ROAMING = 12; - - /** - * Scary case. We were on a network that redirected us to another website - * that delivered us the wrong file. - */ - static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13; - - static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; - - static final int STATE_FAILED_UNLICENSED = 15; - static final int STATE_FAILED_FETCHING_URL = 16; - static final int STATE_FAILED_SDCARD_FULL = 17; - static final int STATE_FAILED_CANCELED = 18; - - static final int STATE_FAILED = 19; - - /** - * Called internally by the stub when the service is bound to the client. - *

- * Critical implementation detail. In onServiceConnected we create the - * remote service and marshaler. This is how we pass the client information - * back to the service so the client can be properly notified of changes. We - * must do this every time we reconnect to the service. - *

- * That is, when you receive this callback, you should call - * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member - * instance of {@link IDownloaderService}, then call - * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved - * from your {@link IStub} proxy object. - * - * @param m the service Messenger. This Messenger is used to call the - * service API from the client. - */ - void onServiceConnected(Messenger m); - - /** - * Called when the download state changes. Depending on the state, there may - * be user requests. The service is free to change the download state in the - * middle of a user request, so the client should be able to handle this. - *

- * The Downloader Library includes a collection of string resources that - * correspond to each of the states, which you can use to provide users a - * useful message based on the state provided in this callback. To fetch the - * appropriate string for a state, call - * {@link Helpers#getDownloaderStringResourceIDFromState}. - *

- * What this means to the developer: The application has gotten a message - * that the download has paused due to lack of WiFi. The developer should - * then show UI asking the user if they want to enable downloading over - * cellular connections with appropriate warnings. If the application - * suddenly starts downloading, the application should revert to showing the - * progress again, rather than leaving up the download over cellular UI up. - * - * @param newState one of the STATE_* values defined in IDownloaderClient - */ - void onDownloadStateChanged(int newState); - - /** - * Shows the download progress. This is intended to be used to fill out a - * client UI. This progress should only be shown in a few states such as - * STATE_DOWNLOADING. - * - * @param progress the DownloadProgressInfo object containing the current - * progress of all downloads. - */ - void onDownloadProgress(DownloadProgressInfo progress); -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java deleted file mode 100644 index 4789afe19c..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IDownloaderService.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; -import android.os.Messenger; - -/** - * This interface is implemented by the DownloaderService and by the - * DownloaderServiceMarshaller. It contains functions to control the service. - * When a client binds to the service, it must call the onClientUpdated - * function. - *

- * You can acquire a proxy that implements this interface for your service by - * calling {@link DownloaderServiceMarshaller#CreateProxy} during the - * {@link IDownloaderClient#onServiceConnected} callback. At which point, you - * should immediately call {@link #onClientUpdated}. - */ -public interface IDownloaderService { - /** - * Set this flag in response to the - * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then - * call RequestContinueDownload to resume a download - */ - public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1; - - /** - * Request that the service abort the current download. The service should - * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}. - */ - void requestAbortDownload(); - - /** - * Request that the service pause the current download. The service should - * respond by changing the state to - * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. - */ - void requestPauseDownload(); - - /** - * Request that the service continue a paused download, when in any paused - * or failed state, including - * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. - */ - void requestContinueDownload(); - - /** - * Set the flags for this download (e.g. - * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). - * - * @param flags - */ - void setDownloadFlags(int flags); - - /** - * Requests that the download status be sent to the client. - */ - void requestDownloadStatus(); - - /** - * Call this when you get {@link - * IDownloaderClient.onServiceConnected(Messenger m)} from the - * DownloaderClient to register the client with the service. It will - * automatically send the current status to the client. - * - * @param clientMessenger - */ - void onClientUpdated(Messenger clientMessenger); -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java deleted file mode 100644 index d5bc3a843e..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/IStub.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.content.Context; -import android.os.Messenger; - -/** - * This is the interface that is used to connect/disconnect from the downloader - * service. - *

- * You should get a proxy object that implements this interface by calling - * {@link DownloaderClientMarshaller#CreateStub} in your activity when the - * downloader service starts. Then, call {@link #connect} during your activity's - * onResume() and call {@link #disconnect} during onStop(). - *

- * Then during the {@link IDownloaderClient#onServiceConnected} callback, you - * should call {@link #getMessenger} to pass the stub's Messenger object to - * {@link IDownloaderService#onClientUpdated}. - */ -public interface IStub { - Messenger getMessenger(); - - void connect(Context c); - - void disconnect(Context c); -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java deleted file mode 100644 index 12edd97ab2..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/SystemFacade.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.telephony.TelephonyManager; -import android.util.Log; - -/** - * Contains useful helper functions, typically tied to the application context. - */ -class SystemFacade { - private Context mContext; - private NotificationManager mNotificationManager; - - public SystemFacade(Context context) { - mContext = context; - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - } - - public long currentTimeMillis() { - return System.currentTimeMillis(); - } - - public Integer getActiveNetworkType() { - ConnectivityManager connectivity = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - return null; - } - - NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); - if (activeInfo == null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is not available"); - } - return null; - } - return activeInfo.getType(); - } - - public boolean isNetworkRoaming() { - ConnectivityManager connectivity = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - return false; - } - - NetworkInfo info = connectivity.getActiveNetworkInfo(); - boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); - TelephonyManager tm = (TelephonyManager) mContext - .getSystemService(Context.TELEPHONY_SERVICE); - if (null == tm) { - Log.w(Constants.TAG, "couldn't get telephony manager"); - return false; - } - boolean isRoaming = isMobile && tm.isNetworkRoaming(); - if (Constants.LOGVV && isRoaming) { - Log.v(Constants.TAG, "network is roaming"); - } - return isRoaming; - } - - public Long getMaxBytesOverMobile() { - return (long) Integer.MAX_VALUE; - } - - public Long getRecommendedMaxBytesOverMobile() { - return 2097152L; - } - - public void sendBroadcast(Intent intent) { - mContext.sendBroadcast(intent); - } - - public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException { - return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid; - } - - public void postNotification(long id, Notification notification) { - /** - * TODO: The system notification manager takes ints, not longs, as IDs, - * but the download manager uses IDs take straight from the database, - * which are longs. This will have to be dealt with at some point. - */ - mNotificationManager.notify((int) id, notification); - } - - public void cancelNotification(long id) { - mNotificationManager.cancel((int) id); - } - - public void cancelAllNotifications() { - mNotificationManager.cancelAll(); - } - - public void startThread(Thread thread) { - thread.start(); - } -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java deleted file mode 100644 index 4667acce67..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * This is a port of AndroidHttpClient to pre-Froyo devices, that takes advantage of - * the SSLSessionCache added Froyo devices using reflection. - */ - -package com.google.android.vending.expansion.downloader.impl; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpException; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.params.HttpClientParams; -import org.apache.http.client.protocol.ClientContext; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.scheme.SocketFactory; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.entity.AbstractHttpEntity; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.client.RequestWrapper; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.http.protocol.BasicHttpProcessor; -import org.apache.http.protocol.HttpContext; - -import android.content.ContentResolver; -import android.content.Context; -import android.net.SSLCertificateSocketFactory; -import android.os.Looper; -import android.util.Log; - -/** - * Subclass of the Apache {@link DefaultHttpClient} that is configured with - * reasonable default settings and registered schemes for Android, and - * also lets the user add {@link HttpRequestInterceptor} classes. - * Don't create this directly, use the {@link #newInstance} factory method. - * - *

This client processes cookies but does not retain them by default. - * To retain cookies, simply add a cookie store to the HttpContext:

- * - *
context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
- */ -public final class AndroidHttpClient implements HttpClient { - - static Class sSslSessionCacheClass; - static { - // if we are on Froyo+ devices, we can take advantage of the SSLSessionCache - try { - sSslSessionCacheClass = Class.forName("android.net.SSLSessionCache"); - } catch (Exception e) { - - } - } - - // Gzip of data shorter than this probably won't be worthwhile - public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256; - - // Default connection and socket timeout of 60 seconds. Tweak to taste. - private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000; - - private static final String TAG = "AndroidHttpClient"; - - - /** Interceptor throws an exception if the executing thread is blocked */ - private static final HttpRequestInterceptor sThreadCheckInterceptor = - new HttpRequestInterceptor() { - public void process(HttpRequest request, HttpContext context) { - // Prevent the HttpRequest from being sent on the main thread - if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) { - throw new RuntimeException("This thread forbids HTTP requests"); - } - } - }; - - /** - * Create a new HttpClient with reasonable defaults (which you can update). - * - * @param userAgent to report in your HTTP requests - * @param context to use for caching SSL sessions (may be null for no caching) - * @return AndroidHttpClient for you to use for all your requests. - */ - public static AndroidHttpClient newInstance(String userAgent, Context context) { - HttpParams params = new BasicHttpParams(); - - // Turn off stale checking. Our connections break all the time anyway, - // and it's not worth it to pay the penalty of checking every time. - HttpConnectionParams.setStaleCheckingEnabled(params, false); - - HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT); - HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT); - HttpConnectionParams.setSocketBufferSize(params, 8192); - - // Don't handle redirects -- return them to the caller. Our code - // often wants to re-POST after a redirect, which we must do ourselves. - HttpClientParams.setRedirecting(params, false); - - Object sessionCache = null; - // Use a session cache for SSL sockets -- Froyo only - if ( null != context && null != sSslSessionCacheClass ) { - Constructor ct; - try { - ct = sSslSessionCacheClass.getConstructor(Context.class); - sessionCache = ct.newInstance(context); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InstantiationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - // Set the specified user agent and register standard protocols. - HttpProtocolParams.setUserAgent(params, userAgent); - SchemeRegistry schemeRegistry = new SchemeRegistry(); - schemeRegistry.register(new Scheme("http", - PlainSocketFactory.getSocketFactory(), 80)); - SocketFactory sslCertificateSocketFactory = null; - if ( null != sessionCache ) { - Method getHttpSocketFactoryMethod; - try { - getHttpSocketFactoryMethod = SSLCertificateSocketFactory.class.getDeclaredMethod("getHttpSocketFactory",Integer.TYPE, sSslSessionCacheClass); - sslCertificateSocketFactory = (SocketFactory)getHttpSocketFactoryMethod.invoke(null, SOCKET_OPERATION_TIMEOUT, sessionCache); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - if ( null == sslCertificateSocketFactory ) { - sslCertificateSocketFactory = SSLSocketFactory.getSocketFactory(); - } - schemeRegistry.register(new Scheme("https", - sslCertificateSocketFactory, 443)); - - ClientConnectionManager manager = - new ThreadSafeClientConnManager(params, schemeRegistry); - - // We use a factory method to modify superclass initialization - // parameters without the funny call-a-static-method dance. - return new AndroidHttpClient(manager, params); - } - - /** - * Create a new HttpClient with reasonable defaults (which you can update). - * @param userAgent to report in your HTTP requests. - * @return AndroidHttpClient for you to use for all your requests. - */ - public static AndroidHttpClient newInstance(String userAgent) { - return newInstance(userAgent, null /* session cache */); - } - - private final HttpClient delegate; - - private RuntimeException mLeakedException = new IllegalStateException( - "AndroidHttpClient created and never closed"); - - private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) { - this.delegate = new DefaultHttpClient(ccm, params) { - @Override - protected BasicHttpProcessor createHttpProcessor() { - // Add interceptor to prevent making requests from main thread. - BasicHttpProcessor processor = super.createHttpProcessor(); - processor.addRequestInterceptor(sThreadCheckInterceptor); - processor.addRequestInterceptor(new CurlLogger()); - - return processor; - } - - @Override - protected HttpContext createHttpContext() { - // Same as DefaultHttpClient.createHttpContext() minus the - // cookie store. - HttpContext context = new BasicHttpContext(); - context.setAttribute( - ClientContext.AUTHSCHEME_REGISTRY, - getAuthSchemes()); - context.setAttribute( - ClientContext.COOKIESPEC_REGISTRY, - getCookieSpecs()); - context.setAttribute( - ClientContext.CREDS_PROVIDER, - getCredentialsProvider()); - return context; - } - }; - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - if (mLeakedException != null) { - Log.e(TAG, "Leak found", mLeakedException); - mLeakedException = null; - } - } - - /** - * Modifies a request to indicate to the server that we would like a - * gzipped response. (Uses the "Accept-Encoding" HTTP header.) - * @param request the request to modify - * @see #getUngzippedContent - */ - public static void modifyRequestToAcceptGzipResponse(HttpRequest request) { - request.addHeader("Accept-Encoding", "gzip"); - } - - /** - * Gets the input stream from a response entity. If the entity is gzipped - * then this will get a stream over the uncompressed data. - * - * @param entity the entity whose content should be read - * @return the input stream to read from - * @throws IOException - */ - public static InputStream getUngzippedContent(HttpEntity entity) - throws IOException { - InputStream responseStream = entity.getContent(); - if (responseStream == null) return responseStream; - Header header = entity.getContentEncoding(); - if (header == null) return responseStream; - String contentEncoding = header.getValue(); - if (contentEncoding == null) return responseStream; - if (contentEncoding.contains("gzip")) responseStream - = new GZIPInputStream(responseStream); - return responseStream; - } - - /** - * Release resources associated with this client. You must call this, - * or significant resources (sockets and memory) may be leaked. - */ - public void close() { - if (mLeakedException != null) { - getConnectionManager().shutdown(); - mLeakedException = null; - } - } - - public HttpParams getParams() { - return delegate.getParams(); - } - - public ClientConnectionManager getConnectionManager() { - return delegate.getConnectionManager(); - } - - public HttpResponse execute(HttpUriRequest request) throws IOException { - return delegate.execute(request); - } - - public HttpResponse execute(HttpUriRequest request, HttpContext context) - throws IOException { - return delegate.execute(request, context); - } - - public HttpResponse execute(HttpHost target, HttpRequest request) - throws IOException { - return delegate.execute(target, request); - } - - public HttpResponse execute(HttpHost target, HttpRequest request, - HttpContext context) throws IOException { - return delegate.execute(target, request, context); - } - - public T execute(HttpUriRequest request, - ResponseHandler responseHandler) - throws IOException, ClientProtocolException { - return delegate.execute(request, responseHandler); - } - - public T execute(HttpUriRequest request, - ResponseHandler responseHandler, HttpContext context) - throws IOException, ClientProtocolException { - return delegate.execute(request, responseHandler, context); - } - - public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler) throws IOException, - ClientProtocolException { - return delegate.execute(target, request, responseHandler); - } - - public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler, HttpContext context) - throws IOException, ClientProtocolException { - return delegate.execute(target, request, responseHandler, context); - } - - /** - * Compress data to send to server. - * Creates a Http Entity holding the gzipped data. - * The data will not be compressed if it is too short. - * @param data The bytes to compress - * @return Entity holding the data - */ - public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver) - throws IOException { - AbstractHttpEntity entity; - if (data.length < getMinGzipSize(resolver)) { - entity = new ByteArrayEntity(data); - } else { - ByteArrayOutputStream arr = new ByteArrayOutputStream(); - OutputStream zipper = new GZIPOutputStream(arr); - zipper.write(data); - zipper.close(); - entity = new ByteArrayEntity(arr.toByteArray()); - entity.setContentEncoding("gzip"); - } - return entity; - } - - /** - * Retrieves the minimum size for compressing data. - * Shorter data will not be compressed. - */ - public static long getMinGzipSize(ContentResolver resolver) { - return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant. - } - - /* cURL logging support. */ - - /** - * Logging tag and level. - */ - private static class LoggingConfiguration { - - private final String tag; - private final int level; - - private LoggingConfiguration(String tag, int level) { - this.tag = tag; - this.level = level; - } - - /** - * Returns true if logging is turned on for this configuration. - */ - private boolean isLoggable() { - return Log.isLoggable(tag, level); - } - - /** - * Prints a message using this configuration. - */ - private void println(String message) { - Log.println(level, tag, message); - } - } - - /** cURL logging configuration. */ - private volatile LoggingConfiguration curlConfiguration; - - /** - * Enables cURL request logging for this client. - * - * @param name to log messages with - * @param level at which to log messages (see {@link android.util.Log}) - */ - public void enableCurlLogging(String name, int level) { - if (name == null) { - throw new NullPointerException("name"); - } - if (level < Log.VERBOSE || level > Log.ASSERT) { - throw new IllegalArgumentException("Level is out of range [" - + Log.VERBOSE + ".." + Log.ASSERT + "]"); - } - - curlConfiguration = new LoggingConfiguration(name, level); - } - - /** - * Disables cURL logging for this client. - */ - public void disableCurlLogging() { - curlConfiguration = null; - } - - /** - * Logs cURL commands equivalent to requests. - */ - private class CurlLogger implements HttpRequestInterceptor { - public void process(HttpRequest request, HttpContext context) - throws HttpException, IOException { - LoggingConfiguration configuration = curlConfiguration; - if (configuration != null - && configuration.isLoggable() - && request instanceof HttpUriRequest) { - // Never print auth token -- we used to check ro.secure=0 to - // enable that, but can't do that in unbundled code. - configuration.println(toCurl((HttpUriRequest) request, false)); - } - } - } - - /** - * Generates a cURL command equivalent to the given request. - */ - private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException { - StringBuilder builder = new StringBuilder(); - - builder.append("curl "); - - for (Header header: request.getAllHeaders()) { - if (!logAuthToken - && (header.getName().equals("Authorization") || - header.getName().equals("Cookie"))) { - continue; - } - builder.append("--header \""); - builder.append(header.toString().trim()); - builder.append("\" "); - } - - URI uri = request.getURI(); - - // If this is a wrapped request, use the URI from the original - // request instead. getURI() on the wrapper seems to return a - // relative URI. We want an absolute URI. - if (request instanceof RequestWrapper) { - HttpRequest original = ((RequestWrapper) request).getOriginal(); - if (original instanceof HttpUriRequest) { - uri = ((HttpUriRequest) original).getURI(); - } - } - - builder.append("\""); - builder.append(uri); - builder.append("\""); - - if (request instanceof HttpEntityEnclosingRequest) { - HttpEntityEnclosingRequest entityRequest = - (HttpEntityEnclosingRequest) request; - HttpEntity entity = entityRequest.getEntity(); - if (entity != null && entity.isRepeatable()) { - if (entity.getContentLength() < 1024) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - entity.writeTo(stream); - String entityString = stream.toString(); - - // TODO: Check the content type, too. - builder.append(" --data-ascii \"") - .append(entityString) - .append("\""); - } else { - builder.append(" [TOO MUCH DATA TO INCLUDE]"); - } - } - } - - return builder.toString(); - } - - /** - * Returns the date of the given HTTP date string. This method can identify - * and parse the date formats emitted by common HTTP servers, such as - * RFC 822, - * RFC 850, - * RFC 1036, - * RFC 1123 and - * ANSI - * C's asctime(). - * - * @return the number of milliseconds since Jan. 1, 1970, midnight GMT. - * @throws IllegalArgumentException if {@code dateString} is not a date or - * of an unsupported format. - */ - public static long parseDate(String dateString) { - return HttpDateTime.parse(dateString); - } -} \ No newline at end of file diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java deleted file mode 100755 index b77af7e085..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.app.Service; -import android.content.Intent; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.util.Log; - -/** - * This service differs from IntentService in a few minor ways/ It will not - * auto-stop itself after the intent is handled unless the target returns "true" - * in should stop. Since the goal of this service is to handle a single kind of - * intent, it does not queue up batches of intents of the same type. - */ -public abstract class CustomIntentService extends Service { - private String mName; - private boolean mRedelivery; - private volatile ServiceHandler mServiceHandler; - private volatile Looper mServiceLooper; - private static final String LOG_TAG = "CancellableIntentService"; - private static final int WHAT_MESSAGE = -10; - - public CustomIntentService(String paramString) { - this.mName = paramString; - } - - @Override - public IBinder onBind(Intent paramIntent) { - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - HandlerThread localHandlerThread = new HandlerThread("IntentService[" - + this.mName + "]"); - localHandlerThread.start(); - this.mServiceLooper = localHandlerThread.getLooper(); - this.mServiceHandler = new ServiceHandler(this.mServiceLooper); - } - - @Override - public void onDestroy() { - Thread localThread = this.mServiceLooper.getThread(); - if ((localThread != null) && (localThread.isAlive())) { - localThread.interrupt(); - } - this.mServiceLooper.quit(); - Log.d(LOG_TAG, "onDestroy"); - } - - protected abstract void onHandleIntent(Intent paramIntent); - - protected abstract boolean shouldStop(); - - @Override - public void onStart(Intent paramIntent, int startId) { - if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) { - Message localMessage = this.mServiceHandler.obtainMessage(); - localMessage.arg1 = startId; - localMessage.obj = paramIntent; - localMessage.what = WHAT_MESSAGE; - this.mServiceHandler.sendMessage(localMessage); - } - } - - @Override - public int onStartCommand(Intent paramIntent, int flags, int startId) { - onStart(paramIntent, startId); - return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; - } - - public void setIntentRedelivery(boolean enabled) { - this.mRedelivery = enabled; - } - - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message paramMessage) { - CustomIntentService.this - .onHandleIntent((Intent) paramMessage.obj); - if (shouldStop()) { - Log.d(LOG_TAG, "stopSelf"); - CustomIntentService.this.stopSelf(paramMessage.arg1); - Log.d(LOG_TAG, "afterStopSelf"); - } - } - } -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java deleted file mode 100644 index 9a0ca02122..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -/** - * Uses the class-loader model to utilize the updated notification builders in - * Honeycomb while maintaining a compatible version for older devices. - */ -public class CustomNotificationFactory { - static public DownloadNotification.ICustomNotification createCustomNotification() { - if (android.os.Build.VERSION.SDK_INT > 13) - return new V14CustomNotification(); - else - return new V3CustomNotification(); - } -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java deleted file mode 100644 index 45111b16a3..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.Helpers; - -import android.util.Log; - -/** - * Representation of information about an individual download from the database. - */ -public class DownloadInfo { - public String mUri; - public final int mIndex; - public final String mFileName; - public String mETag; - public long mTotalBytes; - public long mCurrentBytes; - public long mLastMod; - public int mStatus; - public int mControl; - public int mNumFailed; - public int mRetryAfter; - public int mRedirectCount; - - boolean mInitialized; - - public int mFuzz; - - public DownloadInfo(int index, String fileName, String pkg) { - mFuzz = Helpers.sRandom.nextInt(1001); - mFileName = fileName; - mIndex = index; - } - - public void resetDownload() { - mCurrentBytes = 0; - mETag = ""; - mLastMod = 0; - mStatus = 0; - mControl = 0; - mNumFailed = 0; - mRetryAfter = 0; - mRedirectCount = 0; - } - - /** - * Returns the time when a download should be restarted. - */ - public long restartTime(long now) { - if (mNumFailed == 0) { - return now; - } - if (mRetryAfter > 0) { - return mLastMod + mRetryAfter; - } - return mLastMod + - Constants.RETRY_FIRST_DELAY * - (1000 + mFuzz) * (1 << (mNumFailed - 1)); - } - - public void logVerboseInfo() { - Log.v(Constants.TAG, "Service adding new entry"); - Log.v(Constants.TAG, "FILENAME: " + mFileName); - Log.v(Constants.TAG, "URI : " + mUri); - Log.v(Constants.TAG, "FILENAME: " + mFileName); - Log.v(Constants.TAG, "CONTROL : " + mControl); - Log.v(Constants.TAG, "STATUS : " + mStatus); - Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); - Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); - Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); - Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); - Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); - Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); - Log.v(Constants.TAG, "ETAG : " + mETag); - } -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java deleted file mode 100644 index eef205d7b7..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.android.vending.expansion.downloader.R; -import com.google.android.vending.expansion.downloader.DownloadProgressInfo; -import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.os.Messenger; - -/** - * This class handles displaying the notification associated with the download - * queue going on in the download manager. It handles multiple status types; - * Some require user interaction and some do not. Some of the user interactions - * may be transient. (for example: the user is queried to continue the download - * on 3G when it started on WiFi, but then the phone locks onto WiFi again so - * the prompt automatically goes away) - *

- * The application interface for the downloader also needs to understand and - * handle these transient states. - */ -public class DownloadNotification implements IDownloaderClient { - - private int mState; - private final Context mContext; - private final NotificationManager mNotificationManager; - private String mCurrentTitle; - - private IDownloaderClient mClientProxy; - final ICustomNotification mCustomNotification; - private Notification mNotification; - private Notification mCurrentNotification; - private CharSequence mLabel; - private String mCurrentText; - private PendingIntent mContentIntent; - private DownloadProgressInfo mProgressInfo; - - static final String LOGTAG = "DownloadNotification"; - static final int NOTIFICATION_ID = LOGTAG.hashCode(); - - public PendingIntent getClientIntent() { - return mContentIntent; - } - - public void setClientIntent(PendingIntent mClientIntent) { - this.mContentIntent = mClientIntent; - } - - public void resendState() { - if (null != mClientProxy) { - mClientProxy.onDownloadStateChanged(mState); - } - } - - @Override - public void onDownloadStateChanged(int newState) { - if (null != mClientProxy) { - mClientProxy.onDownloadStateChanged(newState); - } - if (newState != mState) { - mState = newState; - if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) { - return; - } - int stringDownloadID; - int iconResource; - boolean ongoingEvent; - - // get the new title string and paused text - switch (newState) { - case 0: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = R.string.state_unknown; - ongoingEvent = false; - break; - - case IDownloaderClient.STATE_DOWNLOADING: - iconResource = android.R.drawable.stat_sys_download; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - - case IDownloaderClient.STATE_FETCHING_URL: - case IDownloaderClient.STATE_CONNECTING: - iconResource = android.R.drawable.stat_sys_download_done; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - - case IDownloaderClient.STATE_COMPLETED: - case IDownloaderClient.STATE_PAUSED_BY_REQUEST: - iconResource = android.R.drawable.stat_sys_download_done; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = false; - break; - - case IDownloaderClient.STATE_FAILED: - case IDownloaderClient.STATE_FAILED_CANCELED: - case IDownloaderClient.STATE_FAILED_FETCHING_URL: - case IDownloaderClient.STATE_FAILED_SDCARD_FULL: - case IDownloaderClient.STATE_FAILED_UNLICENSED: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = false; - break; - - default: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - } - mCurrentText = mContext.getString(stringDownloadID); - mCurrentTitle = mLabel.toString(); - mCurrentNotification.tickerText = mLabel + ": " + mCurrentText; - mCurrentNotification.icon = iconResource; - mCurrentNotification.setLatestEventInfo(mContext, mCurrentTitle, mCurrentText, - mContentIntent); - if (ongoingEvent) { - mCurrentNotification.flags |= Notification.FLAG_ONGOING_EVENT; - } else { - mCurrentNotification.flags &= ~Notification.FLAG_ONGOING_EVENT; - mCurrentNotification.flags |= Notification.FLAG_AUTO_CANCEL; - } - mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification); - } - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - mProgressInfo = progress; - if (null != mClientProxy) { - mClientProxy.onDownloadProgress(progress); - } - if (progress.mOverallTotal <= 0) { - // we just show the text - mNotification.tickerText = mCurrentTitle; - mNotification.icon = android.R.drawable.stat_sys_download; - mNotification.setLatestEventInfo(mContext, mLabel, mCurrentText, mContentIntent); - mCurrentNotification = mNotification; - } else { - mCustomNotification.setCurrentBytes(progress.mOverallProgress); - mCustomNotification.setTotalBytes(progress.mOverallTotal); - mCustomNotification.setIcon(android.R.drawable.stat_sys_download); - mCustomNotification.setPendingIntent(mContentIntent); - mCustomNotification.setTicker(mLabel + ": " + mCurrentText); - mCustomNotification.setTitle(mLabel); - mCustomNotification.setTimeRemaining(progress.mTimeRemaining); - mCurrentNotification = mCustomNotification.updateNotification(mContext); - } - mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification); - } - - public interface ICustomNotification { - void setTitle(CharSequence title); - - void setTicker(CharSequence ticker); - - void setPendingIntent(PendingIntent mContentIntent); - - void setTotalBytes(long totalBytes); - - void setCurrentBytes(long currentBytes); - - void setIcon(int iconResource); - - void setTimeRemaining(long timeRemaining); - - Notification updateNotification(Context c); - } - - /** - * Called in response to onClientUpdated. Creates a new proxy and notifies - * it of the current state. - * - * @param msg the client Messenger to notify - */ - public void setMessenger(Messenger msg) { - mClientProxy = DownloaderClientMarshaller.CreateProxy(msg); - if (null != mProgressInfo) { - mClientProxy.onDownloadProgress(mProgressInfo); - } - if (mState != -1) { - mClientProxy.onDownloadStateChanged(mState); - } - } - - /** - * Constructor - * - * @param ctx The context to use to obtain access to the Notification - * Service - */ - DownloadNotification(Context ctx, CharSequence applicationLabel) { - mState = -1; - mContext = ctx; - mLabel = applicationLabel; - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - mCustomNotification = CustomNotificationFactory - .createCustomNotification(); - mNotification = new Notification(); - mCurrentNotification = mNotification; - - } - - @Override - public void onServiceConnected(Messenger m) { - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java deleted file mode 100644 index 056d1eca0b..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java +++ /dev/null @@ -1,963 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; - -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.conn.params.ConnRouteParams; - -import android.content.Context; -import android.net.Proxy; -import android.os.PowerManager; -import android.os.Process; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.SyncFailedException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Locale; - -/** - * Runs an actual download - */ -public class DownloadThread { - - private Context mContext; - private DownloadInfo mInfo; - private DownloaderService mService; - private final DownloadsDB mDB; - private final DownloadNotification mNotification; - private String mUserAgent; - - public DownloadThread(DownloadInfo info, DownloaderService service, - DownloadNotification notification) { - mContext = service; - mInfo = info; - mService = service; - mNotification = notification; - mDB = DownloadsDB.getDB(service); - mUserAgent = "APKXDL (Linux; U; Android " + android.os.Build.VERSION.RELEASE + ";" - + Locale.getDefault().toString() + "; " + android.os.Build.DEVICE + "/" - + android.os.Build.ID + ")" + - service.getPackageName(); - } - - /** - * Returns the default user agent - */ - private String userAgent() { - return mUserAgent; - } - - /** - * State for the entire run() method. - */ - private static class State { - public String mFilename; - public FileOutputStream mStream; - public boolean mCountRetry = false; - public int mRetryAfter = 0; - public int mRedirectCount = 0; - public String mNewUri; - public boolean mGotData = false; - public String mRequestUri; - - public State(DownloadInfo info, DownloaderService service) { - mRedirectCount = info.mRedirectCount; - mRequestUri = info.mUri; - mFilename = service.generateTempSaveFileName(info.mFileName); - } - } - - /** - * State within executeDownload() - */ - private static class InnerState { - public int mBytesSoFar = 0; - public int mBytesThisSession = 0; - public String mHeaderETag; - public boolean mContinuingDownload = false; - public String mHeaderContentLength; - public String mHeaderContentDisposition; - public String mHeaderContentLocation; - public int mBytesNotified = 0; - public long mTimeLastNotification = 0; - } - - /** - * Raised from methods called by run() to indicate that the current request - * should be stopped immediately. Note the message passed to this exception - * will be logged and therefore must be guaranteed not to contain any PII, - * meaning it generally can't include any information about the request URI, - * headers, or destination filename. - */ - private class StopRequest extends Throwable { - /** - * - */ - private static final long serialVersionUID = 6338592678988347973L; - public int mFinalStatus; - - public StopRequest(int finalStatus, String message) { - super(message); - mFinalStatus = finalStatus; - } - - public StopRequest(int finalStatus, String message, Throwable throwable) { - super(message, throwable); - mFinalStatus = finalStatus; - } - } - - /** - * Raised from methods called by executeDownload() to indicate that the - * download should be retried immediately. - */ - private class RetryDownload extends Throwable { - - /** - * - */ - private static final long serialVersionUID = 6196036036517540229L; - } - - /** - * Returns the preferred proxy to be used by clients. This is a wrapper - * around {@link android.net.Proxy#getHost()}. Currently no proxy will be - * returned for localhost or if the active network is Wi-Fi. - * - * @param context the context which will be passed to - * {@link android.net.Proxy#getHost()} - * @param url the target URL for the request - * @note Calling this method requires permission - * android.permission.ACCESS_NETWORK_STATE - * @return The preferred proxy to be used by clients, or null if there is no - * proxy. - */ - public HttpHost getPreferredHttpHost(Context context, - String url) { - if (!isLocalHost(url) && !mService.isWiFi()) { - final String proxyHost = Proxy.getHost(context); - if (proxyHost != null) { - return new HttpHost(proxyHost, Proxy.getPort(context), "http"); - } - } - - return null; - } - - static final private boolean isLocalHost(String url) { - if (url == null) { - return false; - } - - try { - final URI uri = URI.create(url); - final String host = uri.getHost(); - if (host != null) { - // TODO: InetAddress.isLoopbackAddress should be used to check - // for localhost. However no public factory methods exist which - // can be used without triggering DNS lookup if host is not - // localhost. - if (host.equalsIgnoreCase("localhost") || - host.equals("127.0.0.1") || - host.equals("[::1]")) { - return true; - } - } - } catch (IllegalArgumentException iex) { - // Ignore (URI.create) - } - - return false; - } - - /** - * Executes the download in a separate thread - */ - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - State state = new State(mInfo, mService); - AndroidHttpClient client = null; - PowerManager.WakeLock wakeLock = null; - int finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; - - try { - PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); - wakeLock.acquire(); - - if (Constants.LOGV) { - Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - - client = AndroidHttpClient.newInstance(userAgent(), mContext); - - boolean finished = false; - while (!finished) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - // Set or unset proxy, which may have changed since last GET - // request. - // setDefaultProxy() supports null as proxy parameter. - ConnRouteParams.setDefaultProxy(client.getParams(), - getPreferredHttpHost(mContext, state.mRequestUri)); - HttpGet request = new HttpGet(state.mRequestUri); - try { - executeDownload(state, client, request); - finished = true; - } catch (RetryDownload exc) { - // fall through - } finally { - request.abort(); - request = null; - } - } - - if (Constants.LOGV) { - Log.v(Constants.TAG, "download completed for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - finalizeDestinationFile(state); - finalStatus = DownloaderService.STATUS_SUCCESS; - } catch (StopRequest error) { - // remove the cause before printing, in case it contains PII - Log.w(Constants.TAG, - "Aborting request for download " + mInfo.mFileName + ": " + error.getMessage()); - error.printStackTrace(); - finalStatus = error.mFinalStatus; - // fall through to finally block - } catch (Throwable ex) { // sometimes the socket code throws unchecked - // exceptions - Log.w(Constants.TAG, "Exception for " + mInfo.mFileName + ": " + ex); - finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; - // falls through to the code that reports an error - } finally { - if (wakeLock != null) { - wakeLock.release(); - wakeLock = null; - } - if (client != null) { - client.close(); - client = null; - } - cleanupDestination(state, finalStatus); - notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter, - state.mRedirectCount, state.mGotData, state.mFilename); - } - } - - /** - * Fully execute a single download request - setup and send the request, - * handle the response, and transfer the data to the destination file. - */ - private void executeDownload(State state, AndroidHttpClient client, HttpGet request) - throws StopRequest, RetryDownload { - InnerState innerState = new InnerState(); - byte data[] = new byte[Constants.BUFFER_SIZE]; - - checkPausedOrCanceled(state); - - setupDestinationFile(state, innerState); - addRequestHeaders(innerState, request); - - // check just before sending the request to avoid using an invalid - // connection at all - checkConnectivity(state); - - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING); - HttpResponse response = sendRequest(state, client, request); - handleExceptionalStatus(state, innerState, response); - - if (Constants.LOGV) { - Log.v(Constants.TAG, "received response for " + mInfo.mUri); - } - - processResponseHeaders(state, innerState, response); - InputStream entityStream = openResponseEntity(state, response); - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_DOWNLOADING); - transferData(state, innerState, data, entityStream); - } - - /** - * Check if current connectivity is valid for this request. - */ - private void checkConnectivity(State state) throws StopRequest { - switch (mService.getNetworkAvailabilityState(mDB)) { - case DownloaderService.NETWORK_OK: - return; - case DownloaderService.NETWORK_NO_CONNECTION: - throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, - "waiting for network to return"); - case DownloaderService.NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: - throw new StopRequest( - DownloaderService.STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION, - "waiting for wifi or for download over cellular to be authorized"); - case DownloaderService.NETWORK_CANNOT_USE_ROAMING: - throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, - "roaming is not allowed"); - case DownloaderService.NETWORK_UNUSABLE_DUE_TO_SIZE: - throw new StopRequest(DownloaderService.STATUS_QUEUED_FOR_WIFI, "waiting for wifi"); - } - } - - /** - * Transfer as much data as possible from the HTTP response to the - * destination file. - * - * @param data buffer to use to read data - * @param entityStream stream for reading the HTTP response entity - */ - private void transferData(State state, InnerState innerState, byte[] data, - InputStream entityStream) throws StopRequest { - for (;;) { - int bytesRead = readFromResponse(state, innerState, data, entityStream); - if (bytesRead == -1) { // success, end of stream already reached - handleEndOfStream(state, innerState); - return; - } - - state.mGotData = true; - writeDataToDestination(state, data, bytesRead); - innerState.mBytesSoFar += bytesRead; - innerState.mBytesThisSession += bytesRead; - reportProgress(state, innerState); - - checkPausedOrCanceled(state); - } - } - - /** - * Called after a successful completion to take any necessary action on the - * downloaded file. - */ - private void finalizeDestinationFile(State state) throws StopRequest { - syncDestination(state); - String tempFilename = state.mFilename; - String finalFilename = Helpers.generateSaveFileName(mService, mInfo.mFileName); - if (!state.mFilename.equals(finalFilename)) { - File startFile = new File(tempFilename); - File destFile = new File(finalFilename); - if (mInfo.mTotalBytes != -1 && mInfo.mCurrentBytes == mInfo.mTotalBytes) { - if (!startFile.renameTo(destFile)) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "unable to finalize destination file"); - } - } else { - throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, - "file delivered with incorrect size. probably due to network not browser configured"); - } - } - } - - /** - * Called just before the thread finishes, regardless of status, to take any - * necessary action on the downloaded file. - */ - private void cleanupDestination(State state, int finalStatus) { - closeDestination(state); - if (state.mFilename != null && DownloaderService.isStatusError(finalStatus)) { - new File(state.mFilename).delete(); - state.mFilename = null; - } - } - - /** - * Sync the destination file to storage. - */ - private void syncDestination(State state) { - FileOutputStream downloadedFileStream = null; - try { - downloadedFileStream = new FileOutputStream(state.mFilename, true); - downloadedFileStream.getFD().sync(); - } catch (FileNotFoundException ex) { - Log.w(Constants.TAG, "file " + state.mFilename + " not found: " + ex); - } catch (SyncFailedException ex) { - Log.w(Constants.TAG, "file " + state.mFilename + " sync failed: " + ex); - } catch (IOException ex) { - Log.w(Constants.TAG, "IOException trying to sync " + state.mFilename + ": " + ex); - } catch (RuntimeException ex) { - Log.w(Constants.TAG, "exception while syncing file: ", ex); - } finally { - if (downloadedFileStream != null) { - try { - downloadedFileStream.close(); - } catch (IOException ex) { - Log.w(Constants.TAG, "IOException while closing synced file: ", ex); - } catch (RuntimeException ex) { - Log.w(Constants.TAG, "exception while closing file: ", ex); - } - } - } - } - - /** - * Close the destination output stream. - */ - private void closeDestination(State state) { - try { - // close the file - if (state.mStream != null) { - state.mStream.close(); - state.mStream = null; - } - } catch (IOException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "exception when closing the file after download : " + ex); - } - // nothing can really be done if the file can't be closed - } - } - - /** - * Check if the download has been paused or canceled, stopping the request - * appropriately if it has been. - */ - private void checkPausedOrCanceled(State state) throws StopRequest { - if (mService.getControl() == DownloaderService.CONTROL_PAUSED) { - int status = mService.getStatus(); - switch (status) { - case DownloaderService.STATUS_PAUSED_BY_APP: - throw new StopRequest(mService.getStatus(), - "download paused"); - } - } - } - - /** - * Report download progress through the database if necessary. - */ - private void reportProgress(State state, InnerState innerState) { - long now = System.currentTimeMillis(); - if (innerState.mBytesSoFar - innerState.mBytesNotified - > Constants.MIN_PROGRESS_STEP - && now - innerState.mTimeLastNotification - > Constants.MIN_PROGRESS_TIME) { - // we store progress updates to the database here - mInfo.mCurrentBytes = innerState.mBytesSoFar; - mDB.updateDownloadCurrentBytes(mInfo); - - innerState.mBytesNotified = innerState.mBytesSoFar; - innerState.mTimeLastNotification = now; - - long totalBytesSoFar = innerState.mBytesThisSession + mService.mBytesSoFar; - - if (Constants.LOGVV) { - Log.v(Constants.TAG, "downloaded " + mInfo.mCurrentBytes + " out of " - + mInfo.mTotalBytes); - Log.v(Constants.TAG, " total " + totalBytesSoFar + " out of " - + mService.mTotalLength); - } - - mService.notifyUpdateBytes(totalBytesSoFar); - } - } - - /** - * Write a data buffer to the destination file. - * - * @param data buffer containing the data to write - * @param bytesRead how many bytes to write from the buffer - */ - private void writeDataToDestination(State state, byte[] data, int bytesRead) - throws StopRequest { - for (;;) { - try { - if (state.mStream == null) { - state.mStream = new FileOutputStream(state.mFilename, true); - } - state.mStream.write(data, 0, bytesRead); - // we close after every write --- this may be too inefficient - closeDestination(state); - return; - } catch (IOException ex) { - if (!Helpers.isExternalMediaMounted()) { - throw new StopRequest(DownloaderService.STATUS_DEVICE_NOT_FOUND_ERROR, - "external media not mounted while writing destination file"); - } - - long availableBytes = - Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename)); - if (availableBytes < bytesRead) { - throw new StopRequest(DownloaderService.STATUS_INSUFFICIENT_SPACE_ERROR, - "insufficient space while writing destination file", ex); - } - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while writing destination file: " + ex.toString(), ex); - } - } - } - - /** - * Called when we've reached the end of the HTTP response stream, to update - * the database and check for consistency. - */ - private void handleEndOfStream(State state, InnerState innerState) throws StopRequest { - mInfo.mCurrentBytes = innerState.mBytesSoFar; - // this should always be set from the market - // if ( innerState.mHeaderContentLength == null ) { - // mInfo.mTotalBytes = innerState.mBytesSoFar; - // } - mDB.updateDownload(mInfo); - - boolean lengthMismatched = (innerState.mHeaderContentLength != null) - && (innerState.mBytesSoFar != Integer.parseInt(innerState.mHeaderContentLength)); - if (lengthMismatched) { - if (cannotResume(innerState)) { - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - "mismatched content length"); - } else { - throw new StopRequest(getFinalStatusForHttpError(state), - "closed socket before end of file"); - } - } - } - - private boolean cannotResume(InnerState innerState) { - return innerState.mBytesSoFar > 0 && innerState.mHeaderETag == null; - } - - /** - * Read some data from the HTTP response stream, handling I/O errors. - * - * @param data buffer to use to read data - * @param entityStream stream for reading the HTTP response entity - * @return the number of bytes actually read or -1 if the end of the stream - * has been reached - */ - private int readFromResponse(State state, InnerState innerState, byte[] data, - InputStream entityStream) throws StopRequest { - try { - return entityStream.read(data); - } catch (IOException ex) { - logNetworkState(); - mInfo.mCurrentBytes = innerState.mBytesSoFar; - mDB.updateDownload(mInfo); - if (cannotResume(innerState)) { - String message = "while reading response: " + ex.toString() - + ", can't resume interrupted download with no ETag"; - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - message, ex); - } else { - throw new StopRequest(getFinalStatusForHttpError(state), - "while reading response: " + ex.toString(), ex); - } - } - } - - /** - * Open a stream for the HTTP response entity, handling I/O errors. - * - * @return an InputStream to read the response entity - */ - private InputStream openResponseEntity(State state, HttpResponse response) - throws StopRequest { - try { - return response.getEntity().getContent(); - } catch (IOException ex) { - logNetworkState(); - throw new StopRequest(getFinalStatusForHttpError(state), - "while getting entity: " + ex.toString(), ex); - } - } - - private void logNetworkState() { - if (Constants.LOGX) { - Log.i(Constants.TAG, - "Net " - + (mService.getNetworkAvailabilityState(mDB) == DownloaderService.NETWORK_OK ? "Up" - : "Down")); - } - } - - /** - * Read HTTP response headers and take appropriate action, including setting - * up the destination file and updating the database. - */ - private void processResponseHeaders(State state, InnerState innerState, HttpResponse response) - throws StopRequest { - if (innerState.mContinuingDownload) { - // ignore response headers on resume requests - return; - } - - readResponseHeaders(state, innerState, response); - - try { - state.mFilename = mService.generateSaveFile(mInfo.mFileName, mInfo.mTotalBytes); - } catch (DownloaderService.GenerateSaveFileError exc) { - throw new StopRequest(exc.mStatus, exc.mMessage); - } - try { - state.mStream = new FileOutputStream(state.mFilename); - } catch (FileNotFoundException exc) { - // make sure the directory exists - File pathFile = new File(Helpers.getSaveFilePath(mService)); - try { - if (pathFile.mkdirs()) { - state.mStream = new FileOutputStream(state.mFilename); - } - } catch (Exception ex) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while opening destination file: " + exc.toString(), exc); - } - } - if (Constants.LOGV) { - Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + state.mFilename); - } - - updateDatabaseFromHeaders(state, innerState); - // check connectivity again now that we know the total size - checkConnectivity(state); - } - - /** - * Update necessary database fields based on values of HTTP response headers - * that have been read. - */ - private void updateDatabaseFromHeaders(State state, InnerState innerState) { - mInfo.mETag = innerState.mHeaderETag; - mDB.updateDownload(mInfo); - } - - /** - * Read headers from the HTTP response and store them into local state. - */ - private void readResponseHeaders(State state, InnerState innerState, HttpResponse response) - throws StopRequest { - Header header = response.getFirstHeader("Content-Disposition"); - if (header != null) { - innerState.mHeaderContentDisposition = header.getValue(); - } - header = response.getFirstHeader("Content-Location"); - if (header != null) { - innerState.mHeaderContentLocation = header.getValue(); - } - header = response.getFirstHeader("ETag"); - if (header != null) { - innerState.mHeaderETag = header.getValue(); - } - String headerTransferEncoding = null; - header = response.getFirstHeader("Transfer-Encoding"); - if (header != null) { - headerTransferEncoding = header.getValue(); - } - String headerContentType = null; - header = response.getFirstHeader("Content-Type"); - if (header != null) { - headerContentType = header.getValue(); - if (!headerContentType.equals("application/vnd.android.obb")) { - throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, - "file delivered with incorrect Mime type"); - } - } - - if (headerTransferEncoding == null) { - header = response.getFirstHeader("Content-Length"); - if (header != null) { - innerState.mHeaderContentLength = header.getValue(); - // this is always set from Market - long contentLength = Long.parseLong(innerState.mHeaderContentLength); - if (contentLength != -1 && contentLength != mInfo.mTotalBytes) { - // we're most likely on a bad wifi connection -- we should - // probably - // also look at the mime type --- but the size mismatch is - // enough - // to tell us that something is wrong here - Log.e(Constants.TAG, "Incorrect file size delivered."); - } - } - } else { - // Ignore content-length with transfer-encoding - 2616 4.4 3 - if (Constants.LOGVV) { - Log.v(Constants.TAG, - "ignoring content-length because of xfer-encoding"); - } - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Content-Disposition: " + - innerState.mHeaderContentDisposition); - Log.v(Constants.TAG, "Content-Length: " + innerState.mHeaderContentLength); - Log.v(Constants.TAG, "Content-Location: " + innerState.mHeaderContentLocation); - Log.v(Constants.TAG, "ETag: " + innerState.mHeaderETag); - Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding); - } - - boolean noSizeInfo = innerState.mHeaderContentLength == null - && (headerTransferEncoding == null - || !headerTransferEncoding.equalsIgnoreCase("chunked")); - if (noSizeInfo) { - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "can't know size of download, giving up"); - } - } - - /** - * Check the HTTP response status and handle anything unusual (e.g. not - * 200/206). - */ - private void handleExceptionalStatus(State state, InnerState innerState, HttpResponse response) - throws StopRequest, RetryDownload { - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) { - handleServiceUnavailable(state, response); - } - if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307) { - handleRedirect(state, response, statusCode); - } - - int expectedStatus = innerState.mContinuingDownload ? 206 - : DownloaderService.STATUS_SUCCESS; - if (statusCode != expectedStatus) { - handleOtherStatus(state, innerState, statusCode); - } else { - // no longer redirected - state.mRedirectCount = 0; - } - } - - /** - * Handle a status that we don't know how to deal with properly. - */ - private void handleOtherStatus(State state, InnerState innerState, int statusCode) - throws StopRequest { - int finalStatus; - if (DownloaderService.isStatusError(statusCode)) { - finalStatus = statusCode; - } else if (statusCode >= 300 && statusCode < 400) { - finalStatus = DownloaderService.STATUS_UNHANDLED_REDIRECT; - } else if (innerState.mContinuingDownload && statusCode == DownloaderService.STATUS_SUCCESS) { - finalStatus = DownloaderService.STATUS_CANNOT_RESUME; - } else { - finalStatus = DownloaderService.STATUS_UNHANDLED_HTTP_CODE; - } - throw new StopRequest(finalStatus, "http error " + statusCode); - } - - /** - * Handle a 3xx redirect status. - */ - private void handleRedirect(State state, HttpResponse response, int statusCode) - throws StopRequest, RetryDownload { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP redirect " + statusCode); - } - if (state.mRedirectCount >= Constants.MAX_REDIRECTS) { - throw new StopRequest(DownloaderService.STATUS_TOO_MANY_REDIRECTS, "too many redirects"); - } - Header header = response.getFirstHeader("Location"); - if (header == null) { - return; - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Location :" + header.getValue()); - } - - String newUri; - try { - newUri = new URI(mInfo.mUri).resolve(new URI(header.getValue())).toString(); - } catch (URISyntaxException ex) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "Couldn't resolve redirect URI " + header.getValue() - + " for " + mInfo.mUri); - } - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "Couldn't resolve redirect URI"); - } - ++state.mRedirectCount; - state.mRequestUri = newUri; - if (statusCode == 301 || statusCode == 303) { - // use the new URI for all future requests (should a retry/resume be - // necessary) - state.mNewUri = newUri; - } - throw new RetryDownload(); - } - - /** - * Add headers for this download to the HTTP request to allow for resume. - */ - private void addRequestHeaders(InnerState innerState, HttpGet request) { - if (innerState.mContinuingDownload) { - if (innerState.mHeaderETag != null) { - request.addHeader("If-Match", innerState.mHeaderETag); - } - request.addHeader("Range", "bytes=" + innerState.mBytesSoFar + "-"); - } - } - - /** - * Handle a 503 Service Unavailable status by processing the Retry-After - * header. - */ - private void handleServiceUnavailable(State state, HttpResponse response) throws StopRequest { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP response code 503"); - } - state.mCountRetry = true; - Header header = response.getFirstHeader("Retry-After"); - if (header != null) { - try { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Retry-After :" + header.getValue()); - } - state.mRetryAfter = Integer.parseInt(header.getValue()); - if (state.mRetryAfter < 0) { - state.mRetryAfter = 0; - } else { - if (state.mRetryAfter < Constants.MIN_RETRY_AFTER) { - state.mRetryAfter = Constants.MIN_RETRY_AFTER; - } else if (state.mRetryAfter > Constants.MAX_RETRY_AFTER) { - state.mRetryAfter = Constants.MAX_RETRY_AFTER; - } - state.mRetryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1); - state.mRetryAfter *= 1000; - } - } catch (NumberFormatException ex) { - // ignored - retryAfter stays 0 in this case. - } - } - throw new StopRequest(DownloaderService.STATUS_WAITING_TO_RETRY, - "got 503 Service Unavailable, will retry later"); - } - - /** - * Send the request to the server, handling any I/O exceptions. - */ - private HttpResponse sendRequest(State state, AndroidHttpClient client, HttpGet request) - throws StopRequest { - try { - return client.execute(request); - } catch (IllegalArgumentException ex) { - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "while trying to execute request: " + ex.toString(), ex); - } catch (IOException ex) { - logNetworkState(); - throw new StopRequest(getFinalStatusForHttpError(state), - "while trying to execute request: " + ex.toString(), ex); - } - } - - private int getFinalStatusForHttpError(State state) { - if (mService.getNetworkAvailabilityState(mDB) != DownloaderService.NETWORK_OK) { - return DownloaderService.STATUS_WAITING_FOR_NETWORK; - } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) { - state.mCountRetry = true; - return DownloaderService.STATUS_WAITING_TO_RETRY; - } else { - Log.w(Constants.TAG, "reached max retries for " + mInfo.mNumFailed); - return DownloaderService.STATUS_HTTP_DATA_ERROR; - } - } - - /** - * Prepare the destination file to receive data. If the file already exists, - * we'll set up appropriately for resumption. - */ - private void setupDestinationFile(State state, InnerState innerState) - throws StopRequest { - if (state.mFilename != null) { // only true if we've already run a - // thread for this download - if (!Helpers.isFilenameValid(state.mFilename)) { - // this should never happen - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "found invalid internal destination filename"); - } - // We're resuming a download that got interrupted - File f = new File(state.mFilename); - if (f.exists()) { - long fileLength = f.length(); - if (fileLength == 0) { - // The download hadn't actually started, we can restart from - // scratch - f.delete(); - state.mFilename = null; - } else if (mInfo.mETag == null) { - // This should've been caught upon failure - f.delete(); - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - "Trying to resume a download that can't be resumed"); - } else { - // All right, we'll be able to resume this download - try { - state.mStream = new FileOutputStream(state.mFilename, true); - } catch (FileNotFoundException exc) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while opening destination for resuming: " + exc.toString(), exc); - } - innerState.mBytesSoFar = (int) fileLength; - if (mInfo.mTotalBytes != -1) { - innerState.mHeaderContentLength = Long.toString(mInfo.mTotalBytes); - } - innerState.mHeaderETag = mInfo.mETag; - innerState.mContinuingDownload = true; - } - } - } - - if (state.mStream != null) { - closeDestination(state); - } - } - - /** - * Stores information about the completed download, and notifies the - * initiating application. - */ - private void notifyDownloadCompleted( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename) { - updateDownloadDatabase( - status, countRetry, retryAfter, redirectCount, gotData, filename); - if (DownloaderService.isStatusCompleted(status)) { - // TBD: send status update? - } - } - - private void updateDownloadDatabase( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename) { - mInfo.mStatus = status; - mInfo.mRetryAfter = retryAfter; - mInfo.mRedirectCount = redirectCount; - mInfo.mLastMod = System.currentTimeMillis(); - if (!countRetry) { - mInfo.mNumFailed = 0; - } else if (gotData) { - mInfo.mNumFailed = 1; - } else { - mInfo.mNumFailed++; - } - mDB.updateDownload(mInfo); - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java deleted file mode 100644 index 627bf3eedd..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java +++ /dev/null @@ -1,1341 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.DownloadProgressInfo; -import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; -import com.google.android.vending.expansion.downloader.IDownloaderService; -import com.google.android.vending.expansion.downloader.IStub; -import com.google.android.vending.licensing.AESObfuscator; -import com.google.android.vending.licensing.APKExpansionPolicy; -import com.google.android.vending.licensing.LicenseChecker; -import com.google.android.vending.licensing.LicenseCheckerCallback; -import com.google.android.vending.licensing.Policy; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; -import android.os.Handler; -import android.os.IBinder; -import android.os.Messenger; -import android.os.SystemClock; -import android.provider.Settings.Secure; -import android.telephony.TelephonyManager; -import android.util.Log; - -import java.io.File; - -/** - * Performs the background downloads requested by applications that use the - * Downloads provider. This service does not run as a foreground task, so - * Android may kill it off at will, but it will try to restart itself if it can. - * Note that Android by default will kill off any process that has an open file - * handle on the shared (SD Card) partition if the partition is unmounted. - */ -public abstract class DownloaderService extends CustomIntentService implements IDownloaderService { - - public DownloaderService() { - super("LVLDownloadService"); - } - - private static final String LOG_TAG = "LVLDL"; - - // the following NETWORK_* constants are used to indicates specific reasons - // for disallowing a - // download from using a network, since specific causes can require special - // handling - - /** - * The network is usable for the given download. - */ - public static final int NETWORK_OK = 1; - - /** - * There is no network connectivity. - */ - public static final int NETWORK_NO_CONNECTION = 2; - - /** - * The download exceeds the maximum size for this network. - */ - public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3; - - /** - * The download exceeds the recommended maximum size for this network, the - * user must confirm for this download to proceed without WiFi. - */ - public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4; - - /** - * The current connection is roaming, and the download can't proceed over a - * roaming connection. - */ - public static final int NETWORK_CANNOT_USE_ROAMING = 5; - - /** - * The app requesting the download specific that it can't use the current - * network connection. - */ - public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6; - - /** - * For intents used to notify the user that a download exceeds a size - * threshold, if this extra is true, WiFi is required for this download - * size; otherwise, it is only recommended. - */ - public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired"; - public static final String EXTRA_FILE_NAME = "downloadId"; - - /** - * Used with DOWNLOAD_STATUS - */ - public static final String EXTRA_STATUS_STATE = "ESS"; - public static final String EXTRA_STATUS_TOTAL_SIZE = "ETS"; - public static final String EXTRA_STATUS_CURRENT_FILE_SIZE = "CFS"; - public static final String EXTRA_STATUS_TOTAL_PROGRESS = "TFP"; - public static final String EXTRA_STATUS_CURRENT_PROGRESS = "CFP"; - - public static final String ACTION_DOWNLOADS_CHANGED = "downloadsChanged"; - - /** - * Broadcast intent action sent by the download manager when a download - * completes. - */ - public final static String ACTION_DOWNLOAD_COMPLETE = "lvldownloader.intent.action.DOWNLOAD_COMPLETE"; - - /** - * Broadcast intent action sent by the download manager when download status - * changes. - */ - public final static String ACTION_DOWNLOAD_STATUS = "lvldownloader.intent.action.DOWNLOAD_STATUS"; - - /* - * Lists the states that the download manager can set on a download to - * notify applications of the download progress. The codes follow the HTTP - * families:
1xx: informational
2xx: success
3xx: redirects (not - * used by the download manager)
4xx: client errors
5xx: server - * errors - */ - - /** - * Returns whether the status is informational (i.e. 1xx). - */ - public static boolean isStatusInformational(int status) { - return (status >= 100 && status < 200); - } - - /** - * Returns whether the status is a success (i.e. 2xx). - */ - public static boolean isStatusSuccess(int status) { - return (status >= 200 && status < 300); - } - - /** - * Returns whether the status is an error (i.e. 4xx or 5xx). - */ - public static boolean isStatusError(int status) { - return (status >= 400 && status < 600); - } - - /** - * Returns whether the status is a client error (i.e. 4xx). - */ - public static boolean isStatusClientError(int status) { - return (status >= 400 && status < 500); - } - - /** - * Returns whether the status is a server error (i.e. 5xx). - */ - public static boolean isStatusServerError(int status) { - return (status >= 500 && status < 600); - } - - /** - * Returns whether the download has completed (either with success or - * error). - */ - public static boolean isStatusCompleted(int status) { - return (status >= 200 && status < 300) - || (status >= 400 && status < 600); - } - - /** - * This download hasn't stated yet - */ - public static final int STATUS_PENDING = 190; - - /** - * This download has started - */ - public static final int STATUS_RUNNING = 192; - - /** - * This download has been paused by the owning app. - */ - public static final int STATUS_PAUSED_BY_APP = 193; - - /** - * This download encountered some network error and is waiting before - * retrying the request. - */ - public static final int STATUS_WAITING_TO_RETRY = 194; - - /** - * This download is waiting for network connectivity to proceed. - */ - public static final int STATUS_WAITING_FOR_NETWORK = 195; - - /** - * This download is waiting for a Wi-Fi connection to proceed or for - * permission to download over cellular. - */ - public static final int STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION = 196; - - /** - * This download is waiting for a Wi-Fi connection to proceed. - */ - public static final int STATUS_QUEUED_FOR_WIFI = 197; - - /** - * This download has successfully completed. Warning: there might be other - * status values that indicate success in the future. Use isSucccess() to - * capture the entire category. - * - * @hide - */ - public static final int STATUS_SUCCESS = 200; - - /** - * The requested URL is no longer available - */ - public static final int STATUS_FORBIDDEN = 403; - - /** - * The file was delivered incorrectly - */ - public static final int STATUS_FILE_DELIVERED_INCORRECTLY = 487; - - /** - * The requested destination file already exists. - */ - public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; - - /** - * Some possibly transient error occurred, but we can't resume the download. - */ - public static final int STATUS_CANNOT_RESUME = 489; - - /** - * This download was canceled - * - * @hide - */ - public static final int STATUS_CANCELED = 490; - - /** - * This download has completed with an error. Warning: there will be other - * status values that indicate errors in the future. Use isStatusError() to - * capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of a storage issue. - * Typically, that's because the filesystem is missing or full. Use the more - * specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} and - * {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. - * - * @hide - */ - public static final int STATUS_FILE_ERROR = 492; - - /** - * This download couldn't be completed because of an HTTP redirect response - * that the download manager couldn't handle. - * - * @hide - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed because of an unspecified unhandled - * HTTP code. - * - * @hide - */ - public static final int STATUS_UNHANDLED_HTTP_CODE = 494; - - /** - * This download couldn't be completed because of an error receiving or - * processing data at the HTTP level. - * - * @hide - */ - public static final int STATUS_HTTP_DATA_ERROR = 495; - - /** - * This download couldn't be completed because of an HttpException while - * setting up the request. - * - * @hide - */ - public static final int STATUS_HTTP_EXCEPTION = 496; - - /** - * This download couldn't be completed because there were too many - * redirects. - * - * @hide - */ - public static final int STATUS_TOO_MANY_REDIRECTS = 497; - - /** - * This download couldn't be completed due to insufficient storage space. - * Typically, this is because the SD card is full. - * - * @hide - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; - - /** - * This download couldn't be completed because no external storage device - * was found. Typically, this is because the SD card is not mounted. - * - * @hide - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - - /** - * This download is allowed to run. - * - * @hide - */ - public static final int CONTROL_RUN = 0; - - /** - * This download must pause at the first opportunity. - * - * @hide - */ - public static final int CONTROL_PAUSED = 1; - - /** - * This download is visible but only shows in the notifications while it's - * in progress. - * - * @hide - */ - public static final int VISIBILITY_VISIBLE = 0; - - /** - * This download is visible and shows in the notifications while in progress - * and after completion. - * - * @hide - */ - public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; - - /** - * This download doesn't show in the UI or in the notifications. - * - * @hide - */ - public static final int VISIBILITY_HIDDEN = 2; - - /** - * Bit flag for {@link #setAllowedNetworkTypes} corresponding to - * {@link ConnectivityManager#TYPE_MOBILE}. - */ - public static final int NETWORK_MOBILE = 1 << 0; - - /** - * Bit flag for {@link #setAllowedNetworkTypes} corresponding to - * {@link ConnectivityManager#TYPE_WIFI}. - */ - public static final int NETWORK_WIFI = 1 << 1; - - private final static String TEMP_EXT = ".tmp"; - - /** - * Service thread status - */ - private static boolean sIsRunning; - - @Override - public IBinder onBind(Intent paramIntent) { - Log.d(Constants.TAG, "Service Bound"); - return this.mServiceMessenger.getBinder(); - } - - /** - * Network state. - */ - private boolean mIsConnected; - private boolean mIsFailover; - private boolean mIsCellularConnection; - private boolean mIsRoaming; - private boolean mIsAtLeast3G; - private boolean mIsAtLeast4G; - private boolean mStateChanged; - - /** - * Download state - */ - private int mControl; - private int mStatus; - - public boolean isWiFi() { - return mIsConnected && !mIsCellularConnection; - } - - /** - * Bindings to important services - */ - private ConnectivityManager mConnectivityManager; - private WifiManager mWifiManager; - - /** - * Package we are downloading for (defaults to package of application) - */ - private PackageInfo mPackageInfo; - - /** - * Byte counts - */ - long mBytesSoFar; - long mTotalLength; - int mFileCount; - - /** - * Used for calculating time remaining and speed - */ - long mBytesAtSample; - long mMillisecondsAtSample; - float mAverageDownloadSpeed; - - /** - * Our binding to the network state broadcasts - */ - private BroadcastReceiver mConnReceiver; - final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this); - final private Messenger mServiceMessenger = mServiceStub.getMessenger(); - private Messenger mClientMessenger; - private DownloadNotification mNotification; - private PendingIntent mPendingIntent; - private PendingIntent mAlarmIntent; - - /** - * Updates the network type based upon the type and subtype returned from - * the connectivity manager. Subtype is only used for cellular signals. - * - * @param type - * @param subType - */ - private void updateNetworkType(int type, int subType) { - switch (type) { - case ConnectivityManager.TYPE_WIFI: - case ConnectivityManager.TYPE_ETHERNET: - case ConnectivityManager.TYPE_BLUETOOTH: - mIsCellularConnection = false; - mIsAtLeast3G = false; - mIsAtLeast4G = false; - break; - case ConnectivityManager.TYPE_WIMAX: - mIsCellularConnection = true; - mIsAtLeast3G = true; - mIsAtLeast4G = true; - break; - case ConnectivityManager.TYPE_MOBILE: - mIsCellularConnection = true; - switch (subType) { - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_GPRS: - case TelephonyManager.NETWORK_TYPE_IDEN: - mIsAtLeast3G = false; - mIsAtLeast4G = false; - break; - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_UMTS: - mIsAtLeast3G = true; - mIsAtLeast4G = false; - break; - case TelephonyManager.NETWORK_TYPE_LTE: // 4G - case TelephonyManager.NETWORK_TYPE_EHRPD: // 3G ++ interop - // with 4G - case TelephonyManager.NETWORK_TYPE_HSPAP: // 3G ++ but - // marketed as - // 4G - mIsAtLeast3G = true; - mIsAtLeast4G = true; - break; - default: - mIsCellularConnection = false; - mIsAtLeast3G = false; - mIsAtLeast4G = false; - } - } - } - - private void updateNetworkState(NetworkInfo info) { - boolean isConnected = mIsConnected; - boolean isFailover = mIsFailover; - boolean isCellularConnection = mIsCellularConnection; - boolean isRoaming = mIsRoaming; - boolean isAtLeast3G = mIsAtLeast3G; - if (null != info) { - mIsRoaming = info.isRoaming(); - mIsFailover = info.isFailover(); - mIsConnected = info.isConnected(); - updateNetworkType(info.getType(), info.getSubtype()); - } else { - mIsRoaming = false; - mIsFailover = false; - mIsConnected = false; - updateNetworkType(-1, -1); - } - mStateChanged = (mStateChanged || isConnected != mIsConnected - || isFailover != mIsFailover - || isCellularConnection != mIsCellularConnection - || isRoaming != mIsRoaming || isAtLeast3G != mIsAtLeast3G); - if (Constants.LOGVV) { - if (mStateChanged) { - Log.v(LOG_TAG, "Network state changed: "); - Log.v(LOG_TAG, "Starting State: " + - (isConnected ? "Connected " : "Not Connected ") + - (isCellularConnection ? "Cellular " : "WiFi ") + - (isRoaming ? "Roaming " : "Local ") + - (isAtLeast3G ? "3G+ " : "<3G ")); - Log.v(LOG_TAG, "Ending State: " + - (mIsConnected ? "Connected " : "Not Connected ") + - (mIsCellularConnection ? "Cellular " : "WiFi ") + - (mIsRoaming ? "Roaming " : "Local ") + - (mIsAtLeast3G ? "3G+ " : "<3G ")); - - if (isServiceRunning()) { - if (mIsRoaming) { - mStatus = STATUS_WAITING_FOR_NETWORK; - mControl = CONTROL_PAUSED; - } else if (mIsCellularConnection) { - DownloadsDB db = DownloadsDB.getDB(this); - int flags = db.getFlags(); - if (0 == (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { - mStatus = STATUS_QUEUED_FOR_WIFI; - mControl = CONTROL_PAUSED; - } - } - } - - } - } - } - - /** - * Polls the network state, setting the flags appropriately. - */ - void pollNetworkState() { - if (null == mConnectivityManager) { - mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - } - if (null == mWifiManager) { - mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); - } - if (mConnectivityManager == null) { - Log.w(Constants.TAG, - "couldn't get connectivity manager to poll network state"); - } else { - NetworkInfo activeInfo = mConnectivityManager - .getActiveNetworkInfo(); - updateNetworkState(activeInfo); - } - } - - public static final int NO_DOWNLOAD_REQUIRED = 0; - public static final int LVL_CHECK_REQUIRED = 1; - public static final int DOWNLOAD_REQUIRED = 2; - - public static final String EXTRA_PACKAGE_NAME = "EPN"; - public static final String EXTRA_PENDING_INTENT = "EPI"; - public static final String EXTRA_MESSAGE_HANDLER = "EMH"; - - /** - * Returns true if the LVL check is required - * - * @param db a downloads DB synchronized with the latest state - * @param pi the package info for the project - * @return returns true if the filenames need to be returned - */ - private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo pi) { - // we need to update the LVL check and get a successful status to - // proceed - if (db.mVersionCode != pi.versionCode) { - return true; - } - return false; - } - - /** - * Careful! Only use this internally. - * - * @return whether we think the service is running - */ - private static synchronized boolean isServiceRunning() { - return sIsRunning; - } - - private static synchronized void setServiceRunning(boolean isRunning) { - sIsRunning = isRunning; - } - - public static int startDownloadServiceIfRequired(Context context, - Intent intent, Class serviceClass) throws NameNotFoundException { - final PendingIntent pendingIntent = (PendingIntent) intent - .getParcelableExtra(EXTRA_PENDING_INTENT); - return startDownloadServiceIfRequired(context, pendingIntent, - serviceClass); - } - - public static int startDownloadServiceIfRequired(Context context, - PendingIntent pendingIntent, Class serviceClass) - throws NameNotFoundException - { - String packageName = context.getPackageName(); - String className = serviceClass.getName(); - - return startDownloadServiceIfRequired(context, pendingIntent, - packageName, className); - } - - /** - * Starts the download if necessary. This function starts a flow that does ` - * many things. 1) Checks to see if the APK version has been checked and the - * metadata database updated 2) If the APK version does not match, checks - * the new LVL status to see if a new download is required 3) If the APK - * version does match, then checks to see if the download(s) have been - * completed 4) If the downloads have been completed, returns - * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the - * startup of an application to quickly ascertain if the application needs - * to wait to hear about any updated APK expansion files. Note that this - * does mean that the application MUST be run for the first time with a - * network connection, even if Market delivers all of the files. - * - * @param context - * @param thisIntent - * @return true if the app should wait for more guidance from the - * downloader, false if the app can continue - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, - PendingIntent pendingIntent, String classPackage, String className) - throws NameNotFoundException { - // first: do we need to do an LVL update? - // we begin by getting our APK version from the package manager - final PackageInfo pi = context.getPackageManager().getPackageInfo( - context.getPackageName(), 0); - - int status = NO_DOWNLOAD_REQUIRED; - - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(context); - - // we need to update the LVL check and get a successful status to - // proceed - if (isLVLCheckRequired(db, pi)) { - status = LVL_CHECK_REQUIRED; - } - // we don't have to update LVL. do we still have a download to start? - if (db.mStatus == 0) { - DownloadInfo[] infos = db.getDownloads(); - if (null != infos) { - for (DownloadInfo info : infos) { - if (!Helpers.doesFileExist(context, info.mFileName, info.mTotalBytes, true)) { - status = DOWNLOAD_REQUIRED; - db.updateStatus(-1); - break; - } - } - } - } else { - status = DOWNLOAD_REQUIRED; - } - switch (status) { - case DOWNLOAD_REQUIRED: - case LVL_CHECK_REQUIRED: - Intent fileIntent = new Intent(); - fileIntent.setClassName(classPackage, className); - fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent); - context.startService(fileIntent); - break; - } - return status; - } - - @Override - public void requestAbortDownload() { - mControl = CONTROL_PAUSED; - mStatus = STATUS_CANCELED; - } - - @Override - public void requestPauseDownload() { - mControl = CONTROL_PAUSED; - mStatus = STATUS_PAUSED_BY_APP; - } - - @Override - public void setDownloadFlags(int flags) { - DownloadsDB.getDB(this).updateFlags(flags); - } - - @Override - public void requestContinueDownload() { - if (mControl == CONTROL_PAUSED) { - mControl = CONTROL_RUN; - } - Intent fileIntent = new Intent(this, this.getClass()); - fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - this.startService(fileIntent); - } - - public abstract String getPublicKey(); - - public abstract byte[] getSALT(); - - public abstract String getAlarmReceiverClassName(); - - private class LVLRunnable implements Runnable { - LVLRunnable(Context context, PendingIntent intent) { - mContext = context; - mPendingIntent = intent; - } - - final Context mContext; - - @Override - public void run() { - setServiceRunning(true); - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_FETCHING_URL); - String deviceId = Secure.getString(mContext.getContentResolver(), - Secure.ANDROID_ID); - - final APKExpansionPolicy aep = new APKExpansionPolicy(mContext, - new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId)); - - // reset our policy back to the start of the world to force a - // re-check - aep.resetPolicy(); - - // let's try and get the OBB file from LVL first - // Construct the LicenseChecker with a Policy. - final LicenseChecker checker = new LicenseChecker(mContext, aep, - getPublicKey() // Your public licensing key. - ); - checker.checkAccess(new LicenseCheckerCallback() { - - @Override - public void allow(int reason) { - try { - int count = aep.getExpansionURLCount(); - DownloadsDB db = DownloadsDB.getDB(mContext); - int status = 0; - if (count != 0) { - for (int i = 0; i < count; i++) { - String currentFileName = aep - .getExpansionFileName(i); - if (null != currentFileName) { - DownloadInfo di = new DownloadInfo(i, - currentFileName, mContext.getPackageName()); - - long fileSize = aep.getExpansionFileSize(i); - if (handleFileUpdated(db, i, currentFileName, - fileSize)) { - status |= -1; - di.resetDownload(); - di.mUri = aep.getExpansionURL(i); - di.mTotalBytes = fileSize; - di.mStatus = status; - db.updateDownload(di); - } else { - // we need to read the download - // information - // from - // the database - DownloadInfo dbdi = db - .getDownloadInfoByFileName(di.mFileName); - if (null == dbdi) { - // the file exists already and is - // the - // correct size - // was delivered by Market or - // through - // another mechanism - Log.d(LOG_TAG, "file " + di.mFileName - + " found. Not downloading."); - di.mStatus = STATUS_SUCCESS; - di.mTotalBytes = fileSize; - di.mCurrentBytes = fileSize; - di.mUri = aep.getExpansionURL(i); - db.updateDownload(di); - } else if (dbdi.mStatus != STATUS_SUCCESS) { - // we just update the URL - dbdi.mUri = aep.getExpansionURL(i); - db.updateDownload(dbdi); - status |= -1; - } - } - } - } - } - // first: do we need to do an LVL update? - // we begin by getting our APK version from the package - // manager - PackageInfo pi; - try { - pi = mContext.getPackageManager().getPackageInfo( - mContext.getPackageName(), 0); - db.updateMetadata(pi.versionCode, status); - Class serviceClass = DownloaderService.this.getClass(); - switch (startDownloadServiceIfRequired(mContext, mPendingIntent, - serviceClass)) { - case NO_DOWNLOAD_REQUIRED: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); - break; - case LVL_CHECK_REQUIRED: - // DANGER WILL ROBINSON! - Log.e(LOG_TAG, "In LVL checking loop!"); - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); - throw new RuntimeException( - "Error with LVL checking and database integrity"); - case DOWNLOAD_REQUIRED: - // do nothing. the download will notify the - // application - // when things are done - break; - } - } catch (NameNotFoundException e1) { - e1.printStackTrace(); - throw new RuntimeException( - "Error with getting information from package name"); - } - } finally { - setServiceRunning(false); - } - } - - @Override - public void dontAllow(int reason) { - try - { - switch (reason) { - case Policy.NOT_LICENSED: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); - break; - case Policy.RETRY: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); - break; - } - } finally { - setServiceRunning(false); - } - - } - - @Override - public void applicationError(int errorCode) { - try { - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); - } finally { - setServiceRunning(false); - } - } - - }); - - } - - }; - - /** - * Updates the LVL information from the server. - * - * @param context - */ - public void updateLVL(final Context context) { - Context c = context.getApplicationContext(); - Handler h = new Handler(c.getMainLooper()); - h.post(new LVLRunnable(c, mPendingIntent)); - } - - /** - * The APK has been updated and a filename has been sent down from the - * Market call. If the file has the same name as the previous file, we do - * nothing as the file is guaranteed to be the same. If the file does not - * have the same name, we download it if it hasn't already been delivered by - * Market. - * - * @param index the index of the file from market (0 = main, 1 = patch) - * @param filename the name of the new file - * @param fileSize the size of the new file - * @return - */ - public boolean handleFileUpdated(DownloadsDB db, int index, - String filename, long fileSize) { - DownloadInfo di = db.getDownloadInfoByFileName(filename); - if (null != di) { - String oldFile = di.mFileName; - // cleanup - if (null != oldFile) { - if (filename.equals(oldFile)) { - return false; - } - - // remove partially downloaded file if it is there - String deleteFile = Helpers.generateSaveFileName(this, oldFile); - File f = new File(deleteFile); - if (f.exists()) - f.delete(); - } - } - return !Helpers.doesFileExist(this, filename, fileSize, true); - } - - private void scheduleAlarm(long wakeUp) { - AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - return; - } - - if (Constants.LOGV) { - Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); - } - - String className = getAlarmReceiverClassName(); - Intent intent = new Intent(Constants.ACTION_RETRY); - intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - intent.setClassName(this.getPackageName(), - className); - mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, - PendingIntent.FLAG_ONE_SHOT); - alarms.set( - AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + wakeUp, mAlarmIntent - ); - } - - private void cancelAlarms() { - if (null != mAlarmIntent) { - AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - return; - } - alarms.cancel(mAlarmIntent); - mAlarmIntent = null; - } - } - - /** - * We use this to track network state, such as when WiFi, Cellular, etc. is - * enabled when downloads are paused or in progress. - */ - private class InnerBroadcastReceiver extends BroadcastReceiver { - final Service mService; - - InnerBroadcastReceiver(Service service) { - mService = service; - } - - @Override - public void onReceive(Context context, Intent intent) { - pollNetworkState(); - if (mStateChanged - && !isServiceRunning()) { - Log.d(Constants.TAG, "InnerBroadcastReceiver Called"); - Intent fileIntent = new Intent(context, mService.getClass()); - fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - // send a new intent to the service - context.startService(fileIntent); - } - } - }; - - /** - * This is the main thread for the Downloader. This thread is responsible - * for queuing up downloads and other goodness. - */ - @Override - protected void onHandleIntent(Intent intent) { - setServiceRunning(true); - try { - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(this); - final PendingIntent pendingIntent = (PendingIntent) intent - .getParcelableExtra(EXTRA_PENDING_INTENT); - - if (null != pendingIntent) - { - mNotification.setClientIntent(pendingIntent); - mPendingIntent = pendingIntent; - } else if (null != mPendingIntent) { - mNotification.setClientIntent(mPendingIntent); - } else { - Log.e(LOG_TAG, "Downloader started in bad state without notification intent."); - return; - } - - // when the LVL check completes, a successful response will update - // the service - if (isLVLCheckRequired(db, mPackageInfo)) { - updateLVL(this); - return; - } - - // get each download - DownloadInfo[] infos = db.getDownloads(); - mBytesSoFar = 0; - mTotalLength = 0; - mFileCount = infos.length; - for (DownloadInfo info : infos) { - // We do an (simple) integrity check on each file, just to make - // sure - if (info.mStatus == STATUS_SUCCESS) { - // verify that the file matches the state - if (!Helpers.doesFileExist(this, info.mFileName, info.mTotalBytes, true)) { - info.mStatus = 0; - info.mCurrentBytes = 0; - } - } - // get aggregate data - mTotalLength += info.mTotalBytes; - mBytesSoFar += info.mCurrentBytes; - } - - // loop through all downloads and fetch them - pollNetworkState(); - if (null == mConnReceiver) { - - /** - * We use this to track network state, such as when WiFi, - * Cellular, etc. is enabled when downloads are paused or in - * progress. - */ - mConnReceiver = new InnerBroadcastReceiver(this); - IntentFilter intentFilter = new IntentFilter( - ConnectivityManager.CONNECTIVITY_ACTION); - intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - registerReceiver(mConnReceiver, intentFilter); - } - - for (DownloadInfo info : infos) { - long startingCount = info.mCurrentBytes; - - if (info.mStatus != STATUS_SUCCESS) { - DownloadThread dt = new DownloadThread(info, this, mNotification); - cancelAlarms(); - scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG); - dt.run(); - cancelAlarms(); - } - db.updateFromDb(info); - boolean setWakeWatchdog = false; - int notifyStatus; - switch (info.mStatus) { - case STATUS_FORBIDDEN: - // the URL is out of date - updateLVL(this); - return; - case STATUS_SUCCESS: - mBytesSoFar += info.mCurrentBytes - startingCount; - db.updateMetadata(mPackageInfo.versionCode, 0); - continue; - case STATUS_FILE_DELIVERED_INCORRECTLY: - // we may be on a network that is returning us a web - // page on redirect - notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE; - info.mCurrentBytes = 0; - db.updateDownload(info); - setWakeWatchdog = true; - break; - case STATUS_PAUSED_BY_APP: - notifyStatus = IDownloaderClient.STATE_PAUSED_BY_REQUEST; - break; - case STATUS_WAITING_FOR_NETWORK: - case STATUS_WAITING_TO_RETRY: - notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE; - setWakeWatchdog = true; - break; - case STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION: - case STATUS_QUEUED_FOR_WIFI: - // look for more detail here - if (null != mWifiManager) { - if (!mWifiManager.isWifiEnabled()) { - notifyStatus = IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION; - setWakeWatchdog = true; - break; - } - } - notifyStatus = IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION; - setWakeWatchdog = true; - break; - case STATUS_CANCELED: - notifyStatus = IDownloaderClient.STATE_FAILED_CANCELED; - setWakeWatchdog = true; - break; - - case STATUS_INSUFFICIENT_SPACE_ERROR: - notifyStatus = IDownloaderClient.STATE_FAILED_SDCARD_FULL; - setWakeWatchdog = true; - break; - - case STATUS_DEVICE_NOT_FOUND_ERROR: - notifyStatus = IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE; - setWakeWatchdog = true; - break; - - default: - notifyStatus = IDownloaderClient.STATE_FAILED; - break; - } - if (setWakeWatchdog) { - scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER); - } else { - cancelAlarms(); - } - // failure or pause state - mNotification.onDownloadStateChanged(notifyStatus); - return; - } - - // all downloads complete - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); - } finally { - setServiceRunning(false); - } - } - - @Override - public void onDestroy() { - if (null != mConnReceiver) { - unregisterReceiver(mConnReceiver); - mConnReceiver = null; - } - mServiceStub.disconnect(this); - super.onDestroy(); - } - - public int getNetworkAvailabilityState(DownloadsDB db) { - if (mIsConnected) { - if (!mIsCellularConnection) - return NETWORK_OK; - int flags = db.mFlags; - if (mIsRoaming) - return NETWORK_CANNOT_USE_ROAMING; - if (0 != (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { - return NETWORK_OK; - } else { - return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR; - } - } - return NETWORK_NO_CONNECTION; - } - - @Override - public void onCreate() { - super.onCreate(); - try { - mPackageInfo = getPackageManager().getPackageInfo( - getPackageName(), 0); - ApplicationInfo ai = getApplicationInfo(); - CharSequence applicationLabel = getPackageManager().getApplicationLabel(ai); - mNotification = new DownloadNotification(this, applicationLabel); - - } catch (NameNotFoundException e) { - e.printStackTrace(); - } - } - - /** - * Exception thrown from methods called by generateSaveFile() for any fatal - * error. - */ - public static class GenerateSaveFileError extends Exception { - private static final long serialVersionUID = 3465966015408936540L; - int mStatus; - String mMessage; - - public GenerateSaveFileError(int status, String message) { - mStatus = status; - mMessage = message; - } - } - - /** - * Returns the filename (where the file should be saved) from info about a - * download - */ - public String generateTempSaveFileName(String fileName) { - String path = Helpers.getSaveFilePath(this) - + File.separator + fileName + TEMP_EXT; - return path; - } - - /** - * Creates a filename (where the file should be saved) from info about a - * download. - */ - public String generateSaveFile(String filename, long filesize) - throws GenerateSaveFileError { - String path = generateTempSaveFileName(filename); - File expPath = new File(path); - if (!Helpers.isExternalMediaMounted()) { - Log.d(Constants.TAG, "External media not mounted: " + path); - throw new GenerateSaveFileError(STATUS_DEVICE_NOT_FOUND_ERROR, - "external media is not yet mounted"); - - } - if (expPath.exists()) { - Log.d(Constants.TAG, "File already exists: " + path); - throw new GenerateSaveFileError(STATUS_FILE_ALREADY_EXISTS_ERROR, - "requested destination file already exists"); - } - if (Helpers.getAvailableBytes(Helpers.getFilesystemRoot(path)) < filesize) { - throw new GenerateSaveFileError(STATUS_INSUFFICIENT_SPACE_ERROR, - "insufficient space on external storage"); - } - return path; - } - - /** - * @return a non-localized string appropriate for logging corresponding to - * one of the NETWORK_* constants. - */ - public String getLogMessageForNetworkError(int networkError) { - switch (networkError) { - case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE: - return "download size exceeds recommended limit for mobile network"; - - case NETWORK_UNUSABLE_DUE_TO_SIZE: - return "download size exceeds limit for mobile network"; - - case NETWORK_NO_CONNECTION: - return "no network connection available"; - - case NETWORK_CANNOT_USE_ROAMING: - return "download cannot use the current network connection because it is roaming"; - - case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: - return "download was requested to not use the current network type"; - - default: - return "unknown error with network connectivity"; - } - } - - public int getControl() { - return mControl; - } - - public int getStatus() { - return mStatus; - } - - /** - * Calculating a moving average for the speed so we don't get jumpy - * calculations for time etc. - */ - static private final float SMOOTHING_FACTOR = 0.005f; - - public void notifyUpdateBytes(long totalBytesSoFar) { - long timeRemaining; - long currentTime = SystemClock.uptimeMillis(); - if (0 != mMillisecondsAtSample) { - // we have a sample. - long timePassed = currentTime - mMillisecondsAtSample; - long bytesInSample = totalBytesSoFar - mBytesAtSample; - float currentSpeedSample = (float) bytesInSample / (float) timePassed; - if (0 != mAverageDownloadSpeed) { - mAverageDownloadSpeed = SMOOTHING_FACTOR * currentSpeedSample - + (1 - SMOOTHING_FACTOR) * mAverageDownloadSpeed; - } else { - mAverageDownloadSpeed = currentSpeedSample; - } - timeRemaining = (long) ((mTotalLength - totalBytesSoFar) / mAverageDownloadSpeed); - } else { - timeRemaining = -1; - } - mMillisecondsAtSample = currentTime; - mBytesAtSample = totalBytesSoFar; - mNotification.onDownloadProgress( - new DownloadProgressInfo(mTotalLength, - totalBytesSoFar, - timeRemaining, - mAverageDownloadSpeed) - ); - - } - - @Override - protected boolean shouldStop() { - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(this); - if (db.mStatus == 0) { - return true; - } - return false; - } - - @Override - public void requestDownloadStatus() { - mNotification.resendState(); - } - - @Override - public void onClientUpdated(Messenger clientMessenger) { - this.mClientMessenger = clientMessenger; - mNotification.setMessenger(mClientMessenger); - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java deleted file mode 100755 index 250299c400..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDoneException; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteStatement; -import android.provider.BaseColumns; -import android.util.Log; - -public class DownloadsDB { - private static final String DATABASE_NAME = "DownloadsDB"; - private static final int DATABASE_VERSION = 7; - public static final String LOG_TAG = DownloadsDB.class.getName(); - final SQLiteOpenHelper mHelper; - SQLiteStatement mGetDownloadByIndex; - SQLiteStatement mUpdateCurrentBytes; - private static DownloadsDB mDownloadsDB; - long mMetadataRowID = -1; - int mVersionCode = -1; - int mStatus = -1; - int mFlags; - - static public synchronized DownloadsDB getDB(Context paramContext) { - if (null == mDownloadsDB) { - return new DownloadsDB(paramContext); - } - return mDownloadsDB; - } - - private SQLiteStatement getDownloadByIndexStatement() { - if (null == mGetDownloadByIndex) { - mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement( - "SELECT " + BaseColumns._ID + " FROM " - + DownloadColumns.TABLE_NAME + " WHERE " - + DownloadColumns.INDEX + " = ?"); - } - return mGetDownloadByIndex; - } - - private SQLiteStatement getUpdateCurrentBytesStatement() { - if (null == mUpdateCurrentBytes) { - mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement( - "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES - + " = ?" + - " WHERE " + DownloadColumns.INDEX + " = ?"); - } - return mUpdateCurrentBytes; - } - - private DownloadsDB(Context paramContext) { - this.mHelper = new DownloadsContentDBHelper(paramContext); - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - // Query for the version code, the row ID of the metadata (for future - // updating) the status and the flags - Cursor cur = sqldb.rawQuery("SELECT " + - MetadataColumns.APKVERSION + "," + - BaseColumns._ID + "," + - MetadataColumns.DOWNLOAD_STATUS + "," + - MetadataColumns.FLAGS + - " FROM " - + MetadataColumns.TABLE_NAME + " LIMIT 1", null); - if (null != cur && cur.moveToFirst()) { - mVersionCode = cur.getInt(0); - mMetadataRowID = cur.getLong(1); - mStatus = cur.getInt(2); - mFlags = cur.getInt(3); - cur.close(); - } - mDownloadsDB = this; - } - - protected DownloadInfo getDownloadInfoByFileName(String fileName) { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor itemcur = null; - try { - itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, - DownloadColumns.FILENAME + " = ?", - new String[] { - fileName - }, null, null, null); - if (null != itemcur && itemcur.moveToFirst()) { - return getDownloadInfoFromCursor(itemcur); - } - } finally { - if (null != itemcur) - itemcur.close(); - } - return null; - } - - public long getIDForDownloadInfo(final DownloadInfo di) { - return getIDByIndex(di.mIndex); - } - - public long getIDByIndex(int index) { - SQLiteStatement downloadByIndex = getDownloadByIndexStatement(); - downloadByIndex.clearBindings(); - downloadByIndex.bindLong(1, index); - try { - return downloadByIndex.simpleQueryForLong(); - } catch (SQLiteDoneException e) { - return -1; - } - } - - public void updateDownloadCurrentBytes(final DownloadInfo di) { - SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement(); - downloadCurrentBytes.clearBindings(); - downloadCurrentBytes.bindLong(1, di.mCurrentBytes); - downloadCurrentBytes.bindLong(2, di.mIndex); - downloadCurrentBytes.execute(); - } - - public void close() { - this.mHelper.close(); - } - - protected static class DownloadsContentDBHelper extends SQLiteOpenHelper { - DownloadsContentDBHelper(Context paramContext) { - super(paramContext, DATABASE_NAME, null, DATABASE_VERSION); - } - - private String createTableQueryFromArray(String paramString, - String[][] paramArrayOfString) { - StringBuilder localStringBuilder = new StringBuilder(); - localStringBuilder.append("CREATE TABLE "); - localStringBuilder.append(paramString); - localStringBuilder.append(" ("); - int i = paramArrayOfString.length; - for (int j = 0;; j++) { - if (j >= i) { - localStringBuilder - .setLength(localStringBuilder.length() - 1); - localStringBuilder.append(");"); - return localStringBuilder.toString(); - } - String[] arrayOfString = paramArrayOfString[j]; - localStringBuilder.append(' '); - localStringBuilder.append(arrayOfString[0]); - localStringBuilder.append(' '); - localStringBuilder.append(arrayOfString[1]); - localStringBuilder.append(','); - } - } - - /** - * These two arrays must match and have the same order. For every Schema - * there must be a corresponding table name. - */ - static final private String[][][] sSchemas = { - DownloadColumns.SCHEMA, MetadataColumns.SCHEMA - }; - - static final private String[] sTables = { - DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME - }; - - /** - * Goes through all of the tables in sTables and drops each table if it - * exists. Altered to no longer make use of reflection. - */ - private void dropTables(SQLiteDatabase paramSQLiteDatabase) { - for (String table : sTables) { - try { - paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table); - } catch (Exception localException) { - localException.printStackTrace(); - } - } - } - - /** - * Goes through all of the tables in sTables and creates a database with - * the corresponding schema described in sSchemas. Altered to no longer - * make use of reflection. - */ - public void onCreate(SQLiteDatabase paramSQLiteDatabase) { - int numSchemas = sSchemas.length; - for (int i = 0; i < numSchemas; i++) { - try { - String[][] schema = (String[][]) sSchemas[i]; - paramSQLiteDatabase.execSQL(createTableQueryFromArray( - sTables[i], schema)); - } catch (Exception localException) { - while (true) - localException.printStackTrace(); - } - } - } - - public void onUpgrade(SQLiteDatabase paramSQLiteDatabase, - int paramInt1, int paramInt2) { - Log.w(DownloadsContentDBHelper.class.getName(), - "Upgrading database from version " + paramInt1 + " to " - + paramInt2 + ", which will destroy all old data"); - dropTables(paramSQLiteDatabase); - onCreate(paramSQLiteDatabase); - } - } - - public static class MetadataColumns implements BaseColumns { - public static final String APKVERSION = "APKVERSION"; - public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS"; - public static final String FLAGS = "DOWNLOADFLAGS"; - - public static final String[][] SCHEMA = { - { - BaseColumns._ID, "INTEGER PRIMARY KEY" - }, - { - APKVERSION, "INTEGER" - }, { - DOWNLOAD_STATUS, "INTEGER" - }, - { - FLAGS, "INTEGER" - } - }; - public static final String TABLE_NAME = "MetadataColumns"; - public static final String _ID = "MetadataColumns._id"; - } - - public static class DownloadColumns implements BaseColumns { - public static final String INDEX = "FILEIDX"; - public static final String URI = "URI"; - public static final String FILENAME = "FN"; - public static final String ETAG = "ETAG"; - - public static final String TOTALBYTES = "TOTALBYTES"; - public static final String CURRENTBYTES = "CURRENTBYTES"; - public static final String LASTMOD = "LASTMOD"; - - public static final String STATUS = "STATUS"; - public static final String CONTROL = "CONTROL"; - public static final String NUM_FAILED = "FAILCOUNT"; - public static final String RETRY_AFTER = "RETRYAFTER"; - public static final String REDIRECT_COUNT = "REDIRECTCOUNT"; - - public static final String[][] SCHEMA = { - { - BaseColumns._ID, "INTEGER PRIMARY KEY" - }, - { - INDEX, "INTEGER UNIQUE" - }, { - URI, "TEXT" - }, - { - FILENAME, "TEXT UNIQUE" - }, { - ETAG, "TEXT" - }, - { - TOTALBYTES, "INTEGER" - }, { - CURRENTBYTES, "INTEGER" - }, - { - LASTMOD, "INTEGER" - }, { - STATUS, "INTEGER" - }, - { - CONTROL, "INTEGER" - }, { - NUM_FAILED, "INTEGER" - }, - { - RETRY_AFTER, "INTEGER" - }, { - REDIRECT_COUNT, "INTEGER" - } - }; - public static final String TABLE_NAME = "DownloadColumns"; - public static final String _ID = "DownloadColumns._id"; - } - - private static final String[] DC_PROJECTION = { - DownloadColumns.FILENAME, - DownloadColumns.URI, DownloadColumns.ETAG, - DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES, - DownloadColumns.LASTMOD, DownloadColumns.STATUS, - DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED, - DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT, - DownloadColumns.INDEX - }; - - private static final int FILENAME_IDX = 0; - private static final int URI_IDX = 1; - private static final int ETAG_IDX = 2; - private static final int TOTALBYTES_IDX = 3; - private static final int CURRENTBYTES_IDX = 4; - private static final int LASTMOD_IDX = 5; - private static final int STATUS_IDX = 6; - private static final int CONTROL_IDX = 7; - private static final int NUM_FAILED_IDX = 8; - private static final int RETRY_AFTER_IDX = 9; - private static final int REDIRECT_COUNT_IDX = 10; - private static final int INDEX_IDX = 11; - - /** - * This function will add a new file to the database if it does not exist. - * - * @param di DownloadInfo that we wish to store - * @return the row id of the record to be updated/inserted, or -1 - */ - public boolean updateDownload(DownloadInfo di) { - ContentValues cv = new ContentValues(); - cv.put(DownloadColumns.INDEX, di.mIndex); - cv.put(DownloadColumns.FILENAME, di.mFileName); - cv.put(DownloadColumns.URI, di.mUri); - cv.put(DownloadColumns.ETAG, di.mETag); - cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes); - cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes); - cv.put(DownloadColumns.LASTMOD, di.mLastMod); - cv.put(DownloadColumns.STATUS, di.mStatus); - cv.put(DownloadColumns.CONTROL, di.mControl); - cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed); - cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter); - cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount); - return updateDownload(di, cv); - } - - public boolean updateDownload(DownloadInfo di, ContentValues cv) { - long id = di == null ? -1 : getIDForDownloadInfo(di); - try { - final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); - if (id != -1) { - if (1 != sqldb.update(DownloadColumns.TABLE_NAME, - cv, DownloadColumns._ID + " = " + id, null)) { - return false; - } - } else { - return -1 != sqldb.insert(DownloadColumns.TABLE_NAME, - DownloadColumns.URI, cv); - } - } catch (android.database.sqlite.SQLiteException ex) { - ex.printStackTrace(); - } - return false; - } - - public int getLastCheckedVersionCode() { - return mVersionCode; - } - - public boolean isDownloadRequired() { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM " - + DownloadColumns.TABLE_NAME + " WHERE " - + DownloadColumns.STATUS + " <> 0", null); - try { - if (null != cur && cur.moveToFirst()) { - return 0 == cur.getInt(0); - } - } finally { - if (null != cur) - cur.close(); - } - return true; - } - - public int getFlags() { - return mFlags; - } - - public boolean updateFlags(int flags) { - if (mFlags != flags) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.FLAGS, flags); - if (updateMetadata(cv)) { - mFlags = flags; - return true; - } else { - return false; - } - } else { - return true; - } - }; - - public boolean updateStatus(int status) { - if (mStatus != status) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.DOWNLOAD_STATUS, status); - if (updateMetadata(cv)) { - mStatus = status; - return true; - } else { - return false; - } - } else { - return true; - } - }; - - public boolean updateMetadata(ContentValues cv) { - final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); - if (-1 == this.mMetadataRowID) { - long newID = sqldb.insert(MetadataColumns.TABLE_NAME, - MetadataColumns.APKVERSION, cv); - if (-1 == newID) - return false; - mMetadataRowID = newID; - } else { - if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv, - BaseColumns._ID + " = " + mMetadataRowID, null)) - return false; - } - return true; - } - - public boolean updateMetadata(int apkVersion, int downloadStatus) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.APKVERSION, apkVersion); - cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus); - if (updateMetadata(cv)) { - mVersionCode = apkVersion; - mStatus = downloadStatus; - return true; - } else { - return false; - } - }; - - public boolean updateFromDb(DownloadInfo di) { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = null; - try { - cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, - DownloadColumns.FILENAME + "= ?", - new String[] { - di.mFileName - }, null, null, null); - if (null != cur && cur.moveToFirst()) { - setDownloadInfoFromCursor(di, cur); - return true; - } - return false; - } finally { - if (null != cur) { - cur.close(); - } - } - } - - public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) { - di.mUri = cur.getString(URI_IDX); - di.mETag = cur.getString(ETAG_IDX); - di.mTotalBytes = cur.getLong(TOTALBYTES_IDX); - di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX); - di.mLastMod = cur.getLong(LASTMOD_IDX); - di.mStatus = cur.getInt(STATUS_IDX); - di.mControl = cur.getInt(CONTROL_IDX); - di.mNumFailed = cur.getInt(NUM_FAILED_IDX); - di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX); - di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX); - } - - public DownloadInfo getDownloadInfoFromCursor(Cursor cur) { - DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX), - cur.getString(FILENAME_IDX), this.getClass().getPackage() - .getName()); - setDownloadInfoFromCursor(di, cur); - return di; - } - - public DownloadInfo[] getDownloads() { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = null; - try { - cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null, - null, null, null, null); - if (null != cur && cur.moveToFirst()) { - DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()]; - int idx = 0; - do { - DownloadInfo di = getDownloadInfoFromCursor(cur); - retInfos[idx++] = di; - } while (cur.moveToNext()); - return retInfos; - } - return null; - } finally { - if (null != cur) { - cur.close(); - } - } - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java deleted file mode 100644 index 3f440e9893..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.text.format.Time; - -import java.util.Calendar; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Helper for parsing an HTTP date. - */ -public final class HttpDateTime { - - /* - * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT - * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850, - * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format - * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon - * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS - * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon - * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first - * digit is zero. Mon can be the full name of the month. - */ - private static final String HTTP_DATE_RFC_REGEXP = - "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" - + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; - - private static final String HTTP_DATE_ANSIC_REGEXP = - "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" - + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; - - /** - * The compiled version of the HTTP-date regular expressions. - */ - private static final Pattern HTTP_DATE_RFC_PATTERN = - Pattern.compile(HTTP_DATE_RFC_REGEXP); - private static final Pattern HTTP_DATE_ANSIC_PATTERN = - Pattern.compile(HTTP_DATE_ANSIC_REGEXP); - - private static class TimeOfDay { - TimeOfDay(int h, int m, int s) { - this.hour = h; - this.minute = m; - this.second = s; - } - - int hour; - int minute; - int second; - } - - public static long parse(String timeString) - throws IllegalArgumentException { - - int date = 1; - int month = Calendar.JANUARY; - int year = 1970; - TimeOfDay timeOfDay; - - Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); - if (rfcMatcher.find()) { - date = getDate(rfcMatcher.group(1)); - month = getMonth(rfcMatcher.group(2)); - year = getYear(rfcMatcher.group(3)); - timeOfDay = getTime(rfcMatcher.group(4)); - } else { - Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); - if (ansicMatcher.find()) { - month = getMonth(ansicMatcher.group(1)); - date = getDate(ansicMatcher.group(2)); - timeOfDay = getTime(ansicMatcher.group(3)); - year = getYear(ansicMatcher.group(4)); - } else { - throw new IllegalArgumentException(); - } - } - - // FIXME: Y2038 BUG! - if (year >= 2038) { - year = 2038; - month = Calendar.JANUARY; - date = 1; - } - - Time time = new Time(Time.TIMEZONE_UTC); - time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, - month, year); - return time.toMillis(false /* use isDst */); - } - - private static int getDate(String dateString) { - if (dateString.length() == 2) { - return (dateString.charAt(0) - '0') * 10 - + (dateString.charAt(1) - '0'); - } else { - return (dateString.charAt(0) - '0'); - } - } - - /* - * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0 - * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20 - * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19 - * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9 - */ - private static int getMonth(String monthString) { - int hash = Character.toLowerCase(monthString.charAt(0)) + - Character.toLowerCase(monthString.charAt(1)) + - Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; - switch (hash) { - case 22: - return Calendar.JANUARY; - case 10: - return Calendar.FEBRUARY; - case 29: - return Calendar.MARCH; - case 32: - return Calendar.APRIL; - case 36: - return Calendar.MAY; - case 42: - return Calendar.JUNE; - case 40: - return Calendar.JULY; - case 26: - return Calendar.AUGUST; - case 37: - return Calendar.SEPTEMBER; - case 35: - return Calendar.OCTOBER; - case 48: - return Calendar.NOVEMBER; - case 9: - return Calendar.DECEMBER; - default: - throw new IllegalArgumentException(); - } - } - - private static int getYear(String yearString) { - if (yearString.length() == 2) { - int year = (yearString.charAt(0) - '0') * 10 - + (yearString.charAt(1) - '0'); - if (year >= 70) { - return year + 1900; - } else { - return year + 2000; - } - } else if (yearString.length() == 3) { - // According to RFC 2822, three digit years should be added to 1900. - int year = (yearString.charAt(0) - '0') * 100 - + (yearString.charAt(1) - '0') * 10 - + (yearString.charAt(2) - '0'); - return year + 1900; - } else if (yearString.length() == 4) { - return (yearString.charAt(0) - '0') * 1000 - + (yearString.charAt(1) - '0') * 100 - + (yearString.charAt(2) - '0') * 10 - + (yearString.charAt(3) - '0'); - } else { - return 1970; - } - } - - private static TimeOfDay getTime(String timeString) { - // HH might be H - int i = 0; - int hour = timeString.charAt(i++) - '0'; - if (timeString.charAt(i) != ':') - hour = hour * 10 + (timeString.charAt(i++) - '0'); - // Skip ':' - i++; - - int minute = (timeString.charAt(i++) - '0') * 10 - + (timeString.charAt(i++) - '0'); - // Skip ':' - i++; - - int second = (timeString.charAt(i++) - '0') * 10 - + (timeString.charAt(i++) - '0'); - - return new TimeOfDay(hour, minute, second); - } -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java deleted file mode 100644 index e736603e2a..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.android.vending.expansion.downloader.R; -import com.google.android.vending.expansion.downloader.Helpers; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; - -public class V14CustomNotification implements DownloadNotification.ICustomNotification { - - CharSequence mTitle; - CharSequence mTicker; - int mIcon; - long mTotalKB = -1; - long mCurrentKB = -1; - long mTimeRemaining; - PendingIntent mPendingIntent; - - @Override - public void setIcon(int icon) { - mIcon = icon; - } - - @Override - public void setTitle(CharSequence title) { - mTitle = title; - } - - @Override - public void setTotalBytes(long totalBytes) { - mTotalKB = totalBytes; - } - - @Override - public void setCurrentBytes(long currentBytes) { - mCurrentKB = currentBytes; - } - - void setProgress(Notification.Builder builder) { - - } - - @Override - public Notification updateNotification(Context c) { - Notification.Builder builder = new Notification.Builder(c); - builder.setContentTitle(mTitle); - if (mTotalKB > 0 && -1 != mCurrentKB) { - builder.setProgress((int) (mTotalKB >> 8), (int) (mCurrentKB >> 8), false); - } else { - builder.setProgress(0, 0, true); - } - builder.setContentText(Helpers.getDownloadProgressString(mCurrentKB, mTotalKB)); - builder.setContentInfo(c.getString(R.string.time_remaining_notification, - Helpers.getTimeRemaining(mTimeRemaining))); - if (mIcon != 0) { - builder.setSmallIcon(mIcon); - } else { - int iconResource = android.R.drawable.stat_sys_download; - builder.setSmallIcon(iconResource); - } - builder.setOngoing(true); - builder.setTicker(mTicker); - builder.setContentIntent(mPendingIntent); - builder.setOnlyAlertOnce(true); - - return builder.getNotification(); - } - - @Override - public void setPendingIntent(PendingIntent contentIntent) { - mPendingIntent = contentIntent; - } - - @Override - public void setTicker(CharSequence ticker) { - mTicker = ticker; - } - - @Override - public void setTimeRemaining(long timeRemaining) { - mTimeRemaining = timeRemaining; - } - -} diff --git a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java b/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java deleted file mode 100644 index e3666e05b9..0000000000 --- a/platform/android/libs/apk_expansion/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.android.vending.expansion.downloader.R; -import com.google.android.vending.expansion.downloader.Helpers; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; -import android.graphics.BitmapFactory; -import android.view.View; -import android.widget.RemoteViews; - -public class V3CustomNotification implements DownloadNotification.ICustomNotification { - - CharSequence mTitle; - CharSequence mTicker; - int mIcon; - long mTotalBytes = -1; - long mCurrentBytes = -1; - long mTimeRemaining; - PendingIntent mPendingIntent; - Notification mNotification = new Notification(); - - @Override - public void setIcon(int icon) { - mIcon = icon; - } - - @Override - public void setTitle(CharSequence title) { - mTitle = title; - } - - @Override - public void setTotalBytes(long totalBytes) { - mTotalBytes = totalBytes; - } - - @Override - public void setCurrentBytes(long currentBytes) { - mCurrentBytes = currentBytes; - } - - @Override - public Notification updateNotification(Context c) { - Notification n = mNotification; - - n.icon = mIcon; - - n.flags |= Notification.FLAG_ONGOING_EVENT; - - if (android.os.Build.VERSION.SDK_INT > 10) { - n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // only matters for - // Honeycomb - } - - // Build the RemoteView object - RemoteViews expandedView = new RemoteViews( - c.getPackageName(), - R.layout.status_bar_ongoing_event_progress_bar); - - expandedView.setTextViewText(R.id.title, mTitle); - // look at strings - expandedView.setViewVisibility(R.id.description, View.VISIBLE); - expandedView.setTextViewText(R.id.description, - Helpers.getDownloadProgressString(mCurrentBytes, mTotalBytes)); - expandedView.setViewVisibility(R.id.progress_bar_frame, View.VISIBLE); - expandedView.setProgressBar(R.id.progress_bar, - (int) (mTotalBytes >> 8), - (int) (mCurrentBytes >> 8), - mTotalBytes <= 0); - expandedView.setViewVisibility(R.id.time_remaining, View.VISIBLE); - expandedView.setTextViewText( - R.id.time_remaining, - c.getString(R.string.time_remaining_notification, - Helpers.getTimeRemaining(mTimeRemaining))); - expandedView.setTextViewText(R.id.progress_text, - Helpers.getDownloadProgressPercent(mCurrentBytes, mTotalBytes)); - expandedView.setImageViewResource(R.id.appIcon, mIcon); - n.contentView = expandedView; - n.contentIntent = mPendingIntent; - return n; - } - - @Override - public void setPendingIntent(PendingIntent contentIntent) { - mPendingIntent = contentIntent; - } - - @Override - public void setTicker(CharSequence ticker) { - mTicker = ticker; - } - - @Override - public void setTimeRemaining(long timeRemaining) { - mTimeRemaining = timeRemaining; - } - -} diff --git a/platform/android/libs/downloader_library/.classpath b/platform/android/libs/downloader_library/.classpath deleted file mode 100644 index 7bc01d9a9c..0000000000 --- a/platform/android/libs/downloader_library/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/platform/android/libs/downloader_library/.settings/org.eclipse.jdt.core.prefs b/platform/android/libs/downloader_library/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index b080d2ddc8..0000000000 --- a/platform/android/libs/downloader_library/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/platform/android/libs/downloader_library/AndroidManifest.xml b/platform/android/libs/downloader_library/AndroidManifest.xml deleted file mode 100644 index 20b74a2988..0000000000 --- a/platform/android/libs/downloader_library/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/platform/android/libs/downloader_library/build.xml b/platform/android/libs/downloader_library/build.xml deleted file mode 100644 index d65c145148..0000000000 --- a/platform/android/libs/downloader_library/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/libs/downloader_library/proguard-project.txt b/platform/android/libs/downloader_library/proguard-project.txt deleted file mode 100644 index f2fe1559a2..0000000000 --- a/platform/android/libs/downloader_library/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/platform/android/libs/downloader_library/project.properties b/platform/android/libs/downloader_library/project.properties deleted file mode 100644 index eda83430bf..0000000000 --- a/platform/android/libs/downloader_library/project.properties +++ /dev/null @@ -1,13 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-15 -android.library=true -android.library.reference.1=../play_licensing diff --git a/platform/android/libs/downloader_library/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/libs/downloader_library/res/drawable-hdpi/notify_panel_notification_icon_bg.png deleted file mode 100644 index f5b762ecf3..0000000000 Binary files a/platform/android/libs/downloader_library/res/drawable-hdpi/notify_panel_notification_icon_bg.png and /dev/null differ diff --git a/platform/android/libs/downloader_library/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/libs/downloader_library/res/drawable-mdpi/notify_panel_notification_icon_bg.png deleted file mode 100644 index 9ecb8af06c..0000000000 Binary files a/platform/android/libs/downloader_library/res/drawable-mdpi/notify_panel_notification_icon_bg.png and /dev/null differ diff --git a/platform/android/libs/downloader_library/res/layout/status_bar_ongoing_event_progress_bar.xml b/platform/android/libs/downloader_library/res/layout/status_bar_ongoing_event_progress_bar.xml deleted file mode 100644 index 23bac02294..0000000000 --- a/platform/android/libs/downloader_library/res/layout/status_bar_ongoing_event_progress_bar.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/platform/android/libs/downloader_library/res/values-v11/styles.xml b/platform/android/libs/downloader_library/res/values-v11/styles.xml deleted file mode 100644 index f2013bc0bf..0000000000 --- a/platform/android/libs/downloader_library/res/values-v11/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/platform/android/libs/downloader_library/res/values-v9/styles.xml b/platform/android/libs/downloader_library/res/values-v9/styles.xml deleted file mode 100644 index 736e77a5d6..0000000000 --- a/platform/android/libs/downloader_library/res/values-v9/styles.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Constants.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Constants.java deleted file mode 100644 index ff2c6f535a..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Constants.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import java.io.File; - - -/** - * Contains the internal constants that are used in the download manager. - * As a general rule, modifying these constants should be done with care. - */ -public class Constants { - /** Tag used for debugging/logging */ - public static final String TAG = "LVLDL"; - - /** - * Expansion path where we store obb files - */ - public static final String EXP_PATH = File.separator + "Android" - + File.separator + "obb" + File.separator; - - /** The intent that gets sent when the service must wake up for a retry */ - public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; - - /** the intent that gets sent when clicking a successful download */ - public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; - - /** the intent that gets sent when clicking an incomplete/failed download */ - public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; - - /** the intent that gets sent when deleting the notification of a completed download */ - public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; - - /** - * When a number has to be appended to the filename, this string is used to separate the - * base filename from the sequence number - */ - public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; - - /** The default user agent used for downloads */ - public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; - - /** The buffer size used to stream the data */ - public static final int BUFFER_SIZE = 4096; - - /** The minimum amount of progress that has to be done before the progress bar gets updated */ - public static final int MIN_PROGRESS_STEP = 4096; - - /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */ - public static final long MIN_PROGRESS_TIME = 1000; - - /** The maximum number of rows in the database (FIFO) */ - public static final int MAX_DOWNLOADS = 1000; - - /** - * The number of times that the download manager will retry its network - * operations when no progress is happening before it gives up. - */ - public static final int MAX_RETRIES = 5; - - /** - * The minimum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MIN_RETRY_AFTER = 30; // 30s - - /** - * The maximum amount of time that the download manager accepts for - * a Retry-After response header with a parameter in delta-seconds. - */ - public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h - - /** - * The maximum number of redirects. - */ - public static final int MAX_REDIRECTS = 5; // can't be more than 7. - - /** - * The time between a failure and the first retry after an IOException. - * Each subsequent retry grows exponentially, doubling each time. - * The time is in seconds. - */ - public static final int RETRY_FIRST_DELAY = 30; - - /** Enable separate connectivity logging */ - public static final boolean LOGX = true; - - /** Enable verbose logging */ - public static final boolean LOGV = false; - - /** Enable super-verbose logging */ - private static final boolean LOCAL_LOGVV = false; - public static final boolean LOGVV = LOCAL_LOGVV && LOGV; - - /** - * This download has successfully completed. - * Warning: there might be other status values that indicate success - * in the future. - * Use isSucccess() to capture the entire category. - */ - public static final int STATUS_SUCCESS = 200; - - /** - * This request couldn't be parsed. This is also used when processing - * requests with unknown/unsupported URI schemes. - */ - public static final int STATUS_BAD_REQUEST = 400; - - /** - * This download can't be performed because the content type cannot be - * handled. - */ - public static final int STATUS_NOT_ACCEPTABLE = 406; - - /** - * This download cannot be performed because the length cannot be - * determined accurately. This is the code for the HTTP error "Length - * Required", which is typically used when making requests that require - * a content length but don't have one, and it is also used in the - * client when a response is received whose length cannot be determined - * accurately (therefore making it impossible to know when a download - * completes). - */ - public static final int STATUS_LENGTH_REQUIRED = 411; - - /** - * This download was interrupted and cannot be resumed. - * This is the code for the HTTP error "Precondition Failed", and it is - * also used in situations where the client doesn't have an ETag at all. - */ - public static final int STATUS_PRECONDITION_FAILED = 412; - - /** - * The lowest-valued error status that is not an actual HTTP status code. - */ - public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; - - /** - * The requested destination file already exists. - */ - public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; - - /** - * Some possibly transient error occurred, but we can't resume the download. - */ - public static final int STATUS_CANNOT_RESUME = 489; - - /** - * This download was canceled - */ - public static final int STATUS_CANCELED = 490; - - /** - * This download has completed with an error. - * Warning: there will be other status values that indicate errors in - * the future. Use isStatusError() to capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of a storage issue. - * Typically, that's because the filesystem is missing or full. - * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} - * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. - */ - public static final int STATUS_FILE_ERROR = 492; - - /** - * This download couldn't be completed because of an HTTP - * redirect response that the download manager couldn't - * handle. - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed because of an - * unspecified unhandled HTTP code. - */ - public static final int STATUS_UNHANDLED_HTTP_CODE = 494; - - /** - * This download couldn't be completed because of an - * error receiving or processing data at the HTTP level. - */ - public static final int STATUS_HTTP_DATA_ERROR = 495; - - /** - * This download couldn't be completed because of an - * HttpException while setting up the request. - */ - public static final int STATUS_HTTP_EXCEPTION = 496; - - /** - * This download couldn't be completed because there were - * too many redirects. - */ - public static final int STATUS_TOO_MANY_REDIRECTS = 497; - - /** - * This download couldn't be completed due to insufficient storage - * space. Typically, this is because the SD card is full. - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; - - /** - * This download couldn't be completed because no external storage - * device was found. Typically, this is because the SD card is not - * mounted. - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - - /** - * The wake duration to check to see if a download is possible. - */ - public static final long WATCHDOG_WAKE_TIMER = 60*1000; - - /** - * The wake duration to check to see if the process was killed. - */ - public static final long ACTIVE_THREAD_WATCHDOG = 5*1000; - -} \ No newline at end of file diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java deleted file mode 100644 index 9cb294d721..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.os.Parcel; -import android.os.Parcelable; - - -/** - * This class contains progress information about the active download(s). - * - * When you build the Activity that initiates a download and tracks the - * progress by implementing the {@link IDownloaderClient} interface, you'll - * receive a DownloadProgressInfo object in each call to the {@link - * IDownloaderClient#onDownloadProgress} method. This allows you to update - * your activity's UI with information about the download progress, such - * as the progress so far, time remaining and current speed. - */ -public class DownloadProgressInfo implements Parcelable { - public long mOverallTotal; - public long mOverallProgress; - public long mTimeRemaining; // time remaining - public float mCurrentSpeed; // speed in KB/S - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel p, int i) { - p.writeLong(mOverallTotal); - p.writeLong(mOverallProgress); - p.writeLong(mTimeRemaining); - p.writeFloat(mCurrentSpeed); - } - - public DownloadProgressInfo(Parcel p) { - mOverallTotal = p.readLong(); - mOverallProgress = p.readLong(); - mTimeRemaining = p.readLong(); - mCurrentSpeed = p.readFloat(); - } - - public DownloadProgressInfo(long overallTotal, long overallProgress, - long timeRemaining, - float currentSpeed) { - this.mOverallTotal = overallTotal; - this.mOverallProgress = overallProgress; - this.mTimeRemaining = timeRemaining; - this.mCurrentSpeed = currentSpeed; - } - - public static final Creator CREATOR = new Creator() { - @Override - public DownloadProgressInfo createFromParcel(Parcel parcel) { - return new DownloadProgressInfo(parcel); - } - - @Override - public DownloadProgressInfo[] newArray(int i) { - return new DownloadProgressInfo[i]; - } - }; - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java deleted file mode 100644 index 2201751254..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.util.Log; - - - -/** - * This class binds the service API to your application client. It contains the IDownloaderClient proxy, - * which is used to call functions in your client as well as the Stub, which is used to call functions - * in the client implementation of IDownloaderClient. - * - *

The IPC is implemented using an Android Messenger and a service Binder. The connect method - * should be called whenever the client wants to bind to the service. It opens up a service connection - * that ends up calling the onServiceConnected client API that passes the service messenger - * in. If the client wants to be notified by the service, it is responsible for then passing its - * messenger to the service in a separate call. - * - *

Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}. - * - *

When your application first starts, you should first check whether your app's expansion files are - * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which - * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method - * returns a value indicating whether download is required or not. - * - *

If a download is required, {@link #startDownloadServiceIfRequired} begins the download through - * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link - * IStub} object that you need in order to receive calls through your {@link IDownloaderClient} - * interface. - */ -public class DownloaderClientMarshaller { - public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10; - public static final int MSG_ONDOWNLOADPROGRESS = 11; - public static final int MSG_ONSERVICECONNECTED = 12; - - public static final String PARAM_NEW_STATE = "newState"; - public static final String PARAM_PROGRESS = "progress"; - public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; - - public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED; - public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED; - public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED; - - private static class Proxy implements IDownloaderClient { - private Messenger mServiceMessenger; - - @Override - public void onDownloadStateChanged(int newState) { - Bundle params = new Bundle(1); - params.putInt(PARAM_NEW_STATE, newState); - send(MSG_ONDOWNLOADSTATE_CHANGED, params); - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - Bundle params = new Bundle(1); - params.putParcelable(PARAM_PROGRESS, progress); - send(MSG_ONDOWNLOADPROGRESS, params); - } - - private void send(int method, Bundle params) { - Message m = Message.obtain(null, method); - m.setData(params); - try { - mServiceMessenger.send(m); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public Proxy(Messenger msg) { - mServiceMessenger = msg; - } - - @Override - public void onServiceConnected(Messenger m) { - /** - * This is never called through the proxy. - */ - } - } - - private static class Stub implements IStub { - private IDownloaderClient mItf = null; - private Class mDownloaderServiceClass; - private boolean mBound; - private Messenger mServiceMessenger; - private Context mContext; - /** - * Target we publish for clients to send messages to IncomingHandler. - */ - final Messenger mMessenger = new Messenger(new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ONDOWNLOADPROGRESS: - Bundle bun = msg.getData(); - if ( null != mContext ) { - bun.setClassLoader(mContext.getClassLoader()); - DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData() - .getParcelable(PARAM_PROGRESS); - mItf.onDownloadProgress(dpi); - } - break; - case MSG_ONDOWNLOADSTATE_CHANGED: - mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE)); - break; - case MSG_ONSERVICECONNECTED: - mItf.onServiceConnected( - (Messenger) msg.getData().getParcelable(PARAM_MESSENGER)); - break; - } - } - }); - - public Stub(IDownloaderClient itf, Class downloaderService) { - mItf = itf; - mDownloaderServiceClass = downloaderService; - } - - /** - * Class for interacting with the main interface of the service. - */ - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - // This is called when the connection with the service has been - // established, giving us the object we can use to - // interact with the service. We are communicating with the - // service using a Messenger, so here we get a client-side - // representation of that from the raw IBinder object. - mServiceMessenger = new Messenger(service); - mItf.onServiceConnected( - mServiceMessenger); - } - - public void onServiceDisconnected(ComponentName className) { - // This is called when the connection with the service has been - // unexpectedly disconnected -- that is, its process crashed. - mServiceMessenger = null; - } - }; - - @Override - public void connect(Context c) { - mContext = c; - Intent bindIntent = new Intent(c, mDownloaderServiceClass); - bindIntent.putExtra(PARAM_MESSENGER, mMessenger); - if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) { - if ( Constants.LOGVV ) { - Log.d(Constants.TAG, "Service Unbound"); - } - } else { - mBound = true; - } - - } - - @Override - public void disconnect(Context c) { - if (mBound) { - c.unbindService(mConnection); - mBound = false; - } - mContext = null; - } - - @Override - public Messenger getMessenger() { - return mMessenger; - } - } - - /** - * Returns a proxy that will marshal calls to IDownloaderClient methods - * - * @param msg - * @return - */ - public static IDownloaderClient CreateProxy(Messenger msg) { - return new Proxy(msg); - } - - /** - * Returns a stub object that, when connected, will listen for marshaled - * {@link IDownloaderClient} methods and translate them into calls to the supplied - * interface. - * - * @param itf An implementation of IDownloaderClient that will be called - * when remote method calls are unmarshaled. - * @param downloaderService The class for your implementation of {@link - * impl.DownloaderService}. - * @return The {@link IStub} that allows you to connect to the service such that - * your {@link IDownloaderClient} receives status updates. - */ - public static IStub CreateStub(IDownloaderClient itf, Class downloaderService) { - return new Stub(itf, downloaderService); - } - - /** - * Starts the download if necessary. This function starts a flow that does ` - * many things. 1) Checks to see if the APK version has been checked and - * the metadata database updated 2) If the APK version does not match, - * checks the new LVL status to see if a new download is required 3) If the - * APK version does match, then checks to see if the download(s) have been - * completed 4) If the downloads have been completed, returns - * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the - * startup of an application to quickly ascertain if the application needs - * to wait to hear about any updated APK expansion files. Note that this does - * mean that the application MUST be run for the first time with a network - * connection, even if Market delivers all of the files. - * - * @param context Your application Context. - * @param notificationClient A PendingIntent to start the Activity in your application - * that shows the download progress and which will also start the application when download - * completes. - * @param serviceClass the class of your {@link imp.DownloaderService} implementation - * @return whether the service was started and the reason for starting the service. - * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link - * #DOWNLOAD_REQUIRED}. - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, - Class serviceClass) - throws NameNotFoundException { - return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, - serviceClass); - } - - /** - * This version assumes that the intent contains the pending intent as a parameter. This - * is used for responding to alarms. - *

The pending intent must be in an extra with the key {@link - * impl.DownloaderService#EXTRA_PENDING_INTENT}. - * - * @param context - * @param notificationClient - * @param serviceClass the class of the service to start - * @return - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, - Class serviceClass) - throws NameNotFoundException { - return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, - serviceClass); - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java deleted file mode 100644 index 054eaa9895..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; - - - -/** - * This class is used by the client activity to proxy requests to the Downloader - * Service. - * - * Most importantly, you must call {@link #CreateProxy} during the {@link - * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate - * an {@link IDownloaderService} object that you can then use to issue commands to the {@link - * DownloaderService} (such as to pause and resume downloads). - */ -public class DownloaderServiceMarshaller { - - public static final int MSG_REQUEST_ABORT_DOWNLOAD = - 1; - public static final int MSG_REQUEST_PAUSE_DOWNLOAD = - 2; - public static final int MSG_SET_DOWNLOAD_FLAGS = - 3; - public static final int MSG_REQUEST_CONTINUE_DOWNLOAD = - 4; - public static final int MSG_REQUEST_DOWNLOAD_STATE = - 5; - public static final int MSG_REQUEST_CLIENT_UPDATE = - 6; - - public static final String PARAMS_FLAGS = "flags"; - public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; - - private static class Proxy implements IDownloaderService { - private Messenger mMsg; - - private void send(int method, Bundle params) { - Message m = Message.obtain(null, method); - m.setData(params); - try { - mMsg.send(m); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - public Proxy(Messenger msg) { - mMsg = msg; - } - - @Override - public void requestAbortDownload() { - send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle()); - } - - @Override - public void requestPauseDownload() { - send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle()); - } - - @Override - public void setDownloadFlags(int flags) { - Bundle params = new Bundle(); - params.putInt(PARAMS_FLAGS, flags); - send(MSG_SET_DOWNLOAD_FLAGS, params); - } - - @Override - public void requestContinueDownload() { - send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle()); - } - - @Override - public void requestDownloadStatus() { - send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle()); - } - - @Override - public void onClientUpdated(Messenger clientMessenger) { - Bundle bundle = new Bundle(1); - bundle.putParcelable(PARAM_MESSENGER, clientMessenger); - send(MSG_REQUEST_CLIENT_UPDATE, bundle); - } - } - - private static class Stub implements IStub { - private IDownloaderService mItf = null; - final Messenger mMessenger = new Messenger(new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_REQUEST_ABORT_DOWNLOAD: - mItf.requestAbortDownload(); - break; - case MSG_REQUEST_CONTINUE_DOWNLOAD: - mItf.requestContinueDownload(); - break; - case MSG_REQUEST_PAUSE_DOWNLOAD: - mItf.requestPauseDownload(); - break; - case MSG_SET_DOWNLOAD_FLAGS: - mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS)); - break; - case MSG_REQUEST_DOWNLOAD_STATE: - mItf.requestDownloadStatus(); - break; - case MSG_REQUEST_CLIENT_UPDATE: - mItf.onClientUpdated((Messenger) msg.getData().getParcelable( - PARAM_MESSENGER)); - break; - } - } - }); - - public Stub(IDownloaderService itf) { - mItf = itf; - } - - @Override - public Messenger getMessenger() { - return mMessenger; - } - - @Override - public void connect(Context c) { - - } - - @Override - public void disconnect(Context c) { - - } - } - - /** - * Returns a proxy that will marshall calls to IDownloaderService methods - * - * @param ctx - * @return - */ - public static IDownloaderService CreateProxy(Messenger msg) { - return new Proxy(msg); - } - - /** - * Returns a stub object that, when connected, will listen for marshalled - * IDownloaderService methods and translate them into calls to the supplied - * interface. - * - * @param itf An implementation of IDownloaderService that will be called - * when remote method calls are unmarshalled. - * @return - */ - public static IStub CreateStub(IDownloaderService itf) { - return new Stub(itf); - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Helpers.java deleted file mode 100644 index 1e84e54a0f..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/Helpers.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.android.vending.expansion.downloader.R; - -import android.content.Context; -import android.os.Environment; -import android.os.StatFs; -import android.os.SystemClock; -import android.util.Log; - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Random; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Some helper functions for the download manager - */ -public class Helpers { - - public static Random sRandom = new Random(SystemClock.uptimeMillis()); - - /** Regex used to parse content-disposition headers */ - private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern - .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); - - private Helpers() { - } - - /* - * Parse the Content-Disposition HTTP Header. The format of the header is - * defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This - * header provides a filename for content that is going to be downloaded to - * the file system. We only support the attachment type. - */ - static String parseContentDisposition(String contentDisposition) { - try { - Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); - if (m.find()) { - return m.group(1); - } - } catch (IllegalStateException ex) { - // This function is defined as returning null when it can't parse - // the header - } - return null; - } - - /** - * @return the root of the filesystem containing the given path - */ - public static File getFilesystemRoot(String path) { - File cache = Environment.getDownloadCacheDirectory(); - if (path.startsWith(cache.getPath())) { - return cache; - } - File external = Environment.getExternalStorageDirectory(); - if (path.startsWith(external.getPath())) { - return external; - } - throw new IllegalArgumentException( - "Cannot determine filesystem root for " + path); - } - - public static boolean isExternalMediaMounted() { - if (!Environment.getExternalStorageState().equals( - Environment.MEDIA_MOUNTED)) { - // No SD card found. - if ( Constants.LOGVV ) { - Log.d(Constants.TAG, "no external storage"); - } - return false; - } - return true; - } - - /** - * @return the number of bytes available on the filesystem rooted at the - * given File - */ - public static long getAvailableBytes(File root) { - StatFs stat = new StatFs(root.getPath()); - // put a bit of margin (in case creating the file grows the system by a - // few blocks) - long availableBlocks = (long) stat.getAvailableBlocks() - 4; - return stat.getBlockSize() * availableBlocks; - } - - /** - * Checks whether the filename looks legitimate - */ - public static boolean isFilenameValid(String filename) { - filename = filename.replaceFirst("/+", "/"); // normalize leading - // slashes - return filename.startsWith(Environment.getDownloadCacheDirectory().toString()) - || filename.startsWith(Environment.getExternalStorageDirectory().toString()); - } - - /* - * Delete the given file from device - */ - /* package */static void deleteFile(String path) { - try { - File file = new File(path); - file.delete(); - } catch (Exception e) { - Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e); - } - } - - /** - * Showing progress in MB here. It would be nice to choose the unit (KB, MB, - * GB) based on total file size, but given what we know about the expected - * ranges of file sizes for APK expansion files, it's probably not necessary. - * - * @param overallProgress - * @param overallTotal - * @return - */ - - static public String getDownloadProgressString(long overallProgress, long overallTotal) { - if (overallTotal == 0) { - if ( Constants.LOGVV ) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return String.format("%.2f", - (float) overallProgress / (1024.0f * 1024.0f)) - + "MB /" + - String.format("%.2f", (float) overallTotal / - (1024.0f * 1024.0f)) + "MB"; - } - - /** - * Adds a percentile to getDownloadProgressString. - * - * @param overallProgress - * @param overallTotal - * @return - */ - static public String getDownloadProgressStringNotification(long overallProgress, - long overallTotal) { - if (overallTotal == 0) { - if ( Constants.LOGVV ) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return getDownloadProgressString(overallProgress, overallTotal) + " (" + - getDownloadProgressPercent(overallProgress, overallTotal) + ")"; - } - - public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { - if (overallTotal == 0) { - if ( Constants.LOGVV ) { - Log.e(Constants.TAG, "Notification called when total is zero"); - } - return ""; - } - return Long.toString(overallProgress * 100 / overallTotal) + "%"; - } - - public static String getSpeedString(float bytesPerMillisecond) { - return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); - } - - public static String getTimeRemaining(long durationInMilliseconds) { - SimpleDateFormat sdf; - if (durationInMilliseconds > 1000 * 60 * 60) { - sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); - } else { - sdf = new SimpleDateFormat("mm:ss", Locale.getDefault()); - } - return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset())); - } - - /** - * Returns the file name (without full path) for an Expansion APK file from - * the given context. - * - * @param c the context - * @param mainFile true for main file, false for patch file - * @param versionCode the version of the file - * @return String the file name of the expansion file - */ - public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) { - return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb"; - } - - /** - * Returns the filename (where the file should be saved) from info about a - * download - */ - static public String generateSaveFileName(Context c, String fileName) { - String path = getSaveFilePath(c) - + File.separator + fileName; - return path; - } - - static public String getSaveFilePath(Context c) { - File root = Environment.getExternalStorageDirectory(); - String path = root.toString() + Constants.EXP_PATH + c.getPackageName(); - return path; - } - - /** - * Helper function to ascertain the existence of a file and return - * true/false appropriately - * - * @param c the app/activity/service context - * @param fileName the name (sans path) of the file to query - * @param fileSize the size that the file must match - * @param deleteFileOnMismatch if the file sizes do not match, delete the - * file - * @return true if it does exist, false otherwise - */ - static public boolean doesFileExist(Context c, String fileName, long fileSize, - boolean deleteFileOnMismatch) { - // the file may have been delivered by Market --- let's make sure - // it's the size we expect - File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); - if (fileForNewFile.exists()) { - if (fileForNewFile.length() == fileSize) { - return true; - } - if (deleteFileOnMismatch) { - // delete the file --- we won't be able to resume - // because we cannot confirm the integrity of the file - fileForNewFile.delete(); - } - } - return false; - } - - /** - * Converts download states that are returned by the {@link - * IDownloaderClient#onDownloadStateChanged} callback into usable strings. - * This is useful if using the state strings built into the library to display user messages. - * @param state One of the STATE_* constants from {@link IDownloaderClient}. - * @return string resource ID for the corresponding string. - */ - static public int getDownloaderStringResourceIDFromState(int state) { - switch (state) { - case IDownloaderClient.STATE_IDLE: - return R.string.state_idle; - case IDownloaderClient.STATE_FETCHING_URL: - return R.string.state_fetching_url; - case IDownloaderClient.STATE_CONNECTING: - return R.string.state_connecting; - case IDownloaderClient.STATE_DOWNLOADING: - return R.string.state_downloading; - case IDownloaderClient.STATE_COMPLETED: - return R.string.state_completed; - case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE: - return R.string.state_paused_network_unavailable; - case IDownloaderClient.STATE_PAUSED_BY_REQUEST: - return R.string.state_paused_by_request; - case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: - return R.string.state_paused_wifi_disabled; - case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: - return R.string.state_paused_wifi_unavailable; - case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED: - return R.string.state_paused_wifi_disabled; - case IDownloaderClient.STATE_PAUSED_NEED_WIFI: - return R.string.state_paused_wifi_unavailable; - case IDownloaderClient.STATE_PAUSED_ROAMING: - return R.string.state_paused_roaming; - case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE: - return R.string.state_paused_network_setup_failure; - case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: - return R.string.state_paused_sdcard_unavailable; - case IDownloaderClient.STATE_FAILED_UNLICENSED: - return R.string.state_failed_unlicensed; - case IDownloaderClient.STATE_FAILED_FETCHING_URL: - return R.string.state_failed_fetching_url; - case IDownloaderClient.STATE_FAILED_SDCARD_FULL: - return R.string.state_failed_sdcard_full; - case IDownloaderClient.STATE_FAILED_CANCELED: - return R.string.state_failed_cancelled; - default: - return R.string.state_unknown; - } - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java deleted file mode 100644 index b8511a62a0..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderClient.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.os.Messenger; - -/** - * This interface should be implemented by the client activity for the - * downloader. It is used to pass status from the service to the client. - */ -public interface IDownloaderClient { - static final int STATE_IDLE = 1; - static final int STATE_FETCHING_URL = 2; - static final int STATE_CONNECTING = 3; - static final int STATE_DOWNLOADING = 4; - static final int STATE_COMPLETED = 5; - - static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6; - static final int STATE_PAUSED_BY_REQUEST = 7; - - /** - * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and - * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and - * cellular permission will restart the service. Wi-Fi disabled means that - * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the - * other case Wi-Fi is enabled but not available. - */ - static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8; - static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9; - - /** - * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that - * Wi-Fi is unavailable and cellular permission will NOT restart the - * service. Wi-Fi disabled means that the Wi-Fi manager is returning that - * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not - * available. - *

- * The service does not return these values. We recommend that app - * developers with very large payloads do not allow these payloads to be - * downloaded over cellular connections. - */ - static final int STATE_PAUSED_WIFI_DISABLED = 10; - static final int STATE_PAUSED_NEED_WIFI = 11; - - static final int STATE_PAUSED_ROAMING = 12; - - /** - * Scary case. We were on a network that redirected us to another website - * that delivered us the wrong file. - */ - static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13; - - static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; - - static final int STATE_FAILED_UNLICENSED = 15; - static final int STATE_FAILED_FETCHING_URL = 16; - static final int STATE_FAILED_SDCARD_FULL = 17; - static final int STATE_FAILED_CANCELED = 18; - - static final int STATE_FAILED = 19; - - /** - * Called internally by the stub when the service is bound to the client. - *

- * Critical implementation detail. In onServiceConnected we create the - * remote service and marshaler. This is how we pass the client information - * back to the service so the client can be properly notified of changes. We - * must do this every time we reconnect to the service. - *

- * That is, when you receive this callback, you should call - * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member - * instance of {@link IDownloaderService}, then call - * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved - * from your {@link IStub} proxy object. - * - * @param m the service Messenger. This Messenger is used to call the - * service API from the client. - */ - void onServiceConnected(Messenger m); - - /** - * Called when the download state changes. Depending on the state, there may - * be user requests. The service is free to change the download state in the - * middle of a user request, so the client should be able to handle this. - *

- * The Downloader Library includes a collection of string resources that - * correspond to each of the states, which you can use to provide users a - * useful message based on the state provided in this callback. To fetch the - * appropriate string for a state, call - * {@link Helpers#getDownloaderStringResourceIDFromState}. - *

- * What this means to the developer: The application has gotten a message - * that the download has paused due to lack of WiFi. The developer should - * then show UI asking the user if they want to enable downloading over - * cellular connections with appropriate warnings. If the application - * suddenly starts downloading, the application should revert to showing the - * progress again, rather than leaving up the download over cellular UI up. - * - * @param newState one of the STATE_* values defined in IDownloaderClient - */ - void onDownloadStateChanged(int newState); - - /** - * Shows the download progress. This is intended to be used to fill out a - * client UI. This progress should only be shown in a few states such as - * STATE_DOWNLOADING. - * - * @param progress the DownloadProgressInfo object containing the current - * progress of all downloads. - */ - void onDownloadProgress(DownloadProgressInfo progress); -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderService.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderService.java deleted file mode 100644 index 4789afe19c..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IDownloaderService.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import com.google.android.vending.expansion.downloader.impl.DownloaderService; -import android.os.Messenger; - -/** - * This interface is implemented by the DownloaderService and by the - * DownloaderServiceMarshaller. It contains functions to control the service. - * When a client binds to the service, it must call the onClientUpdated - * function. - *

- * You can acquire a proxy that implements this interface for your service by - * calling {@link DownloaderServiceMarshaller#CreateProxy} during the - * {@link IDownloaderClient#onServiceConnected} callback. At which point, you - * should immediately call {@link #onClientUpdated}. - */ -public interface IDownloaderService { - /** - * Set this flag in response to the - * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then - * call RequestContinueDownload to resume a download - */ - public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1; - - /** - * Request that the service abort the current download. The service should - * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}. - */ - void requestAbortDownload(); - - /** - * Request that the service pause the current download. The service should - * respond by changing the state to - * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. - */ - void requestPauseDownload(); - - /** - * Request that the service continue a paused download, when in any paused - * or failed state, including - * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. - */ - void requestContinueDownload(); - - /** - * Set the flags for this download (e.g. - * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). - * - * @param flags - */ - void setDownloadFlags(int flags); - - /** - * Requests that the download status be sent to the client. - */ - void requestDownloadStatus(); - - /** - * Call this when you get {@link - * IDownloaderClient.onServiceConnected(Messenger m)} from the - * DownloaderClient to register the client with the service. It will - * automatically send the current status to the client. - * - * @param clientMessenger - */ - void onClientUpdated(Messenger clientMessenger); -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IStub.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IStub.java deleted file mode 100644 index d5bc3a843e..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/IStub.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.content.Context; -import android.os.Messenger; - -/** - * This is the interface that is used to connect/disconnect from the downloader - * service. - *

- * You should get a proxy object that implements this interface by calling - * {@link DownloaderClientMarshaller#CreateStub} in your activity when the - * downloader service starts. Then, call {@link #connect} during your activity's - * onResume() and call {@link #disconnect} during onStop(). - *

- * Then during the {@link IDownloaderClient#onServiceConnected} callback, you - * should call {@link #getMessenger} to pass the stub's Messenger object to - * {@link IDownloaderService#onClientUpdated}. - */ -public interface IStub { - Messenger getMessenger(); - - void connect(Context c); - - void disconnect(Context c); -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/SystemFacade.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/SystemFacade.java deleted file mode 100644 index 12edd97ab2..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/SystemFacade.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.telephony.TelephonyManager; -import android.util.Log; - -/** - * Contains useful helper functions, typically tied to the application context. - */ -class SystemFacade { - private Context mContext; - private NotificationManager mNotificationManager; - - public SystemFacade(Context context) { - mContext = context; - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - } - - public long currentTimeMillis() { - return System.currentTimeMillis(); - } - - public Integer getActiveNetworkType() { - ConnectivityManager connectivity = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - return null; - } - - NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); - if (activeInfo == null) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is not available"); - } - return null; - } - return activeInfo.getType(); - } - - public boolean isNetworkRoaming() { - ConnectivityManager connectivity = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - if (connectivity == null) { - Log.w(Constants.TAG, "couldn't get connectivity manager"); - return false; - } - - NetworkInfo info = connectivity.getActiveNetworkInfo(); - boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); - TelephonyManager tm = (TelephonyManager) mContext - .getSystemService(Context.TELEPHONY_SERVICE); - if (null == tm) { - Log.w(Constants.TAG, "couldn't get telephony manager"); - return false; - } - boolean isRoaming = isMobile && tm.isNetworkRoaming(); - if (Constants.LOGVV && isRoaming) { - Log.v(Constants.TAG, "network is roaming"); - } - return isRoaming; - } - - public Long getMaxBytesOverMobile() { - return (long) Integer.MAX_VALUE; - } - - public Long getRecommendedMaxBytesOverMobile() { - return 2097152L; - } - - public void sendBroadcast(Intent intent) { - mContext.sendBroadcast(intent); - } - - public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException { - return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid; - } - - public void postNotification(long id, Notification notification) { - /** - * TODO: The system notification manager takes ints, not longs, as IDs, - * but the download manager uses IDs take straight from the database, - * which are longs. This will have to be dealt with at some point. - */ - mNotificationManager.notify((int) id, notification); - } - - public void cancelNotification(long id) { - mNotificationManager.cancel((int) id); - } - - public void cancelAllNotifications() { - mNotificationManager.cancelAll(); - } - - public void startThread(Thread thread) { - thread.start(); - } -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java deleted file mode 100644 index 4667acce67..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/AndroidHttpClient.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * This is a port of AndroidHttpClient to pre-Froyo devices, that takes advantage of - * the SSLSessionCache added Froyo devices using reflection. - */ - -package com.google.android.vending.expansion.downloader.impl; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpException; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpRequestInterceptor; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.params.HttpClientParams; -import org.apache.http.client.protocol.ClientContext; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.scheme.SocketFactory; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.entity.AbstractHttpEntity; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.client.RequestWrapper; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.protocol.BasicHttpContext; -import org.apache.http.protocol.BasicHttpProcessor; -import org.apache.http.protocol.HttpContext; - -import android.content.ContentResolver; -import android.content.Context; -import android.net.SSLCertificateSocketFactory; -import android.os.Looper; -import android.util.Log; - -/** - * Subclass of the Apache {@link DefaultHttpClient} that is configured with - * reasonable default settings and registered schemes for Android, and - * also lets the user add {@link HttpRequestInterceptor} classes. - * Don't create this directly, use the {@link #newInstance} factory method. - * - *

This client processes cookies but does not retain them by default. - * To retain cookies, simply add a cookie store to the HttpContext:

- * - *
context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
- */ -public final class AndroidHttpClient implements HttpClient { - - static Class sSslSessionCacheClass; - static { - // if we are on Froyo+ devices, we can take advantage of the SSLSessionCache - try { - sSslSessionCacheClass = Class.forName("android.net.SSLSessionCache"); - } catch (Exception e) { - - } - } - - // Gzip of data shorter than this probably won't be worthwhile - public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256; - - // Default connection and socket timeout of 60 seconds. Tweak to taste. - private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000; - - private static final String TAG = "AndroidHttpClient"; - - - /** Interceptor throws an exception if the executing thread is blocked */ - private static final HttpRequestInterceptor sThreadCheckInterceptor = - new HttpRequestInterceptor() { - public void process(HttpRequest request, HttpContext context) { - // Prevent the HttpRequest from being sent on the main thread - if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) { - throw new RuntimeException("This thread forbids HTTP requests"); - } - } - }; - - /** - * Create a new HttpClient with reasonable defaults (which you can update). - * - * @param userAgent to report in your HTTP requests - * @param context to use for caching SSL sessions (may be null for no caching) - * @return AndroidHttpClient for you to use for all your requests. - */ - public static AndroidHttpClient newInstance(String userAgent, Context context) { - HttpParams params = new BasicHttpParams(); - - // Turn off stale checking. Our connections break all the time anyway, - // and it's not worth it to pay the penalty of checking every time. - HttpConnectionParams.setStaleCheckingEnabled(params, false); - - HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT); - HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT); - HttpConnectionParams.setSocketBufferSize(params, 8192); - - // Don't handle redirects -- return them to the caller. Our code - // often wants to re-POST after a redirect, which we must do ourselves. - HttpClientParams.setRedirecting(params, false); - - Object sessionCache = null; - // Use a session cache for SSL sockets -- Froyo only - if ( null != context && null != sSslSessionCacheClass ) { - Constructor ct; - try { - ct = sSslSessionCacheClass.getConstructor(Context.class); - sessionCache = ct.newInstance(context); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InstantiationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - // Set the specified user agent and register standard protocols. - HttpProtocolParams.setUserAgent(params, userAgent); - SchemeRegistry schemeRegistry = new SchemeRegistry(); - schemeRegistry.register(new Scheme("http", - PlainSocketFactory.getSocketFactory(), 80)); - SocketFactory sslCertificateSocketFactory = null; - if ( null != sessionCache ) { - Method getHttpSocketFactoryMethod; - try { - getHttpSocketFactoryMethod = SSLCertificateSocketFactory.class.getDeclaredMethod("getHttpSocketFactory",Integer.TYPE, sSslSessionCacheClass); - sslCertificateSocketFactory = (SocketFactory)getHttpSocketFactoryMethod.invoke(null, SOCKET_OPERATION_TIMEOUT, sessionCache); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - if ( null == sslCertificateSocketFactory ) { - sslCertificateSocketFactory = SSLSocketFactory.getSocketFactory(); - } - schemeRegistry.register(new Scheme("https", - sslCertificateSocketFactory, 443)); - - ClientConnectionManager manager = - new ThreadSafeClientConnManager(params, schemeRegistry); - - // We use a factory method to modify superclass initialization - // parameters without the funny call-a-static-method dance. - return new AndroidHttpClient(manager, params); - } - - /** - * Create a new HttpClient with reasonable defaults (which you can update). - * @param userAgent to report in your HTTP requests. - * @return AndroidHttpClient for you to use for all your requests. - */ - public static AndroidHttpClient newInstance(String userAgent) { - return newInstance(userAgent, null /* session cache */); - } - - private final HttpClient delegate; - - private RuntimeException mLeakedException = new IllegalStateException( - "AndroidHttpClient created and never closed"); - - private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) { - this.delegate = new DefaultHttpClient(ccm, params) { - @Override - protected BasicHttpProcessor createHttpProcessor() { - // Add interceptor to prevent making requests from main thread. - BasicHttpProcessor processor = super.createHttpProcessor(); - processor.addRequestInterceptor(sThreadCheckInterceptor); - processor.addRequestInterceptor(new CurlLogger()); - - return processor; - } - - @Override - protected HttpContext createHttpContext() { - // Same as DefaultHttpClient.createHttpContext() minus the - // cookie store. - HttpContext context = new BasicHttpContext(); - context.setAttribute( - ClientContext.AUTHSCHEME_REGISTRY, - getAuthSchemes()); - context.setAttribute( - ClientContext.COOKIESPEC_REGISTRY, - getCookieSpecs()); - context.setAttribute( - ClientContext.CREDS_PROVIDER, - getCredentialsProvider()); - return context; - } - }; - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - if (mLeakedException != null) { - Log.e(TAG, "Leak found", mLeakedException); - mLeakedException = null; - } - } - - /** - * Modifies a request to indicate to the server that we would like a - * gzipped response. (Uses the "Accept-Encoding" HTTP header.) - * @param request the request to modify - * @see #getUngzippedContent - */ - public static void modifyRequestToAcceptGzipResponse(HttpRequest request) { - request.addHeader("Accept-Encoding", "gzip"); - } - - /** - * Gets the input stream from a response entity. If the entity is gzipped - * then this will get a stream over the uncompressed data. - * - * @param entity the entity whose content should be read - * @return the input stream to read from - * @throws IOException - */ - public static InputStream getUngzippedContent(HttpEntity entity) - throws IOException { - InputStream responseStream = entity.getContent(); - if (responseStream == null) return responseStream; - Header header = entity.getContentEncoding(); - if (header == null) return responseStream; - String contentEncoding = header.getValue(); - if (contentEncoding == null) return responseStream; - if (contentEncoding.contains("gzip")) responseStream - = new GZIPInputStream(responseStream); - return responseStream; - } - - /** - * Release resources associated with this client. You must call this, - * or significant resources (sockets and memory) may be leaked. - */ - public void close() { - if (mLeakedException != null) { - getConnectionManager().shutdown(); - mLeakedException = null; - } - } - - public HttpParams getParams() { - return delegate.getParams(); - } - - public ClientConnectionManager getConnectionManager() { - return delegate.getConnectionManager(); - } - - public HttpResponse execute(HttpUriRequest request) throws IOException { - return delegate.execute(request); - } - - public HttpResponse execute(HttpUriRequest request, HttpContext context) - throws IOException { - return delegate.execute(request, context); - } - - public HttpResponse execute(HttpHost target, HttpRequest request) - throws IOException { - return delegate.execute(target, request); - } - - public HttpResponse execute(HttpHost target, HttpRequest request, - HttpContext context) throws IOException { - return delegate.execute(target, request, context); - } - - public T execute(HttpUriRequest request, - ResponseHandler responseHandler) - throws IOException, ClientProtocolException { - return delegate.execute(request, responseHandler); - } - - public T execute(HttpUriRequest request, - ResponseHandler responseHandler, HttpContext context) - throws IOException, ClientProtocolException { - return delegate.execute(request, responseHandler, context); - } - - public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler) throws IOException, - ClientProtocolException { - return delegate.execute(target, request, responseHandler); - } - - public T execute(HttpHost target, HttpRequest request, - ResponseHandler responseHandler, HttpContext context) - throws IOException, ClientProtocolException { - return delegate.execute(target, request, responseHandler, context); - } - - /** - * Compress data to send to server. - * Creates a Http Entity holding the gzipped data. - * The data will not be compressed if it is too short. - * @param data The bytes to compress - * @return Entity holding the data - */ - public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver) - throws IOException { - AbstractHttpEntity entity; - if (data.length < getMinGzipSize(resolver)) { - entity = new ByteArrayEntity(data); - } else { - ByteArrayOutputStream arr = new ByteArrayOutputStream(); - OutputStream zipper = new GZIPOutputStream(arr); - zipper.write(data); - zipper.close(); - entity = new ByteArrayEntity(arr.toByteArray()); - entity.setContentEncoding("gzip"); - } - return entity; - } - - /** - * Retrieves the minimum size for compressing data. - * Shorter data will not be compressed. - */ - public static long getMinGzipSize(ContentResolver resolver) { - return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant. - } - - /* cURL logging support. */ - - /** - * Logging tag and level. - */ - private static class LoggingConfiguration { - - private final String tag; - private final int level; - - private LoggingConfiguration(String tag, int level) { - this.tag = tag; - this.level = level; - } - - /** - * Returns true if logging is turned on for this configuration. - */ - private boolean isLoggable() { - return Log.isLoggable(tag, level); - } - - /** - * Prints a message using this configuration. - */ - private void println(String message) { - Log.println(level, tag, message); - } - } - - /** cURL logging configuration. */ - private volatile LoggingConfiguration curlConfiguration; - - /** - * Enables cURL request logging for this client. - * - * @param name to log messages with - * @param level at which to log messages (see {@link android.util.Log}) - */ - public void enableCurlLogging(String name, int level) { - if (name == null) { - throw new NullPointerException("name"); - } - if (level < Log.VERBOSE || level > Log.ASSERT) { - throw new IllegalArgumentException("Level is out of range [" - + Log.VERBOSE + ".." + Log.ASSERT + "]"); - } - - curlConfiguration = new LoggingConfiguration(name, level); - } - - /** - * Disables cURL logging for this client. - */ - public void disableCurlLogging() { - curlConfiguration = null; - } - - /** - * Logs cURL commands equivalent to requests. - */ - private class CurlLogger implements HttpRequestInterceptor { - public void process(HttpRequest request, HttpContext context) - throws HttpException, IOException { - LoggingConfiguration configuration = curlConfiguration; - if (configuration != null - && configuration.isLoggable() - && request instanceof HttpUriRequest) { - // Never print auth token -- we used to check ro.secure=0 to - // enable that, but can't do that in unbundled code. - configuration.println(toCurl((HttpUriRequest) request, false)); - } - } - } - - /** - * Generates a cURL command equivalent to the given request. - */ - private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException { - StringBuilder builder = new StringBuilder(); - - builder.append("curl "); - - for (Header header: request.getAllHeaders()) { - if (!logAuthToken - && (header.getName().equals("Authorization") || - header.getName().equals("Cookie"))) { - continue; - } - builder.append("--header \""); - builder.append(header.toString().trim()); - builder.append("\" "); - } - - URI uri = request.getURI(); - - // If this is a wrapped request, use the URI from the original - // request instead. getURI() on the wrapper seems to return a - // relative URI. We want an absolute URI. - if (request instanceof RequestWrapper) { - HttpRequest original = ((RequestWrapper) request).getOriginal(); - if (original instanceof HttpUriRequest) { - uri = ((HttpUriRequest) original).getURI(); - } - } - - builder.append("\""); - builder.append(uri); - builder.append("\""); - - if (request instanceof HttpEntityEnclosingRequest) { - HttpEntityEnclosingRequest entityRequest = - (HttpEntityEnclosingRequest) request; - HttpEntity entity = entityRequest.getEntity(); - if (entity != null && entity.isRepeatable()) { - if (entity.getContentLength() < 1024) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - entity.writeTo(stream); - String entityString = stream.toString(); - - // TODO: Check the content type, too. - builder.append(" --data-ascii \"") - .append(entityString) - .append("\""); - } else { - builder.append(" [TOO MUCH DATA TO INCLUDE]"); - } - } - } - - return builder.toString(); - } - - /** - * Returns the date of the given HTTP date string. This method can identify - * and parse the date formats emitted by common HTTP servers, such as - * RFC 822, - * RFC 850, - * RFC 1036, - * RFC 1123 and - * ANSI - * C's asctime(). - * - * @return the number of milliseconds since Jan. 1, 1970, midnight GMT. - * @throws IllegalArgumentException if {@code dateString} is not a date or - * of an unsupported format. - */ - public static long parseDate(String dateString) { - return HttpDateTime.parse(dateString); - } -} \ No newline at end of file diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java deleted file mode 100755 index b77af7e085..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.app.Service; -import android.content.Intent; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.util.Log; - -/** - * This service differs from IntentService in a few minor ways/ It will not - * auto-stop itself after the intent is handled unless the target returns "true" - * in should stop. Since the goal of this service is to handle a single kind of - * intent, it does not queue up batches of intents of the same type. - */ -public abstract class CustomIntentService extends Service { - private String mName; - private boolean mRedelivery; - private volatile ServiceHandler mServiceHandler; - private volatile Looper mServiceLooper; - private static final String LOG_TAG = "CancellableIntentService"; - private static final int WHAT_MESSAGE = -10; - - public CustomIntentService(String paramString) { - this.mName = paramString; - } - - @Override - public IBinder onBind(Intent paramIntent) { - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - HandlerThread localHandlerThread = new HandlerThread("IntentService[" - + this.mName + "]"); - localHandlerThread.start(); - this.mServiceLooper = localHandlerThread.getLooper(); - this.mServiceHandler = new ServiceHandler(this.mServiceLooper); - } - - @Override - public void onDestroy() { - Thread localThread = this.mServiceLooper.getThread(); - if ((localThread != null) && (localThread.isAlive())) { - localThread.interrupt(); - } - this.mServiceLooper.quit(); - Log.d(LOG_TAG, "onDestroy"); - } - - protected abstract void onHandleIntent(Intent paramIntent); - - protected abstract boolean shouldStop(); - - @Override - public void onStart(Intent paramIntent, int startId) { - if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) { - Message localMessage = this.mServiceHandler.obtainMessage(); - localMessage.arg1 = startId; - localMessage.obj = paramIntent; - localMessage.what = WHAT_MESSAGE; - this.mServiceHandler.sendMessage(localMessage); - } - } - - @Override - public int onStartCommand(Intent paramIntent, int flags, int startId) { - onStart(paramIntent, startId); - return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; - } - - public void setIntentRedelivery(boolean enabled) { - this.mRedelivery = enabled; - } - - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message paramMessage) { - CustomIntentService.this - .onHandleIntent((Intent) paramMessage.obj); - if (shouldStop()) { - Log.d(LOG_TAG, "stopSelf"); - CustomIntentService.this.stopSelf(paramMessage.arg1); - Log.d(LOG_TAG, "afterStopSelf"); - } - } - } -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java deleted file mode 100644 index 9a0ca02122..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/CustomNotificationFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -/** - * Uses the class-loader model to utilize the updated notification builders in - * Honeycomb while maintaining a compatible version for older devices. - */ -public class CustomNotificationFactory { - static public DownloadNotification.ICustomNotification createCustomNotification() { - if (android.os.Build.VERSION.SDK_INT > 13) - return new V14CustomNotification(); - else - return new V3CustomNotification(); - } -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java deleted file mode 100644 index 45111b16a3..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.Helpers; - -import android.util.Log; - -/** - * Representation of information about an individual download from the database. - */ -public class DownloadInfo { - public String mUri; - public final int mIndex; - public final String mFileName; - public String mETag; - public long mTotalBytes; - public long mCurrentBytes; - public long mLastMod; - public int mStatus; - public int mControl; - public int mNumFailed; - public int mRetryAfter; - public int mRedirectCount; - - boolean mInitialized; - - public int mFuzz; - - public DownloadInfo(int index, String fileName, String pkg) { - mFuzz = Helpers.sRandom.nextInt(1001); - mFileName = fileName; - mIndex = index; - } - - public void resetDownload() { - mCurrentBytes = 0; - mETag = ""; - mLastMod = 0; - mStatus = 0; - mControl = 0; - mNumFailed = 0; - mRetryAfter = 0; - mRedirectCount = 0; - } - - /** - * Returns the time when a download should be restarted. - */ - public long restartTime(long now) { - if (mNumFailed == 0) { - return now; - } - if (mRetryAfter > 0) { - return mLastMod + mRetryAfter; - } - return mLastMod + - Constants.RETRY_FIRST_DELAY * - (1000 + mFuzz) * (1 << (mNumFailed - 1)); - } - - public void logVerboseInfo() { - Log.v(Constants.TAG, "Service adding new entry"); - Log.v(Constants.TAG, "FILENAME: " + mFileName); - Log.v(Constants.TAG, "URI : " + mUri); - Log.v(Constants.TAG, "FILENAME: " + mFileName); - Log.v(Constants.TAG, "CONTROL : " + mControl); - Log.v(Constants.TAG, "STATUS : " + mStatus); - Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); - Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); - Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); - Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); - Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); - Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); - Log.v(Constants.TAG, "ETAG : " + mETag); - } -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java deleted file mode 100644 index eef205d7b7..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.android.vending.expansion.downloader.R; -import com.google.android.vending.expansion.downloader.DownloadProgressInfo; -import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.os.Messenger; - -/** - * This class handles displaying the notification associated with the download - * queue going on in the download manager. It handles multiple status types; - * Some require user interaction and some do not. Some of the user interactions - * may be transient. (for example: the user is queried to continue the download - * on 3G when it started on WiFi, but then the phone locks onto WiFi again so - * the prompt automatically goes away) - *

- * The application interface for the downloader also needs to understand and - * handle these transient states. - */ -public class DownloadNotification implements IDownloaderClient { - - private int mState; - private final Context mContext; - private final NotificationManager mNotificationManager; - private String mCurrentTitle; - - private IDownloaderClient mClientProxy; - final ICustomNotification mCustomNotification; - private Notification mNotification; - private Notification mCurrentNotification; - private CharSequence mLabel; - private String mCurrentText; - private PendingIntent mContentIntent; - private DownloadProgressInfo mProgressInfo; - - static final String LOGTAG = "DownloadNotification"; - static final int NOTIFICATION_ID = LOGTAG.hashCode(); - - public PendingIntent getClientIntent() { - return mContentIntent; - } - - public void setClientIntent(PendingIntent mClientIntent) { - this.mContentIntent = mClientIntent; - } - - public void resendState() { - if (null != mClientProxy) { - mClientProxy.onDownloadStateChanged(mState); - } - } - - @Override - public void onDownloadStateChanged(int newState) { - if (null != mClientProxy) { - mClientProxy.onDownloadStateChanged(newState); - } - if (newState != mState) { - mState = newState; - if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) { - return; - } - int stringDownloadID; - int iconResource; - boolean ongoingEvent; - - // get the new title string and paused text - switch (newState) { - case 0: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = R.string.state_unknown; - ongoingEvent = false; - break; - - case IDownloaderClient.STATE_DOWNLOADING: - iconResource = android.R.drawable.stat_sys_download; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - - case IDownloaderClient.STATE_FETCHING_URL: - case IDownloaderClient.STATE_CONNECTING: - iconResource = android.R.drawable.stat_sys_download_done; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - - case IDownloaderClient.STATE_COMPLETED: - case IDownloaderClient.STATE_PAUSED_BY_REQUEST: - iconResource = android.R.drawable.stat_sys_download_done; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = false; - break; - - case IDownloaderClient.STATE_FAILED: - case IDownloaderClient.STATE_FAILED_CANCELED: - case IDownloaderClient.STATE_FAILED_FETCHING_URL: - case IDownloaderClient.STATE_FAILED_SDCARD_FULL: - case IDownloaderClient.STATE_FAILED_UNLICENSED: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = false; - break; - - default: - iconResource = android.R.drawable.stat_sys_warning; - stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState); - ongoingEvent = true; - break; - } - mCurrentText = mContext.getString(stringDownloadID); - mCurrentTitle = mLabel.toString(); - mCurrentNotification.tickerText = mLabel + ": " + mCurrentText; - mCurrentNotification.icon = iconResource; - mCurrentNotification.setLatestEventInfo(mContext, mCurrentTitle, mCurrentText, - mContentIntent); - if (ongoingEvent) { - mCurrentNotification.flags |= Notification.FLAG_ONGOING_EVENT; - } else { - mCurrentNotification.flags &= ~Notification.FLAG_ONGOING_EVENT; - mCurrentNotification.flags |= Notification.FLAG_AUTO_CANCEL; - } - mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification); - } - } - - @Override - public void onDownloadProgress(DownloadProgressInfo progress) { - mProgressInfo = progress; - if (null != mClientProxy) { - mClientProxy.onDownloadProgress(progress); - } - if (progress.mOverallTotal <= 0) { - // we just show the text - mNotification.tickerText = mCurrentTitle; - mNotification.icon = android.R.drawable.stat_sys_download; - mNotification.setLatestEventInfo(mContext, mLabel, mCurrentText, mContentIntent); - mCurrentNotification = mNotification; - } else { - mCustomNotification.setCurrentBytes(progress.mOverallProgress); - mCustomNotification.setTotalBytes(progress.mOverallTotal); - mCustomNotification.setIcon(android.R.drawable.stat_sys_download); - mCustomNotification.setPendingIntent(mContentIntent); - mCustomNotification.setTicker(mLabel + ": " + mCurrentText); - mCustomNotification.setTitle(mLabel); - mCustomNotification.setTimeRemaining(progress.mTimeRemaining); - mCurrentNotification = mCustomNotification.updateNotification(mContext); - } - mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification); - } - - public interface ICustomNotification { - void setTitle(CharSequence title); - - void setTicker(CharSequence ticker); - - void setPendingIntent(PendingIntent mContentIntent); - - void setTotalBytes(long totalBytes); - - void setCurrentBytes(long currentBytes); - - void setIcon(int iconResource); - - void setTimeRemaining(long timeRemaining); - - Notification updateNotification(Context c); - } - - /** - * Called in response to onClientUpdated. Creates a new proxy and notifies - * it of the current state. - * - * @param msg the client Messenger to notify - */ - public void setMessenger(Messenger msg) { - mClientProxy = DownloaderClientMarshaller.CreateProxy(msg); - if (null != mProgressInfo) { - mClientProxy.onDownloadProgress(mProgressInfo); - } - if (mState != -1) { - mClientProxy.onDownloadStateChanged(mState); - } - } - - /** - * Constructor - * - * @param ctx The context to use to obtain access to the Notification - * Service - */ - DownloadNotification(Context ctx, CharSequence applicationLabel) { - mState = -1; - mContext = ctx; - mLabel = applicationLabel; - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - mCustomNotification = CustomNotificationFactory - .createCustomNotification(); - mNotification = new Notification(); - mCurrentNotification = mNotification; - - } - - @Override - public void onServiceConnected(Messenger m) { - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java deleted file mode 100644 index 056d1eca0b..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadThread.java +++ /dev/null @@ -1,963 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; - -import org.apache.http.Header; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.conn.params.ConnRouteParams; - -import android.content.Context; -import android.net.Proxy; -import android.os.PowerManager; -import android.os.Process; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.SyncFailedException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Locale; - -/** - * Runs an actual download - */ -public class DownloadThread { - - private Context mContext; - private DownloadInfo mInfo; - private DownloaderService mService; - private final DownloadsDB mDB; - private final DownloadNotification mNotification; - private String mUserAgent; - - public DownloadThread(DownloadInfo info, DownloaderService service, - DownloadNotification notification) { - mContext = service; - mInfo = info; - mService = service; - mNotification = notification; - mDB = DownloadsDB.getDB(service); - mUserAgent = "APKXDL (Linux; U; Android " + android.os.Build.VERSION.RELEASE + ";" - + Locale.getDefault().toString() + "; " + android.os.Build.DEVICE + "/" - + android.os.Build.ID + ")" + - service.getPackageName(); - } - - /** - * Returns the default user agent - */ - private String userAgent() { - return mUserAgent; - } - - /** - * State for the entire run() method. - */ - private static class State { - public String mFilename; - public FileOutputStream mStream; - public boolean mCountRetry = false; - public int mRetryAfter = 0; - public int mRedirectCount = 0; - public String mNewUri; - public boolean mGotData = false; - public String mRequestUri; - - public State(DownloadInfo info, DownloaderService service) { - mRedirectCount = info.mRedirectCount; - mRequestUri = info.mUri; - mFilename = service.generateTempSaveFileName(info.mFileName); - } - } - - /** - * State within executeDownload() - */ - private static class InnerState { - public int mBytesSoFar = 0; - public int mBytesThisSession = 0; - public String mHeaderETag; - public boolean mContinuingDownload = false; - public String mHeaderContentLength; - public String mHeaderContentDisposition; - public String mHeaderContentLocation; - public int mBytesNotified = 0; - public long mTimeLastNotification = 0; - } - - /** - * Raised from methods called by run() to indicate that the current request - * should be stopped immediately. Note the message passed to this exception - * will be logged and therefore must be guaranteed not to contain any PII, - * meaning it generally can't include any information about the request URI, - * headers, or destination filename. - */ - private class StopRequest extends Throwable { - /** - * - */ - private static final long serialVersionUID = 6338592678988347973L; - public int mFinalStatus; - - public StopRequest(int finalStatus, String message) { - super(message); - mFinalStatus = finalStatus; - } - - public StopRequest(int finalStatus, String message, Throwable throwable) { - super(message, throwable); - mFinalStatus = finalStatus; - } - } - - /** - * Raised from methods called by executeDownload() to indicate that the - * download should be retried immediately. - */ - private class RetryDownload extends Throwable { - - /** - * - */ - private static final long serialVersionUID = 6196036036517540229L; - } - - /** - * Returns the preferred proxy to be used by clients. This is a wrapper - * around {@link android.net.Proxy#getHost()}. Currently no proxy will be - * returned for localhost or if the active network is Wi-Fi. - * - * @param context the context which will be passed to - * {@link android.net.Proxy#getHost()} - * @param url the target URL for the request - * @note Calling this method requires permission - * android.permission.ACCESS_NETWORK_STATE - * @return The preferred proxy to be used by clients, or null if there is no - * proxy. - */ - public HttpHost getPreferredHttpHost(Context context, - String url) { - if (!isLocalHost(url) && !mService.isWiFi()) { - final String proxyHost = Proxy.getHost(context); - if (proxyHost != null) { - return new HttpHost(proxyHost, Proxy.getPort(context), "http"); - } - } - - return null; - } - - static final private boolean isLocalHost(String url) { - if (url == null) { - return false; - } - - try { - final URI uri = URI.create(url); - final String host = uri.getHost(); - if (host != null) { - // TODO: InetAddress.isLoopbackAddress should be used to check - // for localhost. However no public factory methods exist which - // can be used without triggering DNS lookup if host is not - // localhost. - if (host.equalsIgnoreCase("localhost") || - host.equals("127.0.0.1") || - host.equals("[::1]")) { - return true; - } - } - } catch (IllegalArgumentException iex) { - // Ignore (URI.create) - } - - return false; - } - - /** - * Executes the download in a separate thread - */ - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - State state = new State(mInfo, mService); - AndroidHttpClient client = null; - PowerManager.WakeLock wakeLock = null; - int finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; - - try { - PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG); - wakeLock.acquire(); - - if (Constants.LOGV) { - Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - - client = AndroidHttpClient.newInstance(userAgent(), mContext); - - boolean finished = false; - while (!finished) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - // Set or unset proxy, which may have changed since last GET - // request. - // setDefaultProxy() supports null as proxy parameter. - ConnRouteParams.setDefaultProxy(client.getParams(), - getPreferredHttpHost(mContext, state.mRequestUri)); - HttpGet request = new HttpGet(state.mRequestUri); - try { - executeDownload(state, client, request); - finished = true; - } catch (RetryDownload exc) { - // fall through - } finally { - request.abort(); - request = null; - } - } - - if (Constants.LOGV) { - Log.v(Constants.TAG, "download completed for " + mInfo.mFileName); - Log.v(Constants.TAG, " at " + mInfo.mUri); - } - finalizeDestinationFile(state); - finalStatus = DownloaderService.STATUS_SUCCESS; - } catch (StopRequest error) { - // remove the cause before printing, in case it contains PII - Log.w(Constants.TAG, - "Aborting request for download " + mInfo.mFileName + ": " + error.getMessage()); - error.printStackTrace(); - finalStatus = error.mFinalStatus; - // fall through to finally block - } catch (Throwable ex) { // sometimes the socket code throws unchecked - // exceptions - Log.w(Constants.TAG, "Exception for " + mInfo.mFileName + ": " + ex); - finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR; - // falls through to the code that reports an error - } finally { - if (wakeLock != null) { - wakeLock.release(); - wakeLock = null; - } - if (client != null) { - client.close(); - client = null; - } - cleanupDestination(state, finalStatus); - notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter, - state.mRedirectCount, state.mGotData, state.mFilename); - } - } - - /** - * Fully execute a single download request - setup and send the request, - * handle the response, and transfer the data to the destination file. - */ - private void executeDownload(State state, AndroidHttpClient client, HttpGet request) - throws StopRequest, RetryDownload { - InnerState innerState = new InnerState(); - byte data[] = new byte[Constants.BUFFER_SIZE]; - - checkPausedOrCanceled(state); - - setupDestinationFile(state, innerState); - addRequestHeaders(innerState, request); - - // check just before sending the request to avoid using an invalid - // connection at all - checkConnectivity(state); - - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING); - HttpResponse response = sendRequest(state, client, request); - handleExceptionalStatus(state, innerState, response); - - if (Constants.LOGV) { - Log.v(Constants.TAG, "received response for " + mInfo.mUri); - } - - processResponseHeaders(state, innerState, response); - InputStream entityStream = openResponseEntity(state, response); - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_DOWNLOADING); - transferData(state, innerState, data, entityStream); - } - - /** - * Check if current connectivity is valid for this request. - */ - private void checkConnectivity(State state) throws StopRequest { - switch (mService.getNetworkAvailabilityState(mDB)) { - case DownloaderService.NETWORK_OK: - return; - case DownloaderService.NETWORK_NO_CONNECTION: - throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, - "waiting for network to return"); - case DownloaderService.NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: - throw new StopRequest( - DownloaderService.STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION, - "waiting for wifi or for download over cellular to be authorized"); - case DownloaderService.NETWORK_CANNOT_USE_ROAMING: - throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK, - "roaming is not allowed"); - case DownloaderService.NETWORK_UNUSABLE_DUE_TO_SIZE: - throw new StopRequest(DownloaderService.STATUS_QUEUED_FOR_WIFI, "waiting for wifi"); - } - } - - /** - * Transfer as much data as possible from the HTTP response to the - * destination file. - * - * @param data buffer to use to read data - * @param entityStream stream for reading the HTTP response entity - */ - private void transferData(State state, InnerState innerState, byte[] data, - InputStream entityStream) throws StopRequest { - for (;;) { - int bytesRead = readFromResponse(state, innerState, data, entityStream); - if (bytesRead == -1) { // success, end of stream already reached - handleEndOfStream(state, innerState); - return; - } - - state.mGotData = true; - writeDataToDestination(state, data, bytesRead); - innerState.mBytesSoFar += bytesRead; - innerState.mBytesThisSession += bytesRead; - reportProgress(state, innerState); - - checkPausedOrCanceled(state); - } - } - - /** - * Called after a successful completion to take any necessary action on the - * downloaded file. - */ - private void finalizeDestinationFile(State state) throws StopRequest { - syncDestination(state); - String tempFilename = state.mFilename; - String finalFilename = Helpers.generateSaveFileName(mService, mInfo.mFileName); - if (!state.mFilename.equals(finalFilename)) { - File startFile = new File(tempFilename); - File destFile = new File(finalFilename); - if (mInfo.mTotalBytes != -1 && mInfo.mCurrentBytes == mInfo.mTotalBytes) { - if (!startFile.renameTo(destFile)) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "unable to finalize destination file"); - } - } else { - throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, - "file delivered with incorrect size. probably due to network not browser configured"); - } - } - } - - /** - * Called just before the thread finishes, regardless of status, to take any - * necessary action on the downloaded file. - */ - private void cleanupDestination(State state, int finalStatus) { - closeDestination(state); - if (state.mFilename != null && DownloaderService.isStatusError(finalStatus)) { - new File(state.mFilename).delete(); - state.mFilename = null; - } - } - - /** - * Sync the destination file to storage. - */ - private void syncDestination(State state) { - FileOutputStream downloadedFileStream = null; - try { - downloadedFileStream = new FileOutputStream(state.mFilename, true); - downloadedFileStream.getFD().sync(); - } catch (FileNotFoundException ex) { - Log.w(Constants.TAG, "file " + state.mFilename + " not found: " + ex); - } catch (SyncFailedException ex) { - Log.w(Constants.TAG, "file " + state.mFilename + " sync failed: " + ex); - } catch (IOException ex) { - Log.w(Constants.TAG, "IOException trying to sync " + state.mFilename + ": " + ex); - } catch (RuntimeException ex) { - Log.w(Constants.TAG, "exception while syncing file: ", ex); - } finally { - if (downloadedFileStream != null) { - try { - downloadedFileStream.close(); - } catch (IOException ex) { - Log.w(Constants.TAG, "IOException while closing synced file: ", ex); - } catch (RuntimeException ex) { - Log.w(Constants.TAG, "exception while closing file: ", ex); - } - } - } - } - - /** - * Close the destination output stream. - */ - private void closeDestination(State state) { - try { - // close the file - if (state.mStream != null) { - state.mStream.close(); - state.mStream = null; - } - } catch (IOException ex) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "exception when closing the file after download : " + ex); - } - // nothing can really be done if the file can't be closed - } - } - - /** - * Check if the download has been paused or canceled, stopping the request - * appropriately if it has been. - */ - private void checkPausedOrCanceled(State state) throws StopRequest { - if (mService.getControl() == DownloaderService.CONTROL_PAUSED) { - int status = mService.getStatus(); - switch (status) { - case DownloaderService.STATUS_PAUSED_BY_APP: - throw new StopRequest(mService.getStatus(), - "download paused"); - } - } - } - - /** - * Report download progress through the database if necessary. - */ - private void reportProgress(State state, InnerState innerState) { - long now = System.currentTimeMillis(); - if (innerState.mBytesSoFar - innerState.mBytesNotified - > Constants.MIN_PROGRESS_STEP - && now - innerState.mTimeLastNotification - > Constants.MIN_PROGRESS_TIME) { - // we store progress updates to the database here - mInfo.mCurrentBytes = innerState.mBytesSoFar; - mDB.updateDownloadCurrentBytes(mInfo); - - innerState.mBytesNotified = innerState.mBytesSoFar; - innerState.mTimeLastNotification = now; - - long totalBytesSoFar = innerState.mBytesThisSession + mService.mBytesSoFar; - - if (Constants.LOGVV) { - Log.v(Constants.TAG, "downloaded " + mInfo.mCurrentBytes + " out of " - + mInfo.mTotalBytes); - Log.v(Constants.TAG, " total " + totalBytesSoFar + " out of " - + mService.mTotalLength); - } - - mService.notifyUpdateBytes(totalBytesSoFar); - } - } - - /** - * Write a data buffer to the destination file. - * - * @param data buffer containing the data to write - * @param bytesRead how many bytes to write from the buffer - */ - private void writeDataToDestination(State state, byte[] data, int bytesRead) - throws StopRequest { - for (;;) { - try { - if (state.mStream == null) { - state.mStream = new FileOutputStream(state.mFilename, true); - } - state.mStream.write(data, 0, bytesRead); - // we close after every write --- this may be too inefficient - closeDestination(state); - return; - } catch (IOException ex) { - if (!Helpers.isExternalMediaMounted()) { - throw new StopRequest(DownloaderService.STATUS_DEVICE_NOT_FOUND_ERROR, - "external media not mounted while writing destination file"); - } - - long availableBytes = - Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename)); - if (availableBytes < bytesRead) { - throw new StopRequest(DownloaderService.STATUS_INSUFFICIENT_SPACE_ERROR, - "insufficient space while writing destination file", ex); - } - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while writing destination file: " + ex.toString(), ex); - } - } - } - - /** - * Called when we've reached the end of the HTTP response stream, to update - * the database and check for consistency. - */ - private void handleEndOfStream(State state, InnerState innerState) throws StopRequest { - mInfo.mCurrentBytes = innerState.mBytesSoFar; - // this should always be set from the market - // if ( innerState.mHeaderContentLength == null ) { - // mInfo.mTotalBytes = innerState.mBytesSoFar; - // } - mDB.updateDownload(mInfo); - - boolean lengthMismatched = (innerState.mHeaderContentLength != null) - && (innerState.mBytesSoFar != Integer.parseInt(innerState.mHeaderContentLength)); - if (lengthMismatched) { - if (cannotResume(innerState)) { - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - "mismatched content length"); - } else { - throw new StopRequest(getFinalStatusForHttpError(state), - "closed socket before end of file"); - } - } - } - - private boolean cannotResume(InnerState innerState) { - return innerState.mBytesSoFar > 0 && innerState.mHeaderETag == null; - } - - /** - * Read some data from the HTTP response stream, handling I/O errors. - * - * @param data buffer to use to read data - * @param entityStream stream for reading the HTTP response entity - * @return the number of bytes actually read or -1 if the end of the stream - * has been reached - */ - private int readFromResponse(State state, InnerState innerState, byte[] data, - InputStream entityStream) throws StopRequest { - try { - return entityStream.read(data); - } catch (IOException ex) { - logNetworkState(); - mInfo.mCurrentBytes = innerState.mBytesSoFar; - mDB.updateDownload(mInfo); - if (cannotResume(innerState)) { - String message = "while reading response: " + ex.toString() - + ", can't resume interrupted download with no ETag"; - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - message, ex); - } else { - throw new StopRequest(getFinalStatusForHttpError(state), - "while reading response: " + ex.toString(), ex); - } - } - } - - /** - * Open a stream for the HTTP response entity, handling I/O errors. - * - * @return an InputStream to read the response entity - */ - private InputStream openResponseEntity(State state, HttpResponse response) - throws StopRequest { - try { - return response.getEntity().getContent(); - } catch (IOException ex) { - logNetworkState(); - throw new StopRequest(getFinalStatusForHttpError(state), - "while getting entity: " + ex.toString(), ex); - } - } - - private void logNetworkState() { - if (Constants.LOGX) { - Log.i(Constants.TAG, - "Net " - + (mService.getNetworkAvailabilityState(mDB) == DownloaderService.NETWORK_OK ? "Up" - : "Down")); - } - } - - /** - * Read HTTP response headers and take appropriate action, including setting - * up the destination file and updating the database. - */ - private void processResponseHeaders(State state, InnerState innerState, HttpResponse response) - throws StopRequest { - if (innerState.mContinuingDownload) { - // ignore response headers on resume requests - return; - } - - readResponseHeaders(state, innerState, response); - - try { - state.mFilename = mService.generateSaveFile(mInfo.mFileName, mInfo.mTotalBytes); - } catch (DownloaderService.GenerateSaveFileError exc) { - throw new StopRequest(exc.mStatus, exc.mMessage); - } - try { - state.mStream = new FileOutputStream(state.mFilename); - } catch (FileNotFoundException exc) { - // make sure the directory exists - File pathFile = new File(Helpers.getSaveFilePath(mService)); - try { - if (pathFile.mkdirs()) { - state.mStream = new FileOutputStream(state.mFilename); - } - } catch (Exception ex) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while opening destination file: " + exc.toString(), exc); - } - } - if (Constants.LOGV) { - Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + state.mFilename); - } - - updateDatabaseFromHeaders(state, innerState); - // check connectivity again now that we know the total size - checkConnectivity(state); - } - - /** - * Update necessary database fields based on values of HTTP response headers - * that have been read. - */ - private void updateDatabaseFromHeaders(State state, InnerState innerState) { - mInfo.mETag = innerState.mHeaderETag; - mDB.updateDownload(mInfo); - } - - /** - * Read headers from the HTTP response and store them into local state. - */ - private void readResponseHeaders(State state, InnerState innerState, HttpResponse response) - throws StopRequest { - Header header = response.getFirstHeader("Content-Disposition"); - if (header != null) { - innerState.mHeaderContentDisposition = header.getValue(); - } - header = response.getFirstHeader("Content-Location"); - if (header != null) { - innerState.mHeaderContentLocation = header.getValue(); - } - header = response.getFirstHeader("ETag"); - if (header != null) { - innerState.mHeaderETag = header.getValue(); - } - String headerTransferEncoding = null; - header = response.getFirstHeader("Transfer-Encoding"); - if (header != null) { - headerTransferEncoding = header.getValue(); - } - String headerContentType = null; - header = response.getFirstHeader("Content-Type"); - if (header != null) { - headerContentType = header.getValue(); - if (!headerContentType.equals("application/vnd.android.obb")) { - throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY, - "file delivered with incorrect Mime type"); - } - } - - if (headerTransferEncoding == null) { - header = response.getFirstHeader("Content-Length"); - if (header != null) { - innerState.mHeaderContentLength = header.getValue(); - // this is always set from Market - long contentLength = Long.parseLong(innerState.mHeaderContentLength); - if (contentLength != -1 && contentLength != mInfo.mTotalBytes) { - // we're most likely on a bad wifi connection -- we should - // probably - // also look at the mime type --- but the size mismatch is - // enough - // to tell us that something is wrong here - Log.e(Constants.TAG, "Incorrect file size delivered."); - } - } - } else { - // Ignore content-length with transfer-encoding - 2616 4.4 3 - if (Constants.LOGVV) { - Log.v(Constants.TAG, - "ignoring content-length because of xfer-encoding"); - } - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Content-Disposition: " + - innerState.mHeaderContentDisposition); - Log.v(Constants.TAG, "Content-Length: " + innerState.mHeaderContentLength); - Log.v(Constants.TAG, "Content-Location: " + innerState.mHeaderContentLocation); - Log.v(Constants.TAG, "ETag: " + innerState.mHeaderETag); - Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding); - } - - boolean noSizeInfo = innerState.mHeaderContentLength == null - && (headerTransferEncoding == null - || !headerTransferEncoding.equalsIgnoreCase("chunked")); - if (noSizeInfo) { - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "can't know size of download, giving up"); - } - } - - /** - * Check the HTTP response status and handle anything unusual (e.g. not - * 200/206). - */ - private void handleExceptionalStatus(State state, InnerState innerState, HttpResponse response) - throws StopRequest, RetryDownload { - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) { - handleServiceUnavailable(state, response); - } - if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307) { - handleRedirect(state, response, statusCode); - } - - int expectedStatus = innerState.mContinuingDownload ? 206 - : DownloaderService.STATUS_SUCCESS; - if (statusCode != expectedStatus) { - handleOtherStatus(state, innerState, statusCode); - } else { - // no longer redirected - state.mRedirectCount = 0; - } - } - - /** - * Handle a status that we don't know how to deal with properly. - */ - private void handleOtherStatus(State state, InnerState innerState, int statusCode) - throws StopRequest { - int finalStatus; - if (DownloaderService.isStatusError(statusCode)) { - finalStatus = statusCode; - } else if (statusCode >= 300 && statusCode < 400) { - finalStatus = DownloaderService.STATUS_UNHANDLED_REDIRECT; - } else if (innerState.mContinuingDownload && statusCode == DownloaderService.STATUS_SUCCESS) { - finalStatus = DownloaderService.STATUS_CANNOT_RESUME; - } else { - finalStatus = DownloaderService.STATUS_UNHANDLED_HTTP_CODE; - } - throw new StopRequest(finalStatus, "http error " + statusCode); - } - - /** - * Handle a 3xx redirect status. - */ - private void handleRedirect(State state, HttpResponse response, int statusCode) - throws StopRequest, RetryDownload { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP redirect " + statusCode); - } - if (state.mRedirectCount >= Constants.MAX_REDIRECTS) { - throw new StopRequest(DownloaderService.STATUS_TOO_MANY_REDIRECTS, "too many redirects"); - } - Header header = response.getFirstHeader("Location"); - if (header == null) { - return; - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Location :" + header.getValue()); - } - - String newUri; - try { - newUri = new URI(mInfo.mUri).resolve(new URI(header.getValue())).toString(); - } catch (URISyntaxException ex) { - if (Constants.LOGV) { - Log.d(Constants.TAG, "Couldn't resolve redirect URI " + header.getValue() - + " for " + mInfo.mUri); - } - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "Couldn't resolve redirect URI"); - } - ++state.mRedirectCount; - state.mRequestUri = newUri; - if (statusCode == 301 || statusCode == 303) { - // use the new URI for all future requests (should a retry/resume be - // necessary) - state.mNewUri = newUri; - } - throw new RetryDownload(); - } - - /** - * Add headers for this download to the HTTP request to allow for resume. - */ - private void addRequestHeaders(InnerState innerState, HttpGet request) { - if (innerState.mContinuingDownload) { - if (innerState.mHeaderETag != null) { - request.addHeader("If-Match", innerState.mHeaderETag); - } - request.addHeader("Range", "bytes=" + innerState.mBytesSoFar + "-"); - } - } - - /** - * Handle a 503 Service Unavailable status by processing the Retry-After - * header. - */ - private void handleServiceUnavailable(State state, HttpResponse response) throws StopRequest { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "got HTTP response code 503"); - } - state.mCountRetry = true; - Header header = response.getFirstHeader("Retry-After"); - if (header != null) { - try { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Retry-After :" + header.getValue()); - } - state.mRetryAfter = Integer.parseInt(header.getValue()); - if (state.mRetryAfter < 0) { - state.mRetryAfter = 0; - } else { - if (state.mRetryAfter < Constants.MIN_RETRY_AFTER) { - state.mRetryAfter = Constants.MIN_RETRY_AFTER; - } else if (state.mRetryAfter > Constants.MAX_RETRY_AFTER) { - state.mRetryAfter = Constants.MAX_RETRY_AFTER; - } - state.mRetryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1); - state.mRetryAfter *= 1000; - } - } catch (NumberFormatException ex) { - // ignored - retryAfter stays 0 in this case. - } - } - throw new StopRequest(DownloaderService.STATUS_WAITING_TO_RETRY, - "got 503 Service Unavailable, will retry later"); - } - - /** - * Send the request to the server, handling any I/O exceptions. - */ - private HttpResponse sendRequest(State state, AndroidHttpClient client, HttpGet request) - throws StopRequest { - try { - return client.execute(request); - } catch (IllegalArgumentException ex) { - throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR, - "while trying to execute request: " + ex.toString(), ex); - } catch (IOException ex) { - logNetworkState(); - throw new StopRequest(getFinalStatusForHttpError(state), - "while trying to execute request: " + ex.toString(), ex); - } - } - - private int getFinalStatusForHttpError(State state) { - if (mService.getNetworkAvailabilityState(mDB) != DownloaderService.NETWORK_OK) { - return DownloaderService.STATUS_WAITING_FOR_NETWORK; - } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) { - state.mCountRetry = true; - return DownloaderService.STATUS_WAITING_TO_RETRY; - } else { - Log.w(Constants.TAG, "reached max retries for " + mInfo.mNumFailed); - return DownloaderService.STATUS_HTTP_DATA_ERROR; - } - } - - /** - * Prepare the destination file to receive data. If the file already exists, - * we'll set up appropriately for resumption. - */ - private void setupDestinationFile(State state, InnerState innerState) - throws StopRequest { - if (state.mFilename != null) { // only true if we've already run a - // thread for this download - if (!Helpers.isFilenameValid(state.mFilename)) { - // this should never happen - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "found invalid internal destination filename"); - } - // We're resuming a download that got interrupted - File f = new File(state.mFilename); - if (f.exists()) { - long fileLength = f.length(); - if (fileLength == 0) { - // The download hadn't actually started, we can restart from - // scratch - f.delete(); - state.mFilename = null; - } else if (mInfo.mETag == null) { - // This should've been caught upon failure - f.delete(); - throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME, - "Trying to resume a download that can't be resumed"); - } else { - // All right, we'll be able to resume this download - try { - state.mStream = new FileOutputStream(state.mFilename, true); - } catch (FileNotFoundException exc) { - throw new StopRequest(DownloaderService.STATUS_FILE_ERROR, - "while opening destination for resuming: " + exc.toString(), exc); - } - innerState.mBytesSoFar = (int) fileLength; - if (mInfo.mTotalBytes != -1) { - innerState.mHeaderContentLength = Long.toString(mInfo.mTotalBytes); - } - innerState.mHeaderETag = mInfo.mETag; - innerState.mContinuingDownload = true; - } - } - } - - if (state.mStream != null) { - closeDestination(state); - } - } - - /** - * Stores information about the completed download, and notifies the - * initiating application. - */ - private void notifyDownloadCompleted( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename) { - updateDownloadDatabase( - status, countRetry, retryAfter, redirectCount, gotData, filename); - if (DownloaderService.isStatusCompleted(status)) { - // TBD: send status update? - } - } - - private void updateDownloadDatabase( - int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData, - String filename) { - mInfo.mStatus = status; - mInfo.mRetryAfter = retryAfter; - mInfo.mRedirectCount = redirectCount; - mInfo.mLastMod = System.currentTimeMillis(); - if (!countRetry) { - mInfo.mNumFailed = 0; - } else if (gotData) { - mInfo.mNumFailed = 1; - } else { - mInfo.mNumFailed++; - } - mDB.updateDownload(mInfo); - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java deleted file mode 100644 index 627bf3eedd..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloaderService.java +++ /dev/null @@ -1,1341 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.google.android.vending.expansion.downloader.Constants; -import com.google.android.vending.expansion.downloader.DownloadProgressInfo; -import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller; -import com.google.android.vending.expansion.downloader.Helpers; -import com.google.android.vending.expansion.downloader.IDownloaderClient; -import com.google.android.vending.expansion.downloader.IDownloaderService; -import com.google.android.vending.expansion.downloader.IStub; -import com.google.android.vending.licensing.AESObfuscator; -import com.google.android.vending.licensing.APKExpansionPolicy; -import com.google.android.vending.licensing.LicenseChecker; -import com.google.android.vending.licensing.LicenseCheckerCallback; -import com.google.android.vending.licensing.Policy; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; -import android.os.Handler; -import android.os.IBinder; -import android.os.Messenger; -import android.os.SystemClock; -import android.provider.Settings.Secure; -import android.telephony.TelephonyManager; -import android.util.Log; - -import java.io.File; - -/** - * Performs the background downloads requested by applications that use the - * Downloads provider. This service does not run as a foreground task, so - * Android may kill it off at will, but it will try to restart itself if it can. - * Note that Android by default will kill off any process that has an open file - * handle on the shared (SD Card) partition if the partition is unmounted. - */ -public abstract class DownloaderService extends CustomIntentService implements IDownloaderService { - - public DownloaderService() { - super("LVLDownloadService"); - } - - private static final String LOG_TAG = "LVLDL"; - - // the following NETWORK_* constants are used to indicates specific reasons - // for disallowing a - // download from using a network, since specific causes can require special - // handling - - /** - * The network is usable for the given download. - */ - public static final int NETWORK_OK = 1; - - /** - * There is no network connectivity. - */ - public static final int NETWORK_NO_CONNECTION = 2; - - /** - * The download exceeds the maximum size for this network. - */ - public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3; - - /** - * The download exceeds the recommended maximum size for this network, the - * user must confirm for this download to proceed without WiFi. - */ - public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4; - - /** - * The current connection is roaming, and the download can't proceed over a - * roaming connection. - */ - public static final int NETWORK_CANNOT_USE_ROAMING = 5; - - /** - * The app requesting the download specific that it can't use the current - * network connection. - */ - public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6; - - /** - * For intents used to notify the user that a download exceeds a size - * threshold, if this extra is true, WiFi is required for this download - * size; otherwise, it is only recommended. - */ - public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired"; - public static final String EXTRA_FILE_NAME = "downloadId"; - - /** - * Used with DOWNLOAD_STATUS - */ - public static final String EXTRA_STATUS_STATE = "ESS"; - public static final String EXTRA_STATUS_TOTAL_SIZE = "ETS"; - public static final String EXTRA_STATUS_CURRENT_FILE_SIZE = "CFS"; - public static final String EXTRA_STATUS_TOTAL_PROGRESS = "TFP"; - public static final String EXTRA_STATUS_CURRENT_PROGRESS = "CFP"; - - public static final String ACTION_DOWNLOADS_CHANGED = "downloadsChanged"; - - /** - * Broadcast intent action sent by the download manager when a download - * completes. - */ - public final static String ACTION_DOWNLOAD_COMPLETE = "lvldownloader.intent.action.DOWNLOAD_COMPLETE"; - - /** - * Broadcast intent action sent by the download manager when download status - * changes. - */ - public final static String ACTION_DOWNLOAD_STATUS = "lvldownloader.intent.action.DOWNLOAD_STATUS"; - - /* - * Lists the states that the download manager can set on a download to - * notify applications of the download progress. The codes follow the HTTP - * families:
1xx: informational
2xx: success
3xx: redirects (not - * used by the download manager)
4xx: client errors
5xx: server - * errors - */ - - /** - * Returns whether the status is informational (i.e. 1xx). - */ - public static boolean isStatusInformational(int status) { - return (status >= 100 && status < 200); - } - - /** - * Returns whether the status is a success (i.e. 2xx). - */ - public static boolean isStatusSuccess(int status) { - return (status >= 200 && status < 300); - } - - /** - * Returns whether the status is an error (i.e. 4xx or 5xx). - */ - public static boolean isStatusError(int status) { - return (status >= 400 && status < 600); - } - - /** - * Returns whether the status is a client error (i.e. 4xx). - */ - public static boolean isStatusClientError(int status) { - return (status >= 400 && status < 500); - } - - /** - * Returns whether the status is a server error (i.e. 5xx). - */ - public static boolean isStatusServerError(int status) { - return (status >= 500 && status < 600); - } - - /** - * Returns whether the download has completed (either with success or - * error). - */ - public static boolean isStatusCompleted(int status) { - return (status >= 200 && status < 300) - || (status >= 400 && status < 600); - } - - /** - * This download hasn't stated yet - */ - public static final int STATUS_PENDING = 190; - - /** - * This download has started - */ - public static final int STATUS_RUNNING = 192; - - /** - * This download has been paused by the owning app. - */ - public static final int STATUS_PAUSED_BY_APP = 193; - - /** - * This download encountered some network error and is waiting before - * retrying the request. - */ - public static final int STATUS_WAITING_TO_RETRY = 194; - - /** - * This download is waiting for network connectivity to proceed. - */ - public static final int STATUS_WAITING_FOR_NETWORK = 195; - - /** - * This download is waiting for a Wi-Fi connection to proceed or for - * permission to download over cellular. - */ - public static final int STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION = 196; - - /** - * This download is waiting for a Wi-Fi connection to proceed. - */ - public static final int STATUS_QUEUED_FOR_WIFI = 197; - - /** - * This download has successfully completed. Warning: there might be other - * status values that indicate success in the future. Use isSucccess() to - * capture the entire category. - * - * @hide - */ - public static final int STATUS_SUCCESS = 200; - - /** - * The requested URL is no longer available - */ - public static final int STATUS_FORBIDDEN = 403; - - /** - * The file was delivered incorrectly - */ - public static final int STATUS_FILE_DELIVERED_INCORRECTLY = 487; - - /** - * The requested destination file already exists. - */ - public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; - - /** - * Some possibly transient error occurred, but we can't resume the download. - */ - public static final int STATUS_CANNOT_RESUME = 489; - - /** - * This download was canceled - * - * @hide - */ - public static final int STATUS_CANCELED = 490; - - /** - * This download has completed with an error. Warning: there will be other - * status values that indicate errors in the future. Use isStatusError() to - * capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of a storage issue. - * Typically, that's because the filesystem is missing or full. Use the more - * specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} and - * {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. - * - * @hide - */ - public static final int STATUS_FILE_ERROR = 492; - - /** - * This download couldn't be completed because of an HTTP redirect response - * that the download manager couldn't handle. - * - * @hide - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed because of an unspecified unhandled - * HTTP code. - * - * @hide - */ - public static final int STATUS_UNHANDLED_HTTP_CODE = 494; - - /** - * This download couldn't be completed because of an error receiving or - * processing data at the HTTP level. - * - * @hide - */ - public static final int STATUS_HTTP_DATA_ERROR = 495; - - /** - * This download couldn't be completed because of an HttpException while - * setting up the request. - * - * @hide - */ - public static final int STATUS_HTTP_EXCEPTION = 496; - - /** - * This download couldn't be completed because there were too many - * redirects. - * - * @hide - */ - public static final int STATUS_TOO_MANY_REDIRECTS = 497; - - /** - * This download couldn't be completed due to insufficient storage space. - * Typically, this is because the SD card is full. - * - * @hide - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; - - /** - * This download couldn't be completed because no external storage device - * was found. Typically, this is because the SD card is not mounted. - * - * @hide - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - - /** - * This download is allowed to run. - * - * @hide - */ - public static final int CONTROL_RUN = 0; - - /** - * This download must pause at the first opportunity. - * - * @hide - */ - public static final int CONTROL_PAUSED = 1; - - /** - * This download is visible but only shows in the notifications while it's - * in progress. - * - * @hide - */ - public static final int VISIBILITY_VISIBLE = 0; - - /** - * This download is visible and shows in the notifications while in progress - * and after completion. - * - * @hide - */ - public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; - - /** - * This download doesn't show in the UI or in the notifications. - * - * @hide - */ - public static final int VISIBILITY_HIDDEN = 2; - - /** - * Bit flag for {@link #setAllowedNetworkTypes} corresponding to - * {@link ConnectivityManager#TYPE_MOBILE}. - */ - public static final int NETWORK_MOBILE = 1 << 0; - - /** - * Bit flag for {@link #setAllowedNetworkTypes} corresponding to - * {@link ConnectivityManager#TYPE_WIFI}. - */ - public static final int NETWORK_WIFI = 1 << 1; - - private final static String TEMP_EXT = ".tmp"; - - /** - * Service thread status - */ - private static boolean sIsRunning; - - @Override - public IBinder onBind(Intent paramIntent) { - Log.d(Constants.TAG, "Service Bound"); - return this.mServiceMessenger.getBinder(); - } - - /** - * Network state. - */ - private boolean mIsConnected; - private boolean mIsFailover; - private boolean mIsCellularConnection; - private boolean mIsRoaming; - private boolean mIsAtLeast3G; - private boolean mIsAtLeast4G; - private boolean mStateChanged; - - /** - * Download state - */ - private int mControl; - private int mStatus; - - public boolean isWiFi() { - return mIsConnected && !mIsCellularConnection; - } - - /** - * Bindings to important services - */ - private ConnectivityManager mConnectivityManager; - private WifiManager mWifiManager; - - /** - * Package we are downloading for (defaults to package of application) - */ - private PackageInfo mPackageInfo; - - /** - * Byte counts - */ - long mBytesSoFar; - long mTotalLength; - int mFileCount; - - /** - * Used for calculating time remaining and speed - */ - long mBytesAtSample; - long mMillisecondsAtSample; - float mAverageDownloadSpeed; - - /** - * Our binding to the network state broadcasts - */ - private BroadcastReceiver mConnReceiver; - final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this); - final private Messenger mServiceMessenger = mServiceStub.getMessenger(); - private Messenger mClientMessenger; - private DownloadNotification mNotification; - private PendingIntent mPendingIntent; - private PendingIntent mAlarmIntent; - - /** - * Updates the network type based upon the type and subtype returned from - * the connectivity manager. Subtype is only used for cellular signals. - * - * @param type - * @param subType - */ - private void updateNetworkType(int type, int subType) { - switch (type) { - case ConnectivityManager.TYPE_WIFI: - case ConnectivityManager.TYPE_ETHERNET: - case ConnectivityManager.TYPE_BLUETOOTH: - mIsCellularConnection = false; - mIsAtLeast3G = false; - mIsAtLeast4G = false; - break; - case ConnectivityManager.TYPE_WIMAX: - mIsCellularConnection = true; - mIsAtLeast3G = true; - mIsAtLeast4G = true; - break; - case ConnectivityManager.TYPE_MOBILE: - mIsCellularConnection = true; - switch (subType) { - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_GPRS: - case TelephonyManager.NETWORK_TYPE_IDEN: - mIsAtLeast3G = false; - mIsAtLeast4G = false; - break; - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_UMTS: - mIsAtLeast3G = true; - mIsAtLeast4G = false; - break; - case TelephonyManager.NETWORK_TYPE_LTE: // 4G - case TelephonyManager.NETWORK_TYPE_EHRPD: // 3G ++ interop - // with 4G - case TelephonyManager.NETWORK_TYPE_HSPAP: // 3G ++ but - // marketed as - // 4G - mIsAtLeast3G = true; - mIsAtLeast4G = true; - break; - default: - mIsCellularConnection = false; - mIsAtLeast3G = false; - mIsAtLeast4G = false; - } - } - } - - private void updateNetworkState(NetworkInfo info) { - boolean isConnected = mIsConnected; - boolean isFailover = mIsFailover; - boolean isCellularConnection = mIsCellularConnection; - boolean isRoaming = mIsRoaming; - boolean isAtLeast3G = mIsAtLeast3G; - if (null != info) { - mIsRoaming = info.isRoaming(); - mIsFailover = info.isFailover(); - mIsConnected = info.isConnected(); - updateNetworkType(info.getType(), info.getSubtype()); - } else { - mIsRoaming = false; - mIsFailover = false; - mIsConnected = false; - updateNetworkType(-1, -1); - } - mStateChanged = (mStateChanged || isConnected != mIsConnected - || isFailover != mIsFailover - || isCellularConnection != mIsCellularConnection - || isRoaming != mIsRoaming || isAtLeast3G != mIsAtLeast3G); - if (Constants.LOGVV) { - if (mStateChanged) { - Log.v(LOG_TAG, "Network state changed: "); - Log.v(LOG_TAG, "Starting State: " + - (isConnected ? "Connected " : "Not Connected ") + - (isCellularConnection ? "Cellular " : "WiFi ") + - (isRoaming ? "Roaming " : "Local ") + - (isAtLeast3G ? "3G+ " : "<3G ")); - Log.v(LOG_TAG, "Ending State: " + - (mIsConnected ? "Connected " : "Not Connected ") + - (mIsCellularConnection ? "Cellular " : "WiFi ") + - (mIsRoaming ? "Roaming " : "Local ") + - (mIsAtLeast3G ? "3G+ " : "<3G ")); - - if (isServiceRunning()) { - if (mIsRoaming) { - mStatus = STATUS_WAITING_FOR_NETWORK; - mControl = CONTROL_PAUSED; - } else if (mIsCellularConnection) { - DownloadsDB db = DownloadsDB.getDB(this); - int flags = db.getFlags(); - if (0 == (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { - mStatus = STATUS_QUEUED_FOR_WIFI; - mControl = CONTROL_PAUSED; - } - } - } - - } - } - } - - /** - * Polls the network state, setting the flags appropriately. - */ - void pollNetworkState() { - if (null == mConnectivityManager) { - mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - } - if (null == mWifiManager) { - mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); - } - if (mConnectivityManager == null) { - Log.w(Constants.TAG, - "couldn't get connectivity manager to poll network state"); - } else { - NetworkInfo activeInfo = mConnectivityManager - .getActiveNetworkInfo(); - updateNetworkState(activeInfo); - } - } - - public static final int NO_DOWNLOAD_REQUIRED = 0; - public static final int LVL_CHECK_REQUIRED = 1; - public static final int DOWNLOAD_REQUIRED = 2; - - public static final String EXTRA_PACKAGE_NAME = "EPN"; - public static final String EXTRA_PENDING_INTENT = "EPI"; - public static final String EXTRA_MESSAGE_HANDLER = "EMH"; - - /** - * Returns true if the LVL check is required - * - * @param db a downloads DB synchronized with the latest state - * @param pi the package info for the project - * @return returns true if the filenames need to be returned - */ - private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo pi) { - // we need to update the LVL check and get a successful status to - // proceed - if (db.mVersionCode != pi.versionCode) { - return true; - } - return false; - } - - /** - * Careful! Only use this internally. - * - * @return whether we think the service is running - */ - private static synchronized boolean isServiceRunning() { - return sIsRunning; - } - - private static synchronized void setServiceRunning(boolean isRunning) { - sIsRunning = isRunning; - } - - public static int startDownloadServiceIfRequired(Context context, - Intent intent, Class serviceClass) throws NameNotFoundException { - final PendingIntent pendingIntent = (PendingIntent) intent - .getParcelableExtra(EXTRA_PENDING_INTENT); - return startDownloadServiceIfRequired(context, pendingIntent, - serviceClass); - } - - public static int startDownloadServiceIfRequired(Context context, - PendingIntent pendingIntent, Class serviceClass) - throws NameNotFoundException - { - String packageName = context.getPackageName(); - String className = serviceClass.getName(); - - return startDownloadServiceIfRequired(context, pendingIntent, - packageName, className); - } - - /** - * Starts the download if necessary. This function starts a flow that does ` - * many things. 1) Checks to see if the APK version has been checked and the - * metadata database updated 2) If the APK version does not match, checks - * the new LVL status to see if a new download is required 3) If the APK - * version does match, then checks to see if the download(s) have been - * completed 4) If the downloads have been completed, returns - * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the - * startup of an application to quickly ascertain if the application needs - * to wait to hear about any updated APK expansion files. Note that this - * does mean that the application MUST be run for the first time with a - * network connection, even if Market delivers all of the files. - * - * @param context - * @param thisIntent - * @return true if the app should wait for more guidance from the - * downloader, false if the app can continue - * @throws NameNotFoundException - */ - public static int startDownloadServiceIfRequired(Context context, - PendingIntent pendingIntent, String classPackage, String className) - throws NameNotFoundException { - // first: do we need to do an LVL update? - // we begin by getting our APK version from the package manager - final PackageInfo pi = context.getPackageManager().getPackageInfo( - context.getPackageName(), 0); - - int status = NO_DOWNLOAD_REQUIRED; - - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(context); - - // we need to update the LVL check and get a successful status to - // proceed - if (isLVLCheckRequired(db, pi)) { - status = LVL_CHECK_REQUIRED; - } - // we don't have to update LVL. do we still have a download to start? - if (db.mStatus == 0) { - DownloadInfo[] infos = db.getDownloads(); - if (null != infos) { - for (DownloadInfo info : infos) { - if (!Helpers.doesFileExist(context, info.mFileName, info.mTotalBytes, true)) { - status = DOWNLOAD_REQUIRED; - db.updateStatus(-1); - break; - } - } - } - } else { - status = DOWNLOAD_REQUIRED; - } - switch (status) { - case DOWNLOAD_REQUIRED: - case LVL_CHECK_REQUIRED: - Intent fileIntent = new Intent(); - fileIntent.setClassName(classPackage, className); - fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent); - context.startService(fileIntent); - break; - } - return status; - } - - @Override - public void requestAbortDownload() { - mControl = CONTROL_PAUSED; - mStatus = STATUS_CANCELED; - } - - @Override - public void requestPauseDownload() { - mControl = CONTROL_PAUSED; - mStatus = STATUS_PAUSED_BY_APP; - } - - @Override - public void setDownloadFlags(int flags) { - DownloadsDB.getDB(this).updateFlags(flags); - } - - @Override - public void requestContinueDownload() { - if (mControl == CONTROL_PAUSED) { - mControl = CONTROL_RUN; - } - Intent fileIntent = new Intent(this, this.getClass()); - fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - this.startService(fileIntent); - } - - public abstract String getPublicKey(); - - public abstract byte[] getSALT(); - - public abstract String getAlarmReceiverClassName(); - - private class LVLRunnable implements Runnable { - LVLRunnable(Context context, PendingIntent intent) { - mContext = context; - mPendingIntent = intent; - } - - final Context mContext; - - @Override - public void run() { - setServiceRunning(true); - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_FETCHING_URL); - String deviceId = Secure.getString(mContext.getContentResolver(), - Secure.ANDROID_ID); - - final APKExpansionPolicy aep = new APKExpansionPolicy(mContext, - new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId)); - - // reset our policy back to the start of the world to force a - // re-check - aep.resetPolicy(); - - // let's try and get the OBB file from LVL first - // Construct the LicenseChecker with a Policy. - final LicenseChecker checker = new LicenseChecker(mContext, aep, - getPublicKey() // Your public licensing key. - ); - checker.checkAccess(new LicenseCheckerCallback() { - - @Override - public void allow(int reason) { - try { - int count = aep.getExpansionURLCount(); - DownloadsDB db = DownloadsDB.getDB(mContext); - int status = 0; - if (count != 0) { - for (int i = 0; i < count; i++) { - String currentFileName = aep - .getExpansionFileName(i); - if (null != currentFileName) { - DownloadInfo di = new DownloadInfo(i, - currentFileName, mContext.getPackageName()); - - long fileSize = aep.getExpansionFileSize(i); - if (handleFileUpdated(db, i, currentFileName, - fileSize)) { - status |= -1; - di.resetDownload(); - di.mUri = aep.getExpansionURL(i); - di.mTotalBytes = fileSize; - di.mStatus = status; - db.updateDownload(di); - } else { - // we need to read the download - // information - // from - // the database - DownloadInfo dbdi = db - .getDownloadInfoByFileName(di.mFileName); - if (null == dbdi) { - // the file exists already and is - // the - // correct size - // was delivered by Market or - // through - // another mechanism - Log.d(LOG_TAG, "file " + di.mFileName - + " found. Not downloading."); - di.mStatus = STATUS_SUCCESS; - di.mTotalBytes = fileSize; - di.mCurrentBytes = fileSize; - di.mUri = aep.getExpansionURL(i); - db.updateDownload(di); - } else if (dbdi.mStatus != STATUS_SUCCESS) { - // we just update the URL - dbdi.mUri = aep.getExpansionURL(i); - db.updateDownload(dbdi); - status |= -1; - } - } - } - } - } - // first: do we need to do an LVL update? - // we begin by getting our APK version from the package - // manager - PackageInfo pi; - try { - pi = mContext.getPackageManager().getPackageInfo( - mContext.getPackageName(), 0); - db.updateMetadata(pi.versionCode, status); - Class serviceClass = DownloaderService.this.getClass(); - switch (startDownloadServiceIfRequired(mContext, mPendingIntent, - serviceClass)) { - case NO_DOWNLOAD_REQUIRED: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); - break; - case LVL_CHECK_REQUIRED: - // DANGER WILL ROBINSON! - Log.e(LOG_TAG, "In LVL checking loop!"); - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); - throw new RuntimeException( - "Error with LVL checking and database integrity"); - case DOWNLOAD_REQUIRED: - // do nothing. the download will notify the - // application - // when things are done - break; - } - } catch (NameNotFoundException e1) { - e1.printStackTrace(); - throw new RuntimeException( - "Error with getting information from package name"); - } - } finally { - setServiceRunning(false); - } - } - - @Override - public void dontAllow(int reason) { - try - { - switch (reason) { - case Policy.NOT_LICENSED: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED); - break; - case Policy.RETRY: - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); - break; - } - } finally { - setServiceRunning(false); - } - - } - - @Override - public void applicationError(int errorCode) { - try { - mNotification - .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL); - } finally { - setServiceRunning(false); - } - } - - }); - - } - - }; - - /** - * Updates the LVL information from the server. - * - * @param context - */ - public void updateLVL(final Context context) { - Context c = context.getApplicationContext(); - Handler h = new Handler(c.getMainLooper()); - h.post(new LVLRunnable(c, mPendingIntent)); - } - - /** - * The APK has been updated and a filename has been sent down from the - * Market call. If the file has the same name as the previous file, we do - * nothing as the file is guaranteed to be the same. If the file does not - * have the same name, we download it if it hasn't already been delivered by - * Market. - * - * @param index the index of the file from market (0 = main, 1 = patch) - * @param filename the name of the new file - * @param fileSize the size of the new file - * @return - */ - public boolean handleFileUpdated(DownloadsDB db, int index, - String filename, long fileSize) { - DownloadInfo di = db.getDownloadInfoByFileName(filename); - if (null != di) { - String oldFile = di.mFileName; - // cleanup - if (null != oldFile) { - if (filename.equals(oldFile)) { - return false; - } - - // remove partially downloaded file if it is there - String deleteFile = Helpers.generateSaveFileName(this, oldFile); - File f = new File(deleteFile); - if (f.exists()) - f.delete(); - } - } - return !Helpers.doesFileExist(this, filename, fileSize, true); - } - - private void scheduleAlarm(long wakeUp) { - AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - return; - } - - if (Constants.LOGV) { - Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); - } - - String className = getAlarmReceiverClassName(); - Intent intent = new Intent(Constants.ACTION_RETRY); - intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - intent.setClassName(this.getPackageName(), - className); - mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, - PendingIntent.FLAG_ONE_SHOT); - alarms.set( - AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + wakeUp, mAlarmIntent - ); - } - - private void cancelAlarms() { - if (null != mAlarmIntent) { - AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - return; - } - alarms.cancel(mAlarmIntent); - mAlarmIntent = null; - } - } - - /** - * We use this to track network state, such as when WiFi, Cellular, etc. is - * enabled when downloads are paused or in progress. - */ - private class InnerBroadcastReceiver extends BroadcastReceiver { - final Service mService; - - InnerBroadcastReceiver(Service service) { - mService = service; - } - - @Override - public void onReceive(Context context, Intent intent) { - pollNetworkState(); - if (mStateChanged - && !isServiceRunning()) { - Log.d(Constants.TAG, "InnerBroadcastReceiver Called"); - Intent fileIntent = new Intent(context, mService.getClass()); - fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent); - // send a new intent to the service - context.startService(fileIntent); - } - } - }; - - /** - * This is the main thread for the Downloader. This thread is responsible - * for queuing up downloads and other goodness. - */ - @Override - protected void onHandleIntent(Intent intent) { - setServiceRunning(true); - try { - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(this); - final PendingIntent pendingIntent = (PendingIntent) intent - .getParcelableExtra(EXTRA_PENDING_INTENT); - - if (null != pendingIntent) - { - mNotification.setClientIntent(pendingIntent); - mPendingIntent = pendingIntent; - } else if (null != mPendingIntent) { - mNotification.setClientIntent(mPendingIntent); - } else { - Log.e(LOG_TAG, "Downloader started in bad state without notification intent."); - return; - } - - // when the LVL check completes, a successful response will update - // the service - if (isLVLCheckRequired(db, mPackageInfo)) { - updateLVL(this); - return; - } - - // get each download - DownloadInfo[] infos = db.getDownloads(); - mBytesSoFar = 0; - mTotalLength = 0; - mFileCount = infos.length; - for (DownloadInfo info : infos) { - // We do an (simple) integrity check on each file, just to make - // sure - if (info.mStatus == STATUS_SUCCESS) { - // verify that the file matches the state - if (!Helpers.doesFileExist(this, info.mFileName, info.mTotalBytes, true)) { - info.mStatus = 0; - info.mCurrentBytes = 0; - } - } - // get aggregate data - mTotalLength += info.mTotalBytes; - mBytesSoFar += info.mCurrentBytes; - } - - // loop through all downloads and fetch them - pollNetworkState(); - if (null == mConnReceiver) { - - /** - * We use this to track network state, such as when WiFi, - * Cellular, etc. is enabled when downloads are paused or in - * progress. - */ - mConnReceiver = new InnerBroadcastReceiver(this); - IntentFilter intentFilter = new IntentFilter( - ConnectivityManager.CONNECTIVITY_ACTION); - intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - registerReceiver(mConnReceiver, intentFilter); - } - - for (DownloadInfo info : infos) { - long startingCount = info.mCurrentBytes; - - if (info.mStatus != STATUS_SUCCESS) { - DownloadThread dt = new DownloadThread(info, this, mNotification); - cancelAlarms(); - scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG); - dt.run(); - cancelAlarms(); - } - db.updateFromDb(info); - boolean setWakeWatchdog = false; - int notifyStatus; - switch (info.mStatus) { - case STATUS_FORBIDDEN: - // the URL is out of date - updateLVL(this); - return; - case STATUS_SUCCESS: - mBytesSoFar += info.mCurrentBytes - startingCount; - db.updateMetadata(mPackageInfo.versionCode, 0); - continue; - case STATUS_FILE_DELIVERED_INCORRECTLY: - // we may be on a network that is returning us a web - // page on redirect - notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE; - info.mCurrentBytes = 0; - db.updateDownload(info); - setWakeWatchdog = true; - break; - case STATUS_PAUSED_BY_APP: - notifyStatus = IDownloaderClient.STATE_PAUSED_BY_REQUEST; - break; - case STATUS_WAITING_FOR_NETWORK: - case STATUS_WAITING_TO_RETRY: - notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE; - setWakeWatchdog = true; - break; - case STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION: - case STATUS_QUEUED_FOR_WIFI: - // look for more detail here - if (null != mWifiManager) { - if (!mWifiManager.isWifiEnabled()) { - notifyStatus = IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION; - setWakeWatchdog = true; - break; - } - } - notifyStatus = IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION; - setWakeWatchdog = true; - break; - case STATUS_CANCELED: - notifyStatus = IDownloaderClient.STATE_FAILED_CANCELED; - setWakeWatchdog = true; - break; - - case STATUS_INSUFFICIENT_SPACE_ERROR: - notifyStatus = IDownloaderClient.STATE_FAILED_SDCARD_FULL; - setWakeWatchdog = true; - break; - - case STATUS_DEVICE_NOT_FOUND_ERROR: - notifyStatus = IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE; - setWakeWatchdog = true; - break; - - default: - notifyStatus = IDownloaderClient.STATE_FAILED; - break; - } - if (setWakeWatchdog) { - scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER); - } else { - cancelAlarms(); - } - // failure or pause state - mNotification.onDownloadStateChanged(notifyStatus); - return; - } - - // all downloads complete - mNotification.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED); - } finally { - setServiceRunning(false); - } - } - - @Override - public void onDestroy() { - if (null != mConnReceiver) { - unregisterReceiver(mConnReceiver); - mConnReceiver = null; - } - mServiceStub.disconnect(this); - super.onDestroy(); - } - - public int getNetworkAvailabilityState(DownloadsDB db) { - if (mIsConnected) { - if (!mIsCellularConnection) - return NETWORK_OK; - int flags = db.mFlags; - if (mIsRoaming) - return NETWORK_CANNOT_USE_ROAMING; - if (0 != (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) { - return NETWORK_OK; - } else { - return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR; - } - } - return NETWORK_NO_CONNECTION; - } - - @Override - public void onCreate() { - super.onCreate(); - try { - mPackageInfo = getPackageManager().getPackageInfo( - getPackageName(), 0); - ApplicationInfo ai = getApplicationInfo(); - CharSequence applicationLabel = getPackageManager().getApplicationLabel(ai); - mNotification = new DownloadNotification(this, applicationLabel); - - } catch (NameNotFoundException e) { - e.printStackTrace(); - } - } - - /** - * Exception thrown from methods called by generateSaveFile() for any fatal - * error. - */ - public static class GenerateSaveFileError extends Exception { - private static final long serialVersionUID = 3465966015408936540L; - int mStatus; - String mMessage; - - public GenerateSaveFileError(int status, String message) { - mStatus = status; - mMessage = message; - } - } - - /** - * Returns the filename (where the file should be saved) from info about a - * download - */ - public String generateTempSaveFileName(String fileName) { - String path = Helpers.getSaveFilePath(this) - + File.separator + fileName + TEMP_EXT; - return path; - } - - /** - * Creates a filename (where the file should be saved) from info about a - * download. - */ - public String generateSaveFile(String filename, long filesize) - throws GenerateSaveFileError { - String path = generateTempSaveFileName(filename); - File expPath = new File(path); - if (!Helpers.isExternalMediaMounted()) { - Log.d(Constants.TAG, "External media not mounted: " + path); - throw new GenerateSaveFileError(STATUS_DEVICE_NOT_FOUND_ERROR, - "external media is not yet mounted"); - - } - if (expPath.exists()) { - Log.d(Constants.TAG, "File already exists: " + path); - throw new GenerateSaveFileError(STATUS_FILE_ALREADY_EXISTS_ERROR, - "requested destination file already exists"); - } - if (Helpers.getAvailableBytes(Helpers.getFilesystemRoot(path)) < filesize) { - throw new GenerateSaveFileError(STATUS_INSUFFICIENT_SPACE_ERROR, - "insufficient space on external storage"); - } - return path; - } - - /** - * @return a non-localized string appropriate for logging corresponding to - * one of the NETWORK_* constants. - */ - public String getLogMessageForNetworkError(int networkError) { - switch (networkError) { - case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE: - return "download size exceeds recommended limit for mobile network"; - - case NETWORK_UNUSABLE_DUE_TO_SIZE: - return "download size exceeds limit for mobile network"; - - case NETWORK_NO_CONNECTION: - return "no network connection available"; - - case NETWORK_CANNOT_USE_ROAMING: - return "download cannot use the current network connection because it is roaming"; - - case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: - return "download was requested to not use the current network type"; - - default: - return "unknown error with network connectivity"; - } - } - - public int getControl() { - return mControl; - } - - public int getStatus() { - return mStatus; - } - - /** - * Calculating a moving average for the speed so we don't get jumpy - * calculations for time etc. - */ - static private final float SMOOTHING_FACTOR = 0.005f; - - public void notifyUpdateBytes(long totalBytesSoFar) { - long timeRemaining; - long currentTime = SystemClock.uptimeMillis(); - if (0 != mMillisecondsAtSample) { - // we have a sample. - long timePassed = currentTime - mMillisecondsAtSample; - long bytesInSample = totalBytesSoFar - mBytesAtSample; - float currentSpeedSample = (float) bytesInSample / (float) timePassed; - if (0 != mAverageDownloadSpeed) { - mAverageDownloadSpeed = SMOOTHING_FACTOR * currentSpeedSample - + (1 - SMOOTHING_FACTOR) * mAverageDownloadSpeed; - } else { - mAverageDownloadSpeed = currentSpeedSample; - } - timeRemaining = (long) ((mTotalLength - totalBytesSoFar) / mAverageDownloadSpeed); - } else { - timeRemaining = -1; - } - mMillisecondsAtSample = currentTime; - mBytesAtSample = totalBytesSoFar; - mNotification.onDownloadProgress( - new DownloadProgressInfo(mTotalLength, - totalBytesSoFar, - timeRemaining, - mAverageDownloadSpeed) - ); - - } - - @Override - protected boolean shouldStop() { - // the database automatically reads the metadata for version code - // and download status when the instance is created - DownloadsDB db = DownloadsDB.getDB(this); - if (db.mStatus == 0) { - return true; - } - return false; - } - - @Override - public void requestDownloadStatus() { - mNotification.resendState(); - } - - @Override - public void onClientUpdated(Messenger clientMessenger) { - this.mClientMessenger = clientMessenger; - mNotification.setMessenger(mClientMessenger); - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java deleted file mode 100755 index 250299c400..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteDoneException; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteStatement; -import android.provider.BaseColumns; -import android.util.Log; - -public class DownloadsDB { - private static final String DATABASE_NAME = "DownloadsDB"; - private static final int DATABASE_VERSION = 7; - public static final String LOG_TAG = DownloadsDB.class.getName(); - final SQLiteOpenHelper mHelper; - SQLiteStatement mGetDownloadByIndex; - SQLiteStatement mUpdateCurrentBytes; - private static DownloadsDB mDownloadsDB; - long mMetadataRowID = -1; - int mVersionCode = -1; - int mStatus = -1; - int mFlags; - - static public synchronized DownloadsDB getDB(Context paramContext) { - if (null == mDownloadsDB) { - return new DownloadsDB(paramContext); - } - return mDownloadsDB; - } - - private SQLiteStatement getDownloadByIndexStatement() { - if (null == mGetDownloadByIndex) { - mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement( - "SELECT " + BaseColumns._ID + " FROM " - + DownloadColumns.TABLE_NAME + " WHERE " - + DownloadColumns.INDEX + " = ?"); - } - return mGetDownloadByIndex; - } - - private SQLiteStatement getUpdateCurrentBytesStatement() { - if (null == mUpdateCurrentBytes) { - mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement( - "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES - + " = ?" + - " WHERE " + DownloadColumns.INDEX + " = ?"); - } - return mUpdateCurrentBytes; - } - - private DownloadsDB(Context paramContext) { - this.mHelper = new DownloadsContentDBHelper(paramContext); - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - // Query for the version code, the row ID of the metadata (for future - // updating) the status and the flags - Cursor cur = sqldb.rawQuery("SELECT " + - MetadataColumns.APKVERSION + "," + - BaseColumns._ID + "," + - MetadataColumns.DOWNLOAD_STATUS + "," + - MetadataColumns.FLAGS + - " FROM " - + MetadataColumns.TABLE_NAME + " LIMIT 1", null); - if (null != cur && cur.moveToFirst()) { - mVersionCode = cur.getInt(0); - mMetadataRowID = cur.getLong(1); - mStatus = cur.getInt(2); - mFlags = cur.getInt(3); - cur.close(); - } - mDownloadsDB = this; - } - - protected DownloadInfo getDownloadInfoByFileName(String fileName) { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor itemcur = null; - try { - itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, - DownloadColumns.FILENAME + " = ?", - new String[] { - fileName - }, null, null, null); - if (null != itemcur && itemcur.moveToFirst()) { - return getDownloadInfoFromCursor(itemcur); - } - } finally { - if (null != itemcur) - itemcur.close(); - } - return null; - } - - public long getIDForDownloadInfo(final DownloadInfo di) { - return getIDByIndex(di.mIndex); - } - - public long getIDByIndex(int index) { - SQLiteStatement downloadByIndex = getDownloadByIndexStatement(); - downloadByIndex.clearBindings(); - downloadByIndex.bindLong(1, index); - try { - return downloadByIndex.simpleQueryForLong(); - } catch (SQLiteDoneException e) { - return -1; - } - } - - public void updateDownloadCurrentBytes(final DownloadInfo di) { - SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement(); - downloadCurrentBytes.clearBindings(); - downloadCurrentBytes.bindLong(1, di.mCurrentBytes); - downloadCurrentBytes.bindLong(2, di.mIndex); - downloadCurrentBytes.execute(); - } - - public void close() { - this.mHelper.close(); - } - - protected static class DownloadsContentDBHelper extends SQLiteOpenHelper { - DownloadsContentDBHelper(Context paramContext) { - super(paramContext, DATABASE_NAME, null, DATABASE_VERSION); - } - - private String createTableQueryFromArray(String paramString, - String[][] paramArrayOfString) { - StringBuilder localStringBuilder = new StringBuilder(); - localStringBuilder.append("CREATE TABLE "); - localStringBuilder.append(paramString); - localStringBuilder.append(" ("); - int i = paramArrayOfString.length; - for (int j = 0;; j++) { - if (j >= i) { - localStringBuilder - .setLength(localStringBuilder.length() - 1); - localStringBuilder.append(");"); - return localStringBuilder.toString(); - } - String[] arrayOfString = paramArrayOfString[j]; - localStringBuilder.append(' '); - localStringBuilder.append(arrayOfString[0]); - localStringBuilder.append(' '); - localStringBuilder.append(arrayOfString[1]); - localStringBuilder.append(','); - } - } - - /** - * These two arrays must match and have the same order. For every Schema - * there must be a corresponding table name. - */ - static final private String[][][] sSchemas = { - DownloadColumns.SCHEMA, MetadataColumns.SCHEMA - }; - - static final private String[] sTables = { - DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME - }; - - /** - * Goes through all of the tables in sTables and drops each table if it - * exists. Altered to no longer make use of reflection. - */ - private void dropTables(SQLiteDatabase paramSQLiteDatabase) { - for (String table : sTables) { - try { - paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table); - } catch (Exception localException) { - localException.printStackTrace(); - } - } - } - - /** - * Goes through all of the tables in sTables and creates a database with - * the corresponding schema described in sSchemas. Altered to no longer - * make use of reflection. - */ - public void onCreate(SQLiteDatabase paramSQLiteDatabase) { - int numSchemas = sSchemas.length; - for (int i = 0; i < numSchemas; i++) { - try { - String[][] schema = (String[][]) sSchemas[i]; - paramSQLiteDatabase.execSQL(createTableQueryFromArray( - sTables[i], schema)); - } catch (Exception localException) { - while (true) - localException.printStackTrace(); - } - } - } - - public void onUpgrade(SQLiteDatabase paramSQLiteDatabase, - int paramInt1, int paramInt2) { - Log.w(DownloadsContentDBHelper.class.getName(), - "Upgrading database from version " + paramInt1 + " to " - + paramInt2 + ", which will destroy all old data"); - dropTables(paramSQLiteDatabase); - onCreate(paramSQLiteDatabase); - } - } - - public static class MetadataColumns implements BaseColumns { - public static final String APKVERSION = "APKVERSION"; - public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS"; - public static final String FLAGS = "DOWNLOADFLAGS"; - - public static final String[][] SCHEMA = { - { - BaseColumns._ID, "INTEGER PRIMARY KEY" - }, - { - APKVERSION, "INTEGER" - }, { - DOWNLOAD_STATUS, "INTEGER" - }, - { - FLAGS, "INTEGER" - } - }; - public static final String TABLE_NAME = "MetadataColumns"; - public static final String _ID = "MetadataColumns._id"; - } - - public static class DownloadColumns implements BaseColumns { - public static final String INDEX = "FILEIDX"; - public static final String URI = "URI"; - public static final String FILENAME = "FN"; - public static final String ETAG = "ETAG"; - - public static final String TOTALBYTES = "TOTALBYTES"; - public static final String CURRENTBYTES = "CURRENTBYTES"; - public static final String LASTMOD = "LASTMOD"; - - public static final String STATUS = "STATUS"; - public static final String CONTROL = "CONTROL"; - public static final String NUM_FAILED = "FAILCOUNT"; - public static final String RETRY_AFTER = "RETRYAFTER"; - public static final String REDIRECT_COUNT = "REDIRECTCOUNT"; - - public static final String[][] SCHEMA = { - { - BaseColumns._ID, "INTEGER PRIMARY KEY" - }, - { - INDEX, "INTEGER UNIQUE" - }, { - URI, "TEXT" - }, - { - FILENAME, "TEXT UNIQUE" - }, { - ETAG, "TEXT" - }, - { - TOTALBYTES, "INTEGER" - }, { - CURRENTBYTES, "INTEGER" - }, - { - LASTMOD, "INTEGER" - }, { - STATUS, "INTEGER" - }, - { - CONTROL, "INTEGER" - }, { - NUM_FAILED, "INTEGER" - }, - { - RETRY_AFTER, "INTEGER" - }, { - REDIRECT_COUNT, "INTEGER" - } - }; - public static final String TABLE_NAME = "DownloadColumns"; - public static final String _ID = "DownloadColumns._id"; - } - - private static final String[] DC_PROJECTION = { - DownloadColumns.FILENAME, - DownloadColumns.URI, DownloadColumns.ETAG, - DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES, - DownloadColumns.LASTMOD, DownloadColumns.STATUS, - DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED, - DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT, - DownloadColumns.INDEX - }; - - private static final int FILENAME_IDX = 0; - private static final int URI_IDX = 1; - private static final int ETAG_IDX = 2; - private static final int TOTALBYTES_IDX = 3; - private static final int CURRENTBYTES_IDX = 4; - private static final int LASTMOD_IDX = 5; - private static final int STATUS_IDX = 6; - private static final int CONTROL_IDX = 7; - private static final int NUM_FAILED_IDX = 8; - private static final int RETRY_AFTER_IDX = 9; - private static final int REDIRECT_COUNT_IDX = 10; - private static final int INDEX_IDX = 11; - - /** - * This function will add a new file to the database if it does not exist. - * - * @param di DownloadInfo that we wish to store - * @return the row id of the record to be updated/inserted, or -1 - */ - public boolean updateDownload(DownloadInfo di) { - ContentValues cv = new ContentValues(); - cv.put(DownloadColumns.INDEX, di.mIndex); - cv.put(DownloadColumns.FILENAME, di.mFileName); - cv.put(DownloadColumns.URI, di.mUri); - cv.put(DownloadColumns.ETAG, di.mETag); - cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes); - cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes); - cv.put(DownloadColumns.LASTMOD, di.mLastMod); - cv.put(DownloadColumns.STATUS, di.mStatus); - cv.put(DownloadColumns.CONTROL, di.mControl); - cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed); - cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter); - cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount); - return updateDownload(di, cv); - } - - public boolean updateDownload(DownloadInfo di, ContentValues cv) { - long id = di == null ? -1 : getIDForDownloadInfo(di); - try { - final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); - if (id != -1) { - if (1 != sqldb.update(DownloadColumns.TABLE_NAME, - cv, DownloadColumns._ID + " = " + id, null)) { - return false; - } - } else { - return -1 != sqldb.insert(DownloadColumns.TABLE_NAME, - DownloadColumns.URI, cv); - } - } catch (android.database.sqlite.SQLiteException ex) { - ex.printStackTrace(); - } - return false; - } - - public int getLastCheckedVersionCode() { - return mVersionCode; - } - - public boolean isDownloadRequired() { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM " - + DownloadColumns.TABLE_NAME + " WHERE " - + DownloadColumns.STATUS + " <> 0", null); - try { - if (null != cur && cur.moveToFirst()) { - return 0 == cur.getInt(0); - } - } finally { - if (null != cur) - cur.close(); - } - return true; - } - - public int getFlags() { - return mFlags; - } - - public boolean updateFlags(int flags) { - if (mFlags != flags) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.FLAGS, flags); - if (updateMetadata(cv)) { - mFlags = flags; - return true; - } else { - return false; - } - } else { - return true; - } - }; - - public boolean updateStatus(int status) { - if (mStatus != status) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.DOWNLOAD_STATUS, status); - if (updateMetadata(cv)) { - mStatus = status; - return true; - } else { - return false; - } - } else { - return true; - } - }; - - public boolean updateMetadata(ContentValues cv) { - final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); - if (-1 == this.mMetadataRowID) { - long newID = sqldb.insert(MetadataColumns.TABLE_NAME, - MetadataColumns.APKVERSION, cv); - if (-1 == newID) - return false; - mMetadataRowID = newID; - } else { - if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv, - BaseColumns._ID + " = " + mMetadataRowID, null)) - return false; - } - return true; - } - - public boolean updateMetadata(int apkVersion, int downloadStatus) { - ContentValues cv = new ContentValues(); - cv.put(MetadataColumns.APKVERSION, apkVersion); - cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus); - if (updateMetadata(cv)) { - mVersionCode = apkVersion; - mStatus = downloadStatus; - return true; - } else { - return false; - } - }; - - public boolean updateFromDb(DownloadInfo di) { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = null; - try { - cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, - DownloadColumns.FILENAME + "= ?", - new String[] { - di.mFileName - }, null, null, null); - if (null != cur && cur.moveToFirst()) { - setDownloadInfoFromCursor(di, cur); - return true; - } - return false; - } finally { - if (null != cur) { - cur.close(); - } - } - } - - public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) { - di.mUri = cur.getString(URI_IDX); - di.mETag = cur.getString(ETAG_IDX); - di.mTotalBytes = cur.getLong(TOTALBYTES_IDX); - di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX); - di.mLastMod = cur.getLong(LASTMOD_IDX); - di.mStatus = cur.getInt(STATUS_IDX); - di.mControl = cur.getInt(CONTROL_IDX); - di.mNumFailed = cur.getInt(NUM_FAILED_IDX); - di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX); - di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX); - } - - public DownloadInfo getDownloadInfoFromCursor(Cursor cur) { - DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX), - cur.getString(FILENAME_IDX), this.getClass().getPackage() - .getName()); - setDownloadInfoFromCursor(di, cur); - return di; - } - - public DownloadInfo[] getDownloads() { - final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); - Cursor cur = null; - try { - cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null, - null, null, null, null); - if (null != cur && cur.moveToFirst()) { - DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()]; - int idx = 0; - do { - DownloadInfo di = getDownloadInfoFromCursor(cur); - retInfos[idx++] = di; - } while (cur.moveToNext()); - return retInfos; - } - return null; - } finally { - if (null != cur) { - cur.close(); - } - } - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java deleted file mode 100644 index 3f440e9893..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import android.text.format.Time; - -import java.util.Calendar; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Helper for parsing an HTTP date. - */ -public final class HttpDateTime { - - /* - * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT - * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850, - * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format - * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon - * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS - * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon - * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first - * digit is zero. Mon can be the full name of the month. - */ - private static final String HTTP_DATE_RFC_REGEXP = - "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" - + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; - - private static final String HTTP_DATE_ANSIC_REGEXP = - "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" - + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; - - /** - * The compiled version of the HTTP-date regular expressions. - */ - private static final Pattern HTTP_DATE_RFC_PATTERN = - Pattern.compile(HTTP_DATE_RFC_REGEXP); - private static final Pattern HTTP_DATE_ANSIC_PATTERN = - Pattern.compile(HTTP_DATE_ANSIC_REGEXP); - - private static class TimeOfDay { - TimeOfDay(int h, int m, int s) { - this.hour = h; - this.minute = m; - this.second = s; - } - - int hour; - int minute; - int second; - } - - public static long parse(String timeString) - throws IllegalArgumentException { - - int date = 1; - int month = Calendar.JANUARY; - int year = 1970; - TimeOfDay timeOfDay; - - Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); - if (rfcMatcher.find()) { - date = getDate(rfcMatcher.group(1)); - month = getMonth(rfcMatcher.group(2)); - year = getYear(rfcMatcher.group(3)); - timeOfDay = getTime(rfcMatcher.group(4)); - } else { - Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); - if (ansicMatcher.find()) { - month = getMonth(ansicMatcher.group(1)); - date = getDate(ansicMatcher.group(2)); - timeOfDay = getTime(ansicMatcher.group(3)); - year = getYear(ansicMatcher.group(4)); - } else { - throw new IllegalArgumentException(); - } - } - - // FIXME: Y2038 BUG! - if (year >= 2038) { - year = 2038; - month = Calendar.JANUARY; - date = 1; - } - - Time time = new Time(Time.TIMEZONE_UTC); - time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, - month, year); - return time.toMillis(false /* use isDst */); - } - - private static int getDate(String dateString) { - if (dateString.length() == 2) { - return (dateString.charAt(0) - '0') * 10 - + (dateString.charAt(1) - '0'); - } else { - return (dateString.charAt(0) - '0'); - } - } - - /* - * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0 - * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20 - * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19 - * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9 - */ - private static int getMonth(String monthString) { - int hash = Character.toLowerCase(monthString.charAt(0)) + - Character.toLowerCase(monthString.charAt(1)) + - Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; - switch (hash) { - case 22: - return Calendar.JANUARY; - case 10: - return Calendar.FEBRUARY; - case 29: - return Calendar.MARCH; - case 32: - return Calendar.APRIL; - case 36: - return Calendar.MAY; - case 42: - return Calendar.JUNE; - case 40: - return Calendar.JULY; - case 26: - return Calendar.AUGUST; - case 37: - return Calendar.SEPTEMBER; - case 35: - return Calendar.OCTOBER; - case 48: - return Calendar.NOVEMBER; - case 9: - return Calendar.DECEMBER; - default: - throw new IllegalArgumentException(); - } - } - - private static int getYear(String yearString) { - if (yearString.length() == 2) { - int year = (yearString.charAt(0) - '0') * 10 - + (yearString.charAt(1) - '0'); - if (year >= 70) { - return year + 1900; - } else { - return year + 2000; - } - } else if (yearString.length() == 3) { - // According to RFC 2822, three digit years should be added to 1900. - int year = (yearString.charAt(0) - '0') * 100 - + (yearString.charAt(1) - '0') * 10 - + (yearString.charAt(2) - '0'); - return year + 1900; - } else if (yearString.length() == 4) { - return (yearString.charAt(0) - '0') * 1000 - + (yearString.charAt(1) - '0') * 100 - + (yearString.charAt(2) - '0') * 10 - + (yearString.charAt(3) - '0'); - } else { - return 1970; - } - } - - private static TimeOfDay getTime(String timeString) { - // HH might be H - int i = 0; - int hour = timeString.charAt(i++) - '0'; - if (timeString.charAt(i) != ':') - hour = hour * 10 + (timeString.charAt(i++) - '0'); - // Skip ':' - i++; - - int minute = (timeString.charAt(i++) - '0') * 10 - + (timeString.charAt(i++) - '0'); - // Skip ':' - i++; - - int second = (timeString.charAt(i++) - '0') * 10 - + (timeString.charAt(i++) - '0'); - - return new TimeOfDay(hour, minute, second); - } -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java deleted file mode 100644 index e736603e2a..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V14CustomNotification.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.android.vending.expansion.downloader.R; -import com.google.android.vending.expansion.downloader.Helpers; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; - -public class V14CustomNotification implements DownloadNotification.ICustomNotification { - - CharSequence mTitle; - CharSequence mTicker; - int mIcon; - long mTotalKB = -1; - long mCurrentKB = -1; - long mTimeRemaining; - PendingIntent mPendingIntent; - - @Override - public void setIcon(int icon) { - mIcon = icon; - } - - @Override - public void setTitle(CharSequence title) { - mTitle = title; - } - - @Override - public void setTotalBytes(long totalBytes) { - mTotalKB = totalBytes; - } - - @Override - public void setCurrentBytes(long currentBytes) { - mCurrentKB = currentBytes; - } - - void setProgress(Notification.Builder builder) { - - } - - @Override - public Notification updateNotification(Context c) { - Notification.Builder builder = new Notification.Builder(c); - builder.setContentTitle(mTitle); - if (mTotalKB > 0 && -1 != mCurrentKB) { - builder.setProgress((int) (mTotalKB >> 8), (int) (mCurrentKB >> 8), false); - } else { - builder.setProgress(0, 0, true); - } - builder.setContentText(Helpers.getDownloadProgressString(mCurrentKB, mTotalKB)); - builder.setContentInfo(c.getString(R.string.time_remaining_notification, - Helpers.getTimeRemaining(mTimeRemaining))); - if (mIcon != 0) { - builder.setSmallIcon(mIcon); - } else { - int iconResource = android.R.drawable.stat_sys_download; - builder.setSmallIcon(iconResource); - } - builder.setOngoing(true); - builder.setTicker(mTicker); - builder.setContentIntent(mPendingIntent); - builder.setOnlyAlertOnce(true); - - return builder.getNotification(); - } - - @Override - public void setPendingIntent(PendingIntent contentIntent) { - mPendingIntent = contentIntent; - } - - @Override - public void setTicker(CharSequence ticker) { - mTicker = ticker; - } - - @Override - public void setTimeRemaining(long timeRemaining) { - mTimeRemaining = timeRemaining; - } - -} diff --git a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java b/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java deleted file mode 100644 index e3666e05b9..0000000000 --- a/platform/android/libs/downloader_library/src/com/google/android/vending/expansion/downloader/impl/V3CustomNotification.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.expansion.downloader.impl; - -import com.android.vending.expansion.downloader.R; -import com.google.android.vending.expansion.downloader.Helpers; - -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; -import android.graphics.BitmapFactory; -import android.view.View; -import android.widget.RemoteViews; - -public class V3CustomNotification implements DownloadNotification.ICustomNotification { - - CharSequence mTitle; - CharSequence mTicker; - int mIcon; - long mTotalBytes = -1; - long mCurrentBytes = -1; - long mTimeRemaining; - PendingIntent mPendingIntent; - Notification mNotification = new Notification(); - - @Override - public void setIcon(int icon) { - mIcon = icon; - } - - @Override - public void setTitle(CharSequence title) { - mTitle = title; - } - - @Override - public void setTotalBytes(long totalBytes) { - mTotalBytes = totalBytes; - } - - @Override - public void setCurrentBytes(long currentBytes) { - mCurrentBytes = currentBytes; - } - - @Override - public Notification updateNotification(Context c) { - Notification n = mNotification; - - n.icon = mIcon; - - n.flags |= Notification.FLAG_ONGOING_EVENT; - - if (android.os.Build.VERSION.SDK_INT > 10) { - n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; // only matters for - // Honeycomb - } - - // Build the RemoteView object - RemoteViews expandedView = new RemoteViews( - c.getPackageName(), - R.layout.status_bar_ongoing_event_progress_bar); - - expandedView.setTextViewText(R.id.title, mTitle); - // look at strings - expandedView.setViewVisibility(R.id.description, View.VISIBLE); - expandedView.setTextViewText(R.id.description, - Helpers.getDownloadProgressString(mCurrentBytes, mTotalBytes)); - expandedView.setViewVisibility(R.id.progress_bar_frame, View.VISIBLE); - expandedView.setProgressBar(R.id.progress_bar, - (int) (mTotalBytes >> 8), - (int) (mCurrentBytes >> 8), - mTotalBytes <= 0); - expandedView.setViewVisibility(R.id.time_remaining, View.VISIBLE); - expandedView.setTextViewText( - R.id.time_remaining, - c.getString(R.string.time_remaining_notification, - Helpers.getTimeRemaining(mTimeRemaining))); - expandedView.setTextViewText(R.id.progress_text, - Helpers.getDownloadProgressPercent(mCurrentBytes, mTotalBytes)); - expandedView.setImageViewResource(R.id.appIcon, mIcon); - n.contentView = expandedView; - n.contentIntent = mPendingIntent; - return n; - } - - @Override - public void setPendingIntent(PendingIntent contentIntent) { - mPendingIntent = contentIntent; - } - - @Override - public void setTicker(CharSequence ticker) { - mTicker = ticker; - } - - @Override - public void setTimeRemaining(long timeRemaining) { - mTimeRemaining = timeRemaining; - } - -} diff --git a/platform/android/libs/google_play_services/.classpath b/platform/android/libs/google_play_services/.classpath deleted file mode 100644 index 7bc01d9a9c..0000000000 --- a/platform/android/libs/google_play_services/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/platform/android/libs/google_play_services/AndroidManifest.xml b/platform/android/libs/google_play_services/AndroidManifest.xml deleted file mode 100644 index aecd02b5d0..0000000000 --- a/platform/android/libs/google_play_services/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/platform/android/libs/google_play_services/README.txt b/platform/android/libs/google_play_services/README.txt deleted file mode 100644 index 32f8d5eb85..0000000000 --- a/platform/android/libs/google_play_services/README.txt +++ /dev/null @@ -1,17 +0,0 @@ -Library Project including Google Play services client jar. - -This can be used by an Android project to use the API's provided -by Google Play services. - -There is technically no source, but the src folder is necessary -to ensure that the build system works. The content is actually -located in the libs/ directory. - - -USAGE: - -Make sure you import this Android library project into your IDE -and set this project as a dependency. - -Note that if you use proguard, you will want to include the -options from proguard.txt in your configuration. \ No newline at end of file diff --git a/platform/android/libs/google_play_services/build.xml b/platform/android/libs/google_play_services/build.xml deleted file mode 100644 index 22ccf3aaf4..0000000000 --- a/platform/android/libs/google_play_services/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/libs/google_play_services/libs/google-play-services.jar b/platform/android/libs/google_play_services/libs/google-play-services.jar deleted file mode 100644 index 67d56476b6..0000000000 Binary files a/platform/android/libs/google_play_services/libs/google-play-services.jar and /dev/null differ diff --git a/platform/android/libs/google_play_services/libs/google-play-services.jar.properties b/platform/android/libs/google_play_services/libs/google-play-services.jar.properties deleted file mode 100644 index 429687b792..0000000000 --- a/platform/android/libs/google_play_services/libs/google-play-services.jar.properties +++ /dev/null @@ -1 +0,0 @@ -doc=../../../docs/reference diff --git a/platform/android/libs/google_play_services/proguard-project.txt b/platform/android/libs/google_play_services/proguard-project.txt deleted file mode 100644 index f2fe1559a2..0000000000 --- a/platform/android/libs/google_play_services/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/platform/android/libs/google_play_services/proguard.txt b/platform/android/libs/google_play_services/proguard.txt deleted file mode 100644 index 0c9693a2c0..0000000000 --- a/platform/android/libs/google_play_services/proguard.txt +++ /dev/null @@ -1,20 +0,0 @@ --keep class * extends java.util.ListResourceBundle { - protected Object[][] getContents(); -} - -# Keep SafeParcelable value, needed for reflection. This is required to support backwards -# compatibility of some classes. --keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable { - public static final *** NULL; -} - -# Keep the names of classes/members we need for client functionality. --keepnames @com.google.android.gms.common.annotation.KeepName class * --keepclassmembernames class * { - @com.google.android.gms.common.annotation.KeepName *; -} - -# Needed for Parcelable/SafeParcelable Creators to not get stripped --keepnames class * implements android.os.Parcelable { - public static final ** CREATOR; -} \ No newline at end of file diff --git a/platform/android/libs/google_play_services/project.properties b/platform/android/libs/google_play_services/project.properties deleted file mode 100644 index 36f15941e2..0000000000 --- a/platform/android/libs/google_play_services/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-15 -android.library=true diff --git a/platform/android/libs/google_play_services/res/color/common_signin_btn_text_dark.xml b/platform/android/libs/google_play_services/res/color/common_signin_btn_text_dark.xml deleted file mode 100644 index a615ba2747..0000000000 --- a/platform/android/libs/google_play_services/res/color/common_signin_btn_text_dark.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/color/common_signin_btn_text_light.xml b/platform/android/libs/google_play_services/res/color/common_signin_btn_text_light.xml deleted file mode 100644 index 662066899b..0000000000 --- a/platform/android/libs/google_play_services/res/color/common_signin_btn_text_light.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_dark.9.png deleted file mode 100644 index 0f9e7917e0..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_dark.9.png deleted file mode 100644 index 570e432252..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_light.9.png deleted file mode 100644 index 570e432252..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_light.9.png deleted file mode 100644 index 0f9e7917e0..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_dark.9.png deleted file mode 100644 index f507b9f7da..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_light.9.png deleted file mode 100644 index d5625e5fc1..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_dark.9.png deleted file mode 100644 index aea3c0d168..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_light.9.png deleted file mode 100644 index 849e89f3aa..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_dark.9.png deleted file mode 100644 index f4ab2f2a51..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_light.9.png deleted file mode 100644 index 9fe611d684..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_icon_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_dark.9.png deleted file mode 100644 index bbcde39cf0..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_dark.9.png deleted file mode 100644 index 53957b698f..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_light.9.png deleted file mode 100644 index 53957b698f..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_light.9.png deleted file mode 100644 index bbcde39cf0..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_dark.9.png deleted file mode 100644 index 000d12e8e3..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_light.9.png deleted file mode 100644 index d9279405c6..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_dark.9.png deleted file mode 100644 index 67f263c80e..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_light.9.png deleted file mode 100644 index 96324c52f9..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_dark.9.png deleted file mode 100644 index e4503128f6..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_light.9.png deleted file mode 100644 index fb94b77616..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/common_signin_btn_text_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_medium_off_client.png b/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_medium_off_client.png deleted file mode 100644 index 894f1b9f93..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_medium_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_small_off_client.png b/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_small_off_client.png deleted file mode 100644 index ac777614e6..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_small_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_standard_off_client.png b/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_standard_off_client.png deleted file mode 100644 index f1c32d3b9e..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_standard_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_tall_off_client.png b/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_tall_off_client.png deleted file mode 100644 index 08a4670c47..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-hdpi/ic_plusone_tall_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_dark.9.png deleted file mode 100644 index dddcbebf12..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_dark.9.png deleted file mode 100644 index 58b75bd7de..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_light.9.png deleted file mode 100644 index 58b75bd7de..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_light.9.png deleted file mode 100644 index dddcbebf12..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_dark.9.png deleted file mode 100644 index 7d9ed7834d..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_light.9.png deleted file mode 100644 index 0ca401d376..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_dark.9.png deleted file mode 100644 index f2c3f55717..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_light.9.png deleted file mode 100644 index 83b4fc9d6d..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_dark.9.png deleted file mode 100644 index dd74fe8761..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_light.9.png deleted file mode 100644 index b7dc7aac7e..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_icon_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_dark.9.png deleted file mode 100644 index efdfe2e616..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_dark.9.png deleted file mode 100644 index c7650b09e3..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_light.9.png deleted file mode 100644 index c7650b09e3..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_light.9.png deleted file mode 100644 index efdfe2e616..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_dark.9.png deleted file mode 100644 index 8c76283e50..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_light.9.png deleted file mode 100644 index abd26bcd41..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_dark.9.png deleted file mode 100644 index 28181c338b..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_light.9.png deleted file mode 100644 index 34957fad5f..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_dark.9.png deleted file mode 100644 index e923ee9c75..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_light.9.png deleted file mode 100644 index 34cf6bbad5..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/common_signin_btn_text_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_medium_off_client.png b/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_medium_off_client.png deleted file mode 100644 index d7e5777153..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_medium_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_small_off_client.png b/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_small_off_client.png deleted file mode 100644 index af301c2dc9..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_small_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_standard_off_client.png b/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_standard_off_client.png deleted file mode 100644 index f43e965fb8..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_standard_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_tall_off_client.png b/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_tall_off_client.png deleted file mode 100644 index 0b2b5c9a98..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-mdpi/ic_plusone_tall_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_dark.9.png deleted file mode 100644 index 9044a118af..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_dark.9.png deleted file mode 100644 index e94a49b0ae..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_light.9.png deleted file mode 100644 index e94a49b0ae..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_light.9.png deleted file mode 100644 index 9044a118af..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_dark.9.png deleted file mode 100644 index bfe4f04639..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_light.9.png deleted file mode 100644 index 876884fad7..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_dark.9.png deleted file mode 100644 index b3e6dd5b40..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_light.9.png deleted file mode 100644 index 5a888f28f5..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_dark.9.png deleted file mode 100644 index d0f7b4cbf3..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_light.9.png deleted file mode 100644 index 0db6b06450..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_icon_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_dark.9.png deleted file mode 100644 index d182b5e2c3..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_dark.9.png deleted file mode 100644 index 47e2aeaf32..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_light.9.png deleted file mode 100644 index 47e2aeaf32..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_light.9.png deleted file mode 100644 index d182b5e2c3..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_dark.9.png deleted file mode 100644 index 64e9706874..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_light.9.png deleted file mode 100644 index 0fd8cdda14..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_dark.9.png deleted file mode 100644 index 3427b47681..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_light.9.png deleted file mode 100644 index 31e38c4c12..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_dark.9.png deleted file mode 100644 index e6a7880730..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_light.9.png deleted file mode 100644 index 972962dcfd..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/common_signin_btn_text_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_medium_off_client.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_medium_off_client.png deleted file mode 100644 index bb933092be..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_medium_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_small_off_client.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_small_off_client.png deleted file mode 100644 index 6174fcd9b1..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_small_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_standard_off_client.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_standard_off_client.png deleted file mode 100644 index 6a4c298e2d..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_standard_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_tall_off_client.png b/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_tall_off_client.png deleted file mode 100644 index f68e9133bb..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xhdpi/ic_plusone_tall_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_dark.9.png deleted file mode 100644 index c97f349fae..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_dark.9.png deleted file mode 100644 index 34cbff115c..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_light.9.png deleted file mode 100644 index 34cbff115c..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_light.9.png deleted file mode 100644 index c97f349fae..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_dark.9.png deleted file mode 100644 index 702c49b74c..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_light.9.png deleted file mode 100644 index 06ad5a5ae7..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_dark.9.png deleted file mode 100644 index af160fc73b..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_light.9.png deleted file mode 100644 index c647fb4ce8..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_dark.9.png deleted file mode 100644 index fd0a4312b4..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_light.9.png deleted file mode 100644 index f8ce5a6aca..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_icon_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_dark.9.png deleted file mode 100644 index b491f629fd..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_dark.9.png deleted file mode 100644 index 777c8d6408..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_light.9.png deleted file mode 100644 index 777c8d6408..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_light.9.png deleted file mode 100644 index b491f629fd..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_disabled_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_dark.9.png deleted file mode 100644 index c8a8f1cbdf..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_light.9.png deleted file mode 100644 index bcd0d0caf4..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_focus_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_dark.9.png deleted file mode 100644 index ac75dad52e..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_light.9.png deleted file mode 100644 index c19afad669..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_normal_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_dark.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_dark.9.png deleted file mode 100644 index c49044185a..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_dark.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_light.9.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_light.9.png deleted file mode 100644 index c52be7455e..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/common_signin_btn_text_pressed_light.9.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_medium_off_client.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_medium_off_client.png deleted file mode 100644 index 4f23739dc3..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_medium_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_small_off_client.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_small_off_client.png deleted file mode 100644 index 8ffa1d72e6..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_small_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_standard_off_client.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_standard_off_client.png deleted file mode 100644 index 4d81cf40cd..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_standard_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_tall_off_client.png b/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_tall_off_client.png deleted file mode 100644 index fab5a79b45..0000000000 Binary files a/platform/android/libs/google_play_services/res/drawable-xxhdpi/ic_plusone_tall_off_client.png and /dev/null differ diff --git a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_dark.xml b/platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_dark.xml deleted file mode 100644 index dd1cf679fe..0000000000 --- a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_dark.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_light.xml b/platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_light.xml deleted file mode 100644 index abf412bda8..0000000000 --- a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_icon_light.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_dark.xml b/platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_dark.xml deleted file mode 100644 index 2d92217cdf..0000000000 --- a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_dark.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_light.xml b/platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_light.xml deleted file mode 100644 index 810c02112d..0000000000 --- a/platform/android/libs/google_play_services/res/drawable/common_signin_btn_text_light.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/values-af/strings.xml b/platform/android/libs/google_play_services/res/values-af/strings.xml deleted file mode 100644 index 1b211f5076..0000000000 --- a/platform/android/libs/google_play_services/res/values-af/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Kry Google Play-dienste" - "Hierdie program sal nie loop sonder Google Play-dienste nie, wat nie op jou foon is nie." - "Hierdie program sal nie loop sonder Google Play-dienste nie, wat nie op jou tablet is nie." - "Kry Google Play-dienste" - "Aktiveer Google Play-dienste" - "Hierdie program sal nie werk tensy jy Google Play-dienste aktiveer nie." - "Aktiveer Google Play-dienste" - "Dateer Google Play-dienste op" - "Hierdie program sal nie loop nie, tensy jy Google Play-dienste opdateer." - "Netwerkfout" - "\'n Dataverbinding is nodig om aan Google Play-dienste te koppel." - "Ongeldige rekening" - "Die gespesifiseerde rekening bestaan nie op hierdie toestel nie. Kies asseblief \'n ander rekening." - "Onbekende probleem met Google Play-dienste." - "Google Play-dienste" - "Google Play-dienste, waarop sommige van jou programme staatmaak, werk nie met jou toestel nie. Kontak asseblief die vervaardiger vir bystand." - "Dit lyk of die datum op die toestel verkeerd is. Gaan asseblief die datum op die toestel na." - "Dateer op" - "Meld aan" - "Meld aan met Google" - - "\'n Program het probeer om \'n slegte weergawe van Google Play-dienste te gebruik." - "\'n Program vereis dat Google Play-dienste geaktiveer word." - "\'n Program vereis dat Google Play-dienste geïnstalleer word." - "\'n Program vereis \'n opdatering vir Google Play-dienste." - "Google Play-dienstefout" - "Versoek deur %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-am/strings.xml b/platform/android/libs/google_play_services/res/values-am/strings.xml deleted file mode 100644 index 2585210fe0..0000000000 --- a/platform/android/libs/google_play_services/res/values-am/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play አገልግሎቶችን አግኝ" - "ይህ መተግበሪያ ያለ Google Play አገልግሎቶች አይሰራም፣ እነሱ ደግሞ ስልክዎ ላይ የሉም።" - "ይህ መተግበሪያ ያለ Google Play አገልግሎቶች አይሰራም፣ እነሱ ደግሞ ጡባዊዎ ላይ የሉም።" - "Google Play አገልግሎቶችን አግኝ" - "Google Play አገልግሎቶችን አንቃ" - "Google Play አገልግሎቶችን እስካላነቁ ድረስ ይህ መተግበሪያ አይሰራም።" - "Google Play አገልግሎቶችን አንቃ" - "Google Play አገልግሎቶችን ያዘምኑ" - "Google Play አገልግሎቶችን እስኪያዘምኑ ድረስ ይህ መተግበሪያ አይሰራም።" - "የአውታረ መረብ ስህተት" - "ከGoogle Play አገልግሎቶች ጋር ለመገናኘት የውሂብ ግንኙነት ያስፈልጋል።" - "ልክ ያልሆነ መለያ" - "የተገለጸው መለያ በዚህ መሣሪያ ላይ የለም። እባክው የተለየ መለያ ይምረጡ።" - "በGoogle Play አገልግሎቶች ላይ ያልታወቀ ችግር።" - "Google Play አገልግሎቶች" - "የGoogle Play አገልግሎቶች፣ አንዳንድ መተግበሪያዎችዎ በእሱ ላይ ጥገኛ የሆኑት፣ በመሣሪያዎ አይደገፍም። እባክዎ ለእርዳታ አምራቹን ያግኙ።" - "በመሣሪያው ላይ ያለው ቀን ትክክል አይመስልም። እባክዎ በመሣሪያው ላይ ያለውን ቀን ያረጋግጡ።" - "ያዘምኑ" - "ግባ" - "በGoogle ይግቡ" - - "መተግበሪያው የGoogle Play አገልግሎቶችን መጥፎ ስሪት ለመጠቀም ሞክሯል።" - "መተግበሪያው Google Play አገልግሎቶች እንዲነቁ ይፈልጋል።" - "መተግበሪያው Google Play አገልግሎቶች እንዲጫኑ ይፈልጋል።" - "መተግበሪያው Google Play አገልግሎቶች እንዲዘምን ይፈልጋል።" - "የGoogle Play አገልግሎቶች ስህተት" - "በ%1$s የተጠየቀ" - diff --git a/platform/android/libs/google_play_services/res/values-ar/strings.xml b/platform/android/libs/google_play_services/res/values-ar/strings.xml deleted file mode 100644 index 9451b37181..0000000000 --- a/platform/android/libs/google_play_services/res/values-ar/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "‏الحصول على خدمات Google Play" - "‏لن يتم تشغيل هذا التطبيق بدون خدمات Google Play، والتي لا تتوفر في هاتفك." - "‏لن يتم تشغيل هذا التطبيق بدون خدمات Google Play، والتي لا تتوفر في جهازك اللوحي." - "‏الحصول على خدمات Google Play" - "‏تمكين خدمات Google Play" - "‏لن يعمل هذا التطبيق ما لم يتم تمكين خدمات Google Play." - "‏تمكين خدمات Google Play" - "‏تحديث خدمات Google Play" - "‏لن يتم تشغيل هذا التطبيق ما لم تحدِّث خدمات Google Play." - "خطأ في الشبكة" - "‏يتطلب الاتصال بخدمات Google Play وجود اتصال بيانات." - "حساب غير صالح" - "الحساب الذي تمّ تحديده غير موجود على الجهاز. يُرجى اختيار حساب آخر." - "‏حدثت مشكلة غير معروفة في خدمات Google Play." - "‏خدمات Google Play" - "‏خدمات Google Play التي تستجيب لها بعض تطبيقاتك لا تعمل على جهازك. يُرجى الاتصال بجهة التصنيع للحصول على المساعدة." - "يبدو أن التاريخ على الجهاز غير صحيح. الرجاء التحقق من التاريخ على الجهاز." - "تحديث" - "تسجيل الدخول" - "‏تسجيل الدخول باستخدام Google" - - "‏يحاول أحد التطبيقات استخدام إصدار غير صالح من خدمات Google Play." - "‏يتطلب أحد التطبيقات تمكين خدمات Google Play." - "‏يتطلب أحد التطبيقات تثبيت خدمات Google Play." - "‏يتطلب أحد التطبيقات تحديث خدمات Google Play." - "‏خطأ في خدمات Google Play" - "تم الطلب عن طريق %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-be/strings.xml b/platform/android/libs/google_play_services/res/values-be/strings.xml deleted file mode 100644 index 81382d1c0f..0000000000 --- a/platform/android/libs/google_play_services/res/values-be/strings.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - "Атрымаць службы Google Play" - "Гэта прыкладанне не будзе працаваць без службаў Google Play, якіх няма ў вашым тэлефоне." - "Гэта прыкладанне не будзе працаваць без службаў Google Play, якіх няма на вашым планшэце." - "Атрымаць службы Google Play" - "Уключыць службы Google Play" - "Гэта прыкладанне не будзе працаваць, пакуль вы не ўключыце службы Google Play." - "Уключыць службы Google Play" - "Абнаўленне службаў Google Play" - "Гэта прыкладанне не будзе працаваць падчас абнаўлення службаў Google Play." - - - - - - - - - "Невядомая праблема са службамі Google Play." - "Службы Google Play" - "Службы Google Play, да якiх прывязаны некаторыя прыкладаннi, не падтрымлiваюцца на вашай прыладзе. Па дапамогу звярнiцеся да вытворцы." - - - "Абнавіць" - "Увайсцi" - "Увайсці ў Google" - - "Прыкладанне паспрабавала скарыстацца сапсаванай версіяй службаў Google Play." - "Прыкладанне патрабуе ўключэння службаў Google Play." - "Прыкладанне патрабуе ўсталявання службаў Google Play." - "Прыкладанне патрабуе абнаўлення службаў Google Play." - "Памылка службаў Google Play" - "Запытана прыкладаннем %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-bg/strings.xml b/platform/android/libs/google_play_services/res/values-bg/strings.xml deleted file mode 100644 index bb8da3c105..0000000000 --- a/platform/android/libs/google_play_services/res/values-bg/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Изтегляне на услугите за Google Play" - "Това приложение няма да се изпълнява без услугите за Google Play, които липсват в телефона ви." - "Това приложение няма да се изпълнява без услугите за Google Play, които липсват в таблета ви." - "Услуги за Google Play: Изтегл." - "Активиране на услугите за Google Play" - "Това приложение няма да работи, освен ако не активирате услугите за Google Play." - "Услуги за Google Play: Актив." - "Актуализиране на услугите за Google Play" - "Това приложение няма да се изпълнява, освен ако не актуализирате услугите за Google Play." - "Грешка в мрежата" - "За свързване с услугите за Google Play се изисква връзка за данни." - "Невалиден профил" - "Посоченият профил не съществува на това устройство. Моля, изберете друг." - "Неизвестен проблем с услугите за Google Play." - "Услуги за Google Play" - "Услугите за Google Play, на които разчитат някои от приложенията ви, не се поддържат от устройството ви. Моля, свържете се с производителя за помощ." - "Изглежда, че датата на устройството е неправилна. Моля, проверете я." - "Актуализиране" - "Вход" - "Вход с Google" - - "Приложение опита да ползва неправилна версия на услуг. за Google Play." - "Приложение изисква активирането на услугите за Google Play." - "Приложение изисква инсталирането на услугите за Google Play." - "Приложение изисква актуализирането на услугите за Google Play." - "Грешка в услугите за Google Play" - "Заявено от %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-ca/strings.xml b/platform/android/libs/google_play_services/res/values-ca/strings.xml deleted file mode 100644 index 5b63e86af7..0000000000 --- a/platform/android/libs/google_play_services/res/values-ca/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Baixa els serveis de Google Play" - "Aquesta aplicació no s\'executarà si el telèfon no té instal·lats els serveis de Google Play." - "Aquesta aplicació no funcionarà si la tauleta no té instal·lats els serveis de Google Play." - "Baixa els serveis de Google Play" - "Activa els serveis de Google Play" - "Aquesta aplicació no funcionarà si no actives els serveis de Google Play." - "Activa els serveis de Google Play" - "Actualitza els serveis de Google Play" - "Aquesta aplicació no s\'executarà si no actualitzes els serveis de Google Play." - "Error de xarxa" - "Es requereix una connexió de dades per connectar amb els serveis de Google Play." - "Compte no vàlid" - "El compte especificat no existeix en aquest dispositiu. Tria un compte diferent." - "Error desconegut relacionat amb els serveis de Google Play." - "Serveis de Google Play" - "El teu dispositiu no és compatible amb els serveis de Google Play, en què es basen les teves aplicacions. Per obtenir assistència, contacta amb el fabricant." - "Sembla que la data del dispositiu no és correcta. Comprova-la." - "Actualitza" - "Inicia sessió" - "Inicia sessió amb Google" - - "Una aplic. ha intentat utilitzar una versió errònia de serveis de Play." - "Una aplicació requereix que s\'activin els serveis de Google Play." - "Una aplicació requereix que s\'instal·lin els serveis de Google Play." - "Una aplicació requereix que s\'actualitzin els serveis de Google Play." - "Error dels serveis de Google Play" - "Sol·licitada per %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-cs/strings.xml b/platform/android/libs/google_play_services/res/values-cs/strings.xml deleted file mode 100644 index 1b5423b039..0000000000 --- a/platform/android/libs/google_play_services/res/values-cs/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Instalovat služby Google Play" - "Ke spuštění této aplikace jsou potřeba služby Google Play, které v telefonu nemáte." - "Ke spuštění této aplikace jsou potřeba služby Google Play, které v tabletu nemáte." - "Instalovat služby Google Play" - "Aktivovat služby Google Play" - "Ke spuštění této aplikace je třeba aktivovat služby Google Play." - "Aktivovat služby Google Play" - "Aktualizace služeb Google Play" - "Ke spuštění této aplikace je třeba aktualizovat služby Google Play." - "Chyba sítě" - "Připojení ke službám Google Play vyžaduje datové připojení." - "Neplatný účet" - "Zadaný účet v tomto zařízení neexistuje. Zvolte prosím jiný účet." - "Nastal neznámý problém se službami Google Play." - "Služby Google Play" - "Některé vaše aplikace vyžadují služby Google Play, které ve vašem zařízení nejsou podporovány. S žádostí o pomoc se prosím obraťte na výrobce." - "Datum v zařízení není správně nastaveno. Zkontrolujte prosím datum." - "Aktualizovat" - "Přihlásit se" - "Přihlásit se účtem Google" - - "Aplikace se pokusila použít nesprávnou verzi Služeb Google Play." - "Aplikace vyžaduje aktivované Služby Google Play." - "Aplikace vyžaduje instalaci Služeb Google Play." - "Aplikace vyžaduje aktualizaci Služeb Google Play." - "Chyba služeb Google Play" - "Požadováno aplikací %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-da/strings.xml b/platform/android/libs/google_play_services/res/values-da/strings.xml deleted file mode 100644 index daa2160d3b..0000000000 --- a/platform/android/libs/google_play_services/res/values-da/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Hent Google Play-tjenester" - "Denne app kan ikke køre uden Google Play-tjenester, som mangler på din telefon." - "Denne app kan ikke køre uden Google Play-tjenester, som mangler på din tablet." - "Hent Google Play-tjenester" - "Aktivér Google Play-tjenester" - "Denne app virker ikke, medmindre du aktiverer Google Play-tjenester." - "Aktivér Google Play-tjenester" - "Opdater Google Play-tjenester" - "Denne app kan ikke køre, medmindre du opdaterer Google Play-tjenester." - "Netværksfejl" - "Der kræves en dataforbindelse for at oprette forbindelse til Google Play-tjenester." - "Ugyldig konto" - "Den angivne konto findes ikke på denne enhed. Vælg en anden konto." - "Ukendt problem med Google Play-tjenester." - "Google Play-tjenester" - "Google Play-tjenester, som nogle af dine applikationer er afhængige af, understøttes ikke af din enhed. Kontakt producenten for at få hjælp." - "Datoen på enheden ser ud til at være forkert. Husk at kontrollere datoen på enheden." - "Opdater" - "Log ind" - "Log ind med Google" - - "En applikation forsøgte at bruge en defekt version af Google Play." - "En applikation kræver, at Google Play er aktiveret." - "En applikation kræver, at Google Play er installeret." - "En applikation kræver en opdatering af Google Play." - "Fejl i Google Play-tjenester" - "Anmodning fra %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-de/strings.xml b/platform/android/libs/google_play_services/res/values-de/strings.xml deleted file mode 100644 index df8e88e9e2..0000000000 --- a/platform/android/libs/google_play_services/res/values-de/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play-Dienste installieren" - "Zur Nutzung dieser App sind Google Play-Dienste erforderlich, die auf Ihrem Telefon nicht installiert sind." - "Zur Nutzung dieser App sind Google Play-Dienste erforderlich, die auf Ihrem Tablet nicht installiert sind." - "Google Play-Dienste installieren" - "Google Play-Dienste aktivieren" - "Diese App funktioniert nur, wenn Sie die Google Play-Dienste aktivieren." - "Google Play-Dienste aktivieren" - "Google Play-Dienste aktualisieren" - "Diese App wird nur ausgeführt, wenn Sie die Google Play-Dienste aktualisieren." - "Netzwerkfehler" - "Um eine Verbindung zu den Google Play-Diensten herzustellen, ist eine Datenverbindung erforderlich." - "Ungültiges Konto" - "Das angegebene Konto ist auf diesem Gerät nicht vorhanden. Bitte wählen Sie ein anderes Konto aus." - "Unbekanntes Problem mit Google Play-Diensten" - "Google Play-Dienste" - "Google Play-Dienste, auf denen einige Ihrer Apps basieren, werden von diesem Gerät nicht unterstützt. Wenden Sie sich für weitere Informationen an den Hersteller." - "Das Datum auf dem Gerät scheint falsch zu sein. Bitte überprüfen Sie das Datum auf dem Gerät." - "Aktualisieren" - "Anmelden" - "Über Google anmelden" - - "App versuchte, defekte Google Play-Dienste-Version zu verwenden" - "App erfordert aktivierte Google Play-Dienste" - "App erfordert die Installation von Google Play-Diensten" - "App erfordert ein Update für Google Play-Dienste" - "Fehler bei Google Play-Diensten" - "Angefordert von %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-el/strings.xml b/platform/android/libs/google_play_services/res/values-el/strings.xml deleted file mode 100644 index 13a5dc5ef3..0000000000 --- a/platform/android/libs/google_play_services/res/values-el/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Λήψη υπηρεσιών Google Play" - "Αυτή η εφαρμογή δεν θα εκτελεστεί χωρίς τις υπηρεσίες Google Play, οι οποίες λείπουν από το τηλέφωνό σας." - "Αυτή η εφαρμογή δεν θα εκτελεστεί χωρίς τις υπηρεσίες Google Play, οι οποίες λείπουν από το tablet σας." - "Λήψη υπηρεσιών Google Play" - "Ενεργοποίηση υπηρεσιών Google Play" - "Αυτή η εφαρμογή δεν θα λειτουργήσει εάν δεν έχετε ενεργοποιήσει τις υπηρεσίες Google Play." - "Ενεργοπ. υπηρεσιών Google Play" - "Ενημέρωση υπηρεσιών Google Play" - "Αυτή η εφαρμογή θα εκτελεστεί αφού ενημερώσετε τις υπηρεσίες Google Play." - "Σφάλμα δικτύου" - "Απαιτείται σύνδεση δεδομένων για να συνδεθείτε με τις Υπηρεσίες Google Play." - "Μη έγκυρος λογαριασμός" - "Ο συγκεκριμένος λογαριασμός δεν υπάρχει σε αυτήν τη συσκευή. Επιλέξτε έναν διαφορετικό λογαριασμό." - "Άγνωστο πρόβλημα με τις υπηρεσίες Google Play." - "Υπηρεσίες Google Play" - "Οι υπηρεσίες Google Play, στις οποίες βασίζονται ορισμένες από τις εφαρμογές σας, δεν υποστηρίζονται στη συσκευή σας. Επικοινωνήστε με τον κατασκευαστή για υποστήριξη." - "Η ημερομηνία στη συσκευή φαίνεται λανθασμένη. Ελέγξτε την ημερομηνία στη συσκευή." - "Ενημέρωση" - "Σύνδεση" - "Συνδεθείτε στο Google" - - "Απόπειρα χρήσης ακατάλληλης έκδοσης Υπηρεσιών Google Play από εφαρμογή" - "Μια εφαρμογή απαιτεί τις Υπηρεσίες Google Play για ενεργοποίηση." - "Μια εφαρμογή απαιτεί την εγκατάσταση των Υπηρεσιών Google Play." - "Μια εφαρμογή απαιτεί μια ενημέρωση για τις Υπηρεσίες Google Play." - "Σφάλμα υπηρεσιών Google Play" - "Υποβλήθηκε αίτημα από την εφαρμογή %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-en-rGB/strings.xml b/platform/android/libs/google_play_services/res/values-en-rGB/strings.xml deleted file mode 100644 index 106d390b3e..0000000000 --- a/platform/android/libs/google_play_services/res/values-en-rGB/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Get Google Play services" - "This app won\'t run without Google Play services, which are missing from your phone." - "This app won\'t run without Google Play services, which are missing from your tablet." - "Get Google Play services" - "Enable Google Play services" - "This app won\'t work unless you enable Google Play services." - "Enable Google Play services" - "Update Google Play services" - "This app won\'t run unless you update Google Play services." - "Network Error" - "A data connection is required to connect to Google Play services." - "Invalid Account" - "The specified account does not exist on this device. Please choose a different account." - "Unknown issue with Google Play services." - "Google Play services" - "Google Play services, which some of your applications rely on, is not supported by your device. Please contact the manufacturer for assistance." - "The date on the device appears to be incorrect. Please check the date on the device." - "Update" - "Sign in" - "Sign in with Google" - - "An application attempted to use a bad version of Google Play Services." - "An application requires Google Play Services to be enabled." - "An application requires installation of Google Play Services." - "An application requires an update for Google Play Services." - "Google Play services error" - "Requested by %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-en-rIN/strings.xml b/platform/android/libs/google_play_services/res/values-en-rIN/strings.xml deleted file mode 100644 index 106d390b3e..0000000000 --- a/platform/android/libs/google_play_services/res/values-en-rIN/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Get Google Play services" - "This app won\'t run without Google Play services, which are missing from your phone." - "This app won\'t run without Google Play services, which are missing from your tablet." - "Get Google Play services" - "Enable Google Play services" - "This app won\'t work unless you enable Google Play services." - "Enable Google Play services" - "Update Google Play services" - "This app won\'t run unless you update Google Play services." - "Network Error" - "A data connection is required to connect to Google Play services." - "Invalid Account" - "The specified account does not exist on this device. Please choose a different account." - "Unknown issue with Google Play services." - "Google Play services" - "Google Play services, which some of your applications rely on, is not supported by your device. Please contact the manufacturer for assistance." - "The date on the device appears to be incorrect. Please check the date on the device." - "Update" - "Sign in" - "Sign in with Google" - - "An application attempted to use a bad version of Google Play Services." - "An application requires Google Play Services to be enabled." - "An application requires installation of Google Play Services." - "An application requires an update for Google Play Services." - "Google Play services error" - "Requested by %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-es-rUS/strings.xml b/platform/android/libs/google_play_services/res/values-es-rUS/strings.xml deleted file mode 100644 index 6be905908c..0000000000 --- a/platform/android/libs/google_play_services/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Obtener Google Play Services" - "Esta aplicación no se ejecutará si no instalasGoogle Play Services en tu dispositivo." - "Esta aplicación no se ejecutará si no instalas Google Play Services en tu tablet." - "Descargar Google Play Services" - "Activar Google Play Services" - "Esta aplicación no funcionará si no activas Google Play Services." - "Activar Google Play Services" - "Actualizar Google Play Services" - "Esta aplicación no se ejecutará si no actualizas Google Play Services." - "Error de red" - "Se necesita una conexión de datos para establecer conexión con Google Play Services." - "Cuenta no válida" - "La cuenta especificada no existe en este dispositivo. Elige otra cuenta." - "Error desconocido relacionado con Google Play Services" - "Google Play Services" - "Google Play Services, del cual dependen algunas de tus aplicaciones, no es compatible con tu dispositivo. Comunícate con el fabricante para obtener ayuda." - "Parece que la fecha del dispositivo es incorrecta. ¿Puedes revisarla?" - "Actualizar" - "Acceder" - "Acceder con Google" - - "Una aplic. intentó usar una versión no válida de Google Play Services" - "Una aplicación requiere que se active Google Play Services" - "Una aplicación requiere que se instale Google Play Services" - "Una aplicación requiere que se actualice Google Play Services" - "Error de Google Play Services" - "Solicitada por %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-es/strings.xml b/platform/android/libs/google_play_services/res/values-es/strings.xml deleted file mode 100644 index ed32995cf2..0000000000 --- a/platform/android/libs/google_play_services/res/values-es/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Descargar servicios de Google Play" - "Esta aplicación no se ejecutará si tu teléfono no tiene instalados los servicios de Google Play." - "Esta aplicación no se ejecutará si tu tablet no tiene instalados los servicios de Google Play." - "Descargar servicios de Google Play" - "Habilitar servicios de Google Play" - "Esta aplicación no funcionará si no habilitas los servicios de Google Play." - "Habilitar servicios de Google Play" - "Actualizar servicios de Google Play" - "Esta aplicación no se ejecutará si no actualizas los servicios de Google Play." - "Error de red" - "Se necesita una conexión de datos para establecer conexión con los servicios de Google Play." - "Cuenta no válida" - "La cuenta especificada no existe en este dispositivo. Selecciona otra cuenta." - "Error desconocido relacionado con los servicios de Google Play" - "Servicios de Google Play" - "Tu dispositivo no es compatible con los servicios de Google Play, de los cuales dependen tus aplicaciones. Para obtener asistencia, ponte en contacto el fabricante." - "Parece que la fecha del dispositivo es incorrecta. Compruébala." - "Actualizar" - "Iniciar sesión" - "Iniciar sesión con Google" - - "Una aplicación intentó usar versión incorrecta de servicios de Google Play." - "Una aplicación requiere que se habiliten los servicios de Play." - "Una aplicación requiere que se instalen los servicios de Google Play." - "Una aplicación requiere que se actualicen los servicios de Google Play." - "Error de los servicios de Google Play" - "Solicitada por %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-et-rEE/strings.xml b/platform/android/libs/google_play_services/res/values-et-rEE/strings.xml deleted file mode 100644 index 281caff497..0000000000 --- a/platform/android/libs/google_play_services/res/values-et-rEE/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Hankige Google Play teenused" - "Selle rakenduse käitamiseks on vaja Google Play teenuseid, mida teie telefonis pole." - "Selle rakenduse käitamiseks on vaja Google Play teenuseid, mida teie tahvelarvutis pole." - "Hankige Google Play teenused" - "Lubage Google Play teenused" - "See rakendus ei tööta, kui te ei luba Google Play teenuseid." - "Lubage Google Play teenused" - "Värskendage Google Play teenuseid" - "Seda rakendust ei saa käitada, kui te ei värskenda Google Play teenuseid." - "Võrgu viga" - "Google Play teenustega ühenduse loomiseks on vajalik andmesideühendus." - "Vale konto" - "Määratud kontot pole selles seadmes olemas. Valige muu konto." - "Google Play teenuste tundmatu probleem." - "Google Play teenused" - "Teie seade ei toeta Google Play teenuseid, millele mõni teie rakendustest toetub. Abi saamiseks võtke ühendust tootjaga." - "Seadme kuupäev paistab olevat vale. Kontrollige seadme kuupäeva." - "Värskenda" - "Logi sisse" - "Logi sisse Google\'iga" - - "Rakendus püüdis kasutada Google Play teenuste sobimatut versiooni." - "Rakenduse kasutamiseks peavad olema lubatud Google Play teenused." - "Rakenduse kasutamiseks peavad olema installitud Google Play teenused." - "Rakenduse kasutamiseks tuleb värskendada Google Play teenuseid." - "Viga Google Play teenustes" - "Päringu esitas: %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-fa/strings.xml b/platform/android/libs/google_play_services/res/values-fa/strings.xml deleted file mode 100644 index 87e10d189c..0000000000 --- a/platform/android/libs/google_play_services/res/values-fa/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "‏دریافت خدمات Google Play" - "‏این برنامه بدون خدمات Google Play اجرا نمی‌شود، این خدمات در تلفن شما وجود ندارد." - "‏این برنامه بدون خدمات Google Play اجرا نمی‌شود، این خدمات در رایانهٔ لوحی شما وجود ندارد." - "‏دریافت خدمات Google Play" - "‏فعال کردن خدمات Google Play" - "‏تا زمانی‌که خدمات Google Play را فعال نکنید این برنامه کار نمی‌کند." - "‏فعال کردن خدمات Google Play" - "‏به‌روزرسانی خدمات Google Play" - "‏تا زمانی‌که خدمات Google Play را به‌روز نکنید این برنامه کار نمی‌کند." - "خطای شبکه" - "‏برای اتصال به خدمات Google Play اتصال داده لازم است." - "حساب نامعتبر" - "حسابی که تعیین کردید در این دستگاه وجود ندارد. لطفاً حساب دیگری را انتخاب کنید." - "‏مشکل نامشخص در خدمات Google Play." - "‏خدمات Google Play" - "‏خدمات Google Play، که برخی از برنامه‌های شما به آن وابسته است، توسط دستگاه شما پشتیبانی نمی‌شود. لطفاً برای دریافت کمک با سازنده تماس بگیرید." - "تاریخ روی دستگاه ظاهراً اشتباه است. لطفاً تاریخ روی دستگاه را بررسی کنید." - "به‌روزرسانی" - "ورود به سیستم" - "‏ورود به سیستم با Google‎" - - "‏برنامه‌ای تلاش کرد از نسخه نادرستی از خدمات Google Play استفاده کند." - "‏برنامه‌ای به فعال کردن خدمات Google Play نیاز دارد." - "‏برنامه‌ای به نصب خدمات Google Play نیاز دارد." - "‏برنامه‌ای به به‌روزرسانی خدمات Google Play نیاز دارد." - "‏خطا در خدمات Google Play" - "درخواست توسط %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-fi/strings.xml b/platform/android/libs/google_play_services/res/values-fi/strings.xml deleted file mode 100644 index 00d3ceb215..0000000000 --- a/platform/android/libs/google_play_services/res/values-fi/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Asenna Google Play -palvelut" - "Tämä sovellus ei toimi ilman Google Play -palveluita, jotka puuttuvat puhelimesta." - "Tämä sovellus ei toimi ilman Google Play -palveluita, jotka puuttuvat tablet-laitteesta." - "Asenna Google Play -palvelut" - "Ota Google Play -palvelut käyttöön" - "Tämä sovellus ei toimi, ellet ota Google Play -palveluita käyttöön." - "Ota Google Play -palv. käyttöön" - "Päivitä Google Play -palvelut" - "Tämä sovellus ei toimi, ellet päivitä Google Play -palveluita." - "Verkkovirhe" - "Google Play -palveluiden käyttöön tarvitaan tietoliikenneyhteys." - "Tili ei kelpaa" - "Kyseistä tiliä ei ole tällä laitteella. Valitse toinen tili." - "Tuntematon ongelma käytettäessä Google Play -palveluita." - "Google Play -palvelut" - "Google Play -palveluita, joita osa sovelluksistasi käyttää, ei tueta laitteellasi. Pyydä ohjeita laitteen valmistajalta." - "Laitteen päivämäärä vaikuttaa virheelliseltä. Tarkista laitteen päivämäärä." - "Päivitä" - "Kirjaudu" - "Kirjaudu Google-tiliin" - - "Sovellus yritti käyttää virheellistä Google Play -palveluiden versiota" - "Ota käyttöön Google Play -palvelut, jotta sovellus toimii." - "Asenna Google Play -palvelut, jotta sovellus toimii." - "Päivitä Google Play -palvelut, jotta sovellus toimii." - "Virhe Google Play -palveluissa" - "Pyynnön teki %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-fr-rCA/strings.xml b/platform/android/libs/google_play_services/res/values-fr-rCA/strings.xml deleted file mode 100644 index e915fe4067..0000000000 --- a/platform/android/libs/google_play_services/res/values-fr-rCA/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Installer les services Google Play" - "Cette application ne fonctionnera pas sans les services Google Play, qui ne sont pas installés sur votre téléphone." - "Cette application ne fonctionnera pas sans les services Google Play, qui ne sont pas installés sur votre tablette." - "Installer les services Google Play" - "Activer les services Google Play" - "Cette application ne fonctionnera pas tant que vous n\'aurez pas activé les services Google Play." - "Activer les services Google Play" - "Mettre à jour les services Google Play" - "Cette application ne fonctionnera pas tant que vous n\'aurez pas mis à jour les services Google Play." - "Erreur réseau" - "Vous devez disposer d\'une connexion de données pour utiliser les services Google Play." - "Compte erroné" - "Le compte indiqué n\'existe pas sur cet appareil. Veuillez sélectionner un autre compte." - "Problème inconnu avec les services Google Play." - "Services Google Play" - "Les services Google Play, dont dépendent certaines de vos applications, ne sont pas compatibles avec votre appareil. Veuillez contacter le fabricant pour obtenir de l\'aide." - "La date sur l\'appareil semble incorrecte. Veuillez la vérifier." - "Mettre à jour" - "Connexion" - "Se connecter via Google" - - "Une application requiert une version valide des services Google Play" - "Une application requiert l\'activation des services Google Play" - "Une application requiert l\'installation des services Google Play" - "Une application requiert la mise à jour des services Google Play" - "Erreur liée aux services Google Play" - "Demandée par %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-fr/strings.xml b/platform/android/libs/google_play_services/res/values-fr/strings.xml deleted file mode 100644 index 321b28370b..0000000000 --- a/platform/android/libs/google_play_services/res/values-fr/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Installer les services Google Play" - "Cette application ne fonctionnera pas sans les services Google Play, qui ne sont pas installés sur votre téléphone." - "Cette application ne fonctionnera pas sans les services Google Play, qui ne sont pas installés sur votre tablette." - "Installer services Google Play" - "Activer les services Google Play" - "Cette application ne fonctionnera pas tant que vous n\'aurez pas activé les services Google Play." - "Activer services Google Play" - "Mettre à jour les services Google Play" - "Cette application ne fonctionnera pas tant que vous n\'aurez pas mis à jour les services Google Play." - "Erreur réseau" - "Vous devez disposer d\'une connexion de données pour utiliser les services Google Play." - "Compte erroné" - "Le compte indiqué n\'existe pas sur cet appareil. Veuillez sélectionner un autre compte." - "Problème inconnu avec les services Google Play." - "Services Google Play" - "Les services Google Play, dont dépendent certaines de vos applications, ne sont pas compatibles avec votre appareil. Veuillez contacter le fabricant pour obtenir de l\'aide." - "La date sur l\'appareil semble incorrecte. Veuillez la vérifier." - "Mettre à jour" - "Connexion" - "Se connecter avec Google" - - "Une application requiert une version valide des services Google Play" - "Une application requiert l\'activation des services Google Play" - "Une application requiert l\'installation des services Google Play" - "Une application requiert la mise à jour des services Google Play" - "Erreur liée aux services Google Play" - "Demandée par %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-hi/strings.xml b/platform/android/libs/google_play_services/res/values-hi/strings.xml deleted file mode 100644 index b36feb00a7..0000000000 --- a/platform/android/libs/google_play_services/res/values-hi/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play सेवाएं पाएं" - "यह ऐप्स Google Play सेवाओं के बिना नहीं चलेगा, जो आपके फ़ोन में नहीं हैं." - "यह ऐप्स Google Play सेवाओं के बिना नहीं चलेगा, जो आपके टेबलेट में नहीं हैं." - "Google Play सेवाएं पाएं" - "Google Play सेवाएं सक्षम करें" - "जब तक आप Google Play सेवाएं सक्षम नहीं करते, तब तक यह ऐप्स कार्य नहीं करेगा." - "Google Play सेवाएं सक्षम करें" - "Google Play सेवाएं से नई जानकारी" - "जब तक आप Google Play सेवाओं से नई जानकारी नहीं लेते हैं, तब तक यह ऐप्स नहीं चलेगा." - "नेटवर्क त्रुटि" - "Google Play सेवाओं से कनेक्ट करने के लिए डेटा कनेक्शन की आवश्यकता है." - "अमान्य खाता" - "निर्दिष्ट खाता इस उपकरण पर मौजूद नहीं है. कृपया कोई भिन्न खाता चुनें." - "Google Play सेवाओं के साथ अज्ञात समस्या." - "Google Play सेवाएं" - "Google Play सेवाएं, जिन पर आपके कुछ ऐप्स निर्भर करते हैं, आपके उपकरण द्वारा समर्थित नहीं हैं. कृपया सहायता के लिए निर्माता से संपर्क करें." - "उपकरण का दिनांक गलत प्रतीत हो रहा है. कृपया उपकरण का दिनांक जांचें." - "नई जानकारी पाएं" - "प्रवेश करें" - "Google से प्रवेश करें" - - "ऐप्स ने Google Play सेवाओं के खराब संस्करण के उपयोग का प्रयास किया." - "ऐप्स के लिए Google Play सेवाओं को सक्षम किए जाने की आवश्यकता है." - "ऐप्स के लिए Google Play सेवाओं के इंस्टॉलेशन की आवश्यकता है." - "ऐप्स के लिए Google Play सेवाओं में Google Play से नई जानकारी की आवश्यकता है." - "Google Play सेवाएं त्रुटि" - "%1$s द्वारा अनुरोधित" - diff --git a/platform/android/libs/google_play_services/res/values-hr/strings.xml b/platform/android/libs/google_play_services/res/values-hr/strings.xml deleted file mode 100644 index b7d462d882..0000000000 --- a/platform/android/libs/google_play_services/res/values-hr/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Preuzmi usluge za Google Play" - "Ova aplikacija neće funkcionirati bez usluga za Google Play, koje nisu instalirane na vašem telefonu." - "Ova aplikacija neće funkcionirati bez usluga za Google Play, koje nisu instalirane na vašem tabletnom računalu." - "Preuzmi usluge za Google Play" - "Omogući usluge za Google Play" - "Ova aplikacija neće raditi ako ne omogućite usluge za Google Play." - "Omogući usluge za Google Play" - "Ažuriraj usluge za Google Play" - "Ova se aplikacija neće pokrenuti ako ne ažurirate usluge za Google Play." - "Mrežna pogreška" - "Potrebna je podatkovna veza za povezivanje s uslugama Google Play." - "Nevažeći račun" - "Navedeni račun ne postoji na ovom uređaju. Odaberite neki drugi račun." - "Nepoznata poteškoća s uslugama za Google Play." - "Usluge za Google Play" - "Usluge za Google Play, koje su potrebne za funkcioniranje nekih vaših aplikacija, nisu podržane na vašem uređaju. Pomoć potražite od proizvođača." - "Čini se da datum na uređaju nije točan. Provjerite datum na uređaju." - "Ažuriranje" - "Prijava" - "Prijava uslugom Google" - - "Aplikacija je pokušala upotrijebiti lošu verziju Usluga za Google Play." - "Aplikacija zahtijeva omogućavanje Usluga za Google Play." - "Aplikacija zahtijeva instaliranje Usluga za Google Play." - "Aplikacija zahtijeva ažuriranje Usluga za Google Play." - "Pogreška usluga za Google Play" - "Zahtijeva aplikacija %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-hu/strings.xml b/platform/android/libs/google_play_services/res/values-hu/strings.xml deleted file mode 100644 index cd15ad328f..0000000000 --- a/platform/android/libs/google_play_services/res/values-hu/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Play Szolgáltatások telepítése" - "Az alkalmazás működéséhez a Google Play Szolgáltatások szükségesek, ezek nincsenek telepítve a telefonon." - "Az alkalmazás működéséhez a Google Play Szolgáltatások szükségesek, ezek nincsenek telepítve a táblagépen." - "Play Szolgáltatások telepítése" - "Google Play Szolgáltatások aktiválása" - "Az alkalmazás csak akkor fog működni, ha engedélyezi a Google Play Szolgáltatásokat." - "Play Szolgáltatások aktiválása" - "Play Szolgáltatások frissítése" - "Az alkalmazás csak akkor fog működni, ha frissíti a Google Play Szolgáltatásokat." - "Hálózati hiba" - "A Google Play Szolgáltatásokhoz történő kapcsolódáshoz adatkapcsolat szükséges." - "Érvénytelen fiók" - "A megadott fiók nem létezik ezen az eszközön. Kérjük, válasszon másik fiókot." - "Ismeretlen hiba a Google Play Szolgáltatásokban." - "Google Play Szolgáltatások" - "A Google Play Szolgáltatásokat, amelyre egyes alkalmazások támaszkodnak, nem támogatja az eszköz. Segítségért forduljon az eszköz gyártójához." - "Az eszközön beállított dátum helytelen. Kérjük, ellenőrizze azt." - "Frissítés" - "Belépés" - "Google-bejelentkezés" - - "Egy alkalmazás a Play Szolgáltatások rossz verzióját akarta használni." - "Egy alkalmazás kéri a Google Play Szolgáltatások engedélyezését." - "Egy alkalmazás kéri a Google Play Szolgáltatások telepítését." - "Egy alkalmazás kéri a Google Play Szolgáltatások frissítését." - "Google Play szolgáltatási hiba" - "Igénylő: %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-hy-rAM/strings.xml b/platform/android/libs/google_play_services/res/values-hy-rAM/strings.xml deleted file mode 100644 index d89be9bf64..0000000000 --- a/platform/android/libs/google_play_services/res/values-hy-rAM/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Տեղադրեք Google Play ծառայությունները" - "Այս հավելվածը չի գործարկվի առանց Google Play ծառայությունների, որոնք բացակայում են ձեր հեռախոսում:" - "Այս հավելվածը չի գործարկվի առանց Google Play ծառայությունների, որոնք բացակայում են ձեր գրասալիկում:" - "Տեղադրել Google Play ծառայությունները" - "Միացնել Google Play ծառայությունները" - "Այս ծրագիրը չի աշխատի, եթե դուք չմիացնեք Google Play ծառայությունները:" - "Միացնել Google Play ծառայությունները" - "Նորացրեք Google Play ծառայությունները" - "Այս ծրագիրը չի գործարկվի, եթե դուք չնորացնեք Google Play ծառայությունները:" - "Ցանցի սխալ կա" - "Պահանջվում է տվյալների կապ` Google Play ծառայություններին միանալու համար:" - "Հաշիվն անվավեր է" - "Նշված հաշիվը գոյություն չունի այս սարքում: Ընտրեք այլ հաշիվ:" - "Անհայտ խնդիր՝ Google Play ծառայություններում:" - "Google Play ծառայություններ" - "Google Play ծառայությունները, որոնց ապավինում են ձեր ծրագրերից որոշները, չեն աջակցվում ձեր սարքի կողմից: Խնդրում ենք կապվել արտադրողի հետ օգնության համար:" - "Սարքի ամսաթիվը կարծես սխալ է: Ստուգեք սարքի ամսաթիվը:" - "Նորացնել" - "Մուտք գործել" - "Մուտք գործեք Google-ով" - - "Հավելվածը փորձել է կիրառել Google Play ծառայությունների վատ տարբերակը:" - "Հավելվածը պահանջում է միացնել Google Play ծառայությունները:" - "Հավելվածը պահանջում է տեղադրել Google Play ծառայությունները:" - "Հավելվածը պահանջում է թարմացնել Google Play ծառայությունները:" - "Google Play ծառայությունների սխալ" - "%1$s-ի հարցմամբ" - diff --git a/platform/android/libs/google_play_services/res/values-in/strings.xml b/platform/android/libs/google_play_services/res/values-in/strings.xml deleted file mode 100644 index 526b84a816..0000000000 --- a/platform/android/libs/google_play_services/res/values-in/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Dapatkan layanan Google Play" - "Aplikasi ini tidak akan berjalan tanpa layanan Google Play, yang tidak ada di ponsel Anda." - "Aplikasi ini tidak akan berjalan tanpa layanan Google Play, yang tidak ada di tablet Anda." - "Dapatkan layanan Google Play" - "Aktifkan layanan Google Play" - "Aplikasi ini tidak akan bekerja sampai Anda mengaktifkan layanan Google Play." - "Aktifkan layanan Google Play" - "Perbarui layanan Google Play" - "Aplikasi ini tidak akan berjalan sampai Anda memperbarui layanan Google Play." - "Kesalahan Jaringan" - "Sambungan data diperlukan untuk tersambung ke layanan Google Play." - "Akun Tidak Valid" - "Akun yang ditentukan tidak ada di perangkat ini. Pilih akun lain." - "Masalah tidak diketahui pada layanan Google Play." - "Layanan Google Play" - "Layanan Google Play, yang diandalkan oleh beberapa aplikasi Anda, tidak didukung oleh perangkat Anda. Hubungi pabrikan untuk mendapatkan bantuan." - "Tampaknya tanggal di perangkat salah. Periksa tanggal di perangkat." - "Perbarui" - "Masuk" - "Masuk dengan Google" - - "Aplikasi mencoba menggunakan versi Layanan Google Play yang rusak." - "Aplikasi membutuhkan Layanan Google Play untuk dapat diaktifkan." - "Aplikasi membutuhkan pemasangan Layanan Google Play." - "Aplikasi membutuhkan pembaruan untuk Layanan Google Play." - "Kesalahan layanan Google Play" - "Diminta oleh %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-it/strings.xml b/platform/android/libs/google_play_services/res/values-it/strings.xml deleted file mode 100644 index f3c9f1fa5c..0000000000 --- a/platform/android/libs/google_play_services/res/values-it/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Installa Google Play Services" - "L\'app non funzionerà senza Google Play Services, non presente sul tuo telefono." - "L\'app non funzionerà senza Google Play Services, non presente sul tuo tablet." - "Installa Google Play Services" - "Attiva Google Play Services" - "L\'app non funzionerà se non attivi Google Play Services." - "Attiva Google Play Services" - "Aggiorna Google Play Services" - "L\'app non funzionerà se non aggiorni Google Play Services." - "Errore di rete" - "È necessaria una connessione dati per connettersi a Google Play Services." - "Account non valido" - "L\'account specificato non esiste su questo dispositivo. Scegli un altro account." - "Problema sconosciuto con Google Play Services." - "Google Play Services" - "La piattaforma Google Play Services, su cui sono basate alcune delle tue applicazioni, non è supportata dal dispositivo in uso. Per assistenza, contatta il produttore." - "La data sul dispositivo sembra sbagliata. Controllala." - "Aggiorna" - "Accedi" - "Accedi con Google" - - "Un\'app ha tentato di usare una versione non valida di Play Services." - "Un\'applicazione richiede l\'attivazione di Google Play Services." - "Un\'applicazione richiede l\'installazione di Google Play Services." - "Un\'applicazione richiede un aggiornamento di Google Play Services." - "Errore Google Play Services" - "Richiesta da %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-iw/strings.xml b/platform/android/libs/google_play_services/res/values-iw/strings.xml deleted file mode 100644 index 7474e53506..0000000000 --- a/platform/android/libs/google_play_services/res/values-iw/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "‏קבל את שירותי Google Play" - "‏אפליקציה זו לא תפעל ללא שירותי Google Play, החסרים בטלפון שלך." - "‏אפליקציה זו לא תפעל ללא שירותי Google Play, החסרים בטאבלט שלך." - "‏קבל את שירותי Google Play" - "‏הפעלת שירותי Google Play" - "‏אפליקציה זו לא תעבוד אם לא תפעיל את שירותי Google Play." - "‏הפעל את שירותי Google Play" - "‏עדכון שירותי Google Play" - "‏אפליקציה זו לא תפעל אם לא תעדכן את שירותי Google Play." - "שגיאת רשת." - "‏דרוש חיבור נתונים כדי להתחבר לשירותי Google Play." - "חשבון לא חוקי" - "החשבון שצוין לא קיים במכשיר זה. בחר חשבון אחר." - "‏בעיה לא ידועה בשירותי Google Play." - "‏שירותי Google Play" - "‏שירותי Google Play, שחלק מהאפליקציות שלך מתבססות עליהם, אינם נתמכים על ידי המכשיר שברשותך. צור קשר עם היצרן לקבלת סיוע." - "נראה שהתאריך במכשיר שגוי. בדוק את התאריך במכשיר." - "עדכן" - "היכנס" - "‏היכנס באמצעות Google" - - "‏יש אפליקציה שניסתה להשתמש בגרסה שגויה של שירותי Google Play." - "‏יש אפליקציה המחייבת הפעלה של שירותי Google Play." - "‏יש אפליקציה המחייבת התקנה של שירותי Google Play." - "‏יש אפליקציה המחייבת עדכון של שירותי Google Play." - "‏שגיאה בשירותי Google Play" - "התבקשה על ידי %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-ja/strings.xml b/platform/android/libs/google_play_services/res/values-ja/strings.xml deleted file mode 100644 index 0d8b606230..0000000000 --- a/platform/android/libs/google_play_services/res/values-ja/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Play開発者サービスの入手" - "このアプリの実行にはGoogle Play開発者サービスが必要ですが、お使いの携帯端末にはインストールされていません。" - "このアプリの実行にはGoogle Play開発者サービスが必要ですが、お使いのタブレットにはインストールされていません。" - "Play開発者サービスの入手" - "Play開発者サービスの有効化" - "このアプリの実行には、Google Play開発者サービスの有効化が必要です。" - "Play開発者サービスの有効化" - "Play開発者サービスの更新" - "このアプリの実行には、Google Play開発者サービスの更新が必要です。" - "ネットワークエラー" - "Google Play開発者サービスに接続するには、データ接続が必要です。" - "無効なアカウント" - "指定したアカウントはこの端末上に存在しません。別のアカウントを選択してください。" - "Google Play開発者サービスで原因不明の問題が発生しました。" - "Google Play開発者サービス" - "一部のアプリが使用しているGoogle Play開発者サービスは、お使いの端末ではサポートされていません。詳しくは、端末メーカーまでお問い合わせください。" - "端末上の日付が正しくないようです。端末上の日付をご確認ください。" - "更新" - "ログイン" - "Googleでログイン" - - "アプリはGoogle Play開発者サービスの不適切なバージョンを使用しようとしました。" - "アプリではGoogle Play開発者サービスを有効にする必要があります。" - "アプリではGoogle Play開発者サービスをインストールする必要があります。" - "アプリではGoogle Play開発者サービスをアップデートする必要があります。" - "Google Play開発者サービスのエラー" - "%1$sによるリクエスト" - diff --git a/platform/android/libs/google_play_services/res/values-ka-rGE/strings.xml b/platform/android/libs/google_play_services/res/values-ka-rGE/strings.xml deleted file mode 100644 index 8a2c74aa98..0000000000 --- a/platform/android/libs/google_play_services/res/values-ka-rGE/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play სერვისების მიღება" - "ეს აპი ვერ გაეშვება Google Play სერვისების გარეშე, რაც თქვენს ტელეფონზე ვერ იძებნება." - "ეს აპი ვერ გაეშვება Google Play სერვისების გარეშე, რაც თქვენს ტელეფონზე ვერ იძებნება." - "Google Play სერვისების მიღება" - "Google Play სერვისების გააქტიურება" - "ეს აპი არ იმუშავებს, თუ არ გაააქტიურებთ Google Play სერვისებს." - "Google Play სერვისების გააქტიურება" - "Google Play სერვისების განახლება" - "ეს აპი ვერ გაეშვება, თუ Google Play სერვისებს არ განაახლებთ." - "ქსელის შეცდომა" - "Google Play Services-თან დასაკავშირებლად მონაცემთა გადაცემა აუცილებელია." - "ანგარიში არასწორია" - "მითითებული ანგარიში ამ მოწყობილობაზე არ არსებობს. გთხოვთ, აირჩიოთ სხვა ანგარიში." - "Google Play სერვისებთან დაკავშირებით უცნობი შეფერხება წარმოიშვა." - "Google Play სერვისები" - "Google Play სერვისები, რაც თქვენს ზოგიერთ აპს ჭირდება, თქვენს მოწყობილობაზე მხარდაჭერილი არ არის. გთხოვთ, დაუკავშირდეთ მწარმოებელს დახმარებისათვის." - "როგორც ჩანს, მოწყობილობის თარიღი არასწორია. გთხოვთ, შეამოწმოთ მოწყობილობის თარიღი." - "განახლება" - "შესვლა" - "Google-ით შესვლა" - - "აპლიკაცია შეეცადა გამოეყენებინა Google Play სერვისების არასწორი ვერსია." - "აპლიკაცია საჭიროებს გააქტიურებულ Google Play Services." - "აპლიკაცია საჭიროებს Google Play Services-ის ინსტალაციას." - "აპლიკაცია საჭიროებს Google Play Services-ის განახლებას." - "Google Play სერვისების შეცდომა" - "მომთხოვნი: %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-km-rKH/strings.xml b/platform/android/libs/google_play_services/res/values-km-rKH/strings.xml deleted file mode 100644 index afebf30875..0000000000 --- a/platform/android/libs/google_play_services/res/values-km-rKH/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "ទទួល​សេវាកម្ម​កម្សាន្ត Google" - "កម្មវិធី​នេះ​នឹង​មិន​ដំណើរការ​​ទេ​បើ​គ្មាន​​សេវាកម្ម​កម្សាន្ត​ Google ដែល​ទូរស័ព្ទ​របស់​​អ្នក​មិន​មាន។" - "​​កម្មវិធី​នេះ​នឹង​មិន​ដំណើរការ​​ទេ​បើ​គ្មាន​​សេវាកម្ម​កម្សាន្ត​ Google ដែល​​កុំព្យូទ័រ​បន្ទះ​របស់​អ្នក​មិន​មាន។" - "ទទួល​សេវាកម្ម​កម្សាន្ត Google" - "បើក​សេវាកម្ម​កម្សាន្ត Google" - "កម្ម​វិធី​នេះ​នឹង​មិន​ដំណើរការ​ទេ​ លុះត្រាតែ​អ្នក​បើក​សេវាកម្ម​​កម្សាន្ត​ Google ។" - "បើក​សេវាកម្ម​កម្សាន្ត Google" - "ធ្វើ​បច្ចុប្បន្នភាព​សេវាកម្ម​កម្សាន្ត Google" - "កម្មវិធី​នេះ​នឹង​មិន​ដំណើរការ​ទេ​ លុះត្រាតែ​អ្នក​ធ្វើ​បច្ចុប្បន្នភាព​សេវាកម្ម​កម្សាន្ត Google ។" - "កំហុស​​បណ្ដាញ" - "បាន​ទាមទារ​ការ​តភ្ជាប់​ទិន្នន័យ ដើម្បី​ភ្ជាប់​សេវាកម្ម​ឃ្លាំង​កម្មវិធី។" - "គណនី​មិន​ត្រឹមត្រូវ" - "គណនី​ដែល​បាន​បញ្ជាក់​មិន​មាន​នៅ​លើ​ឧបករណ៍​នេះ​ទេ។ សូម​ជ្រើស​គណនី​ផ្សេង​។" - "មិន​ស្គាល់​បញ្ហា​ជាមួយ​សេវាកម្ម​កម្សាន្ត Google ។" - "សេវាកម្ម​កម្សាន្ត​ Google" - "សេវាកម្ម​កម្សាន្ត Google អាស្រ័យ​លើ​កម្មវិធី​របស់​អ្នក មិន​ត្រូវ​បាន​គាំទ្រ​ដោយ​ឧបករណ៍​របស់​អ្នក។ សូម​ទាក់ទង​ក្រុមហ៊ុន​ផលិត​សម្រាប់​ជំនួយ។" - "កាលបរិច្ឆេទ​លើ​ឧបករណ៍​បង្ហាញ​ថា​មិន​ត្រឹមត្រូវ។ សូម​ពិនិត្យ​កាលបរិច្ឆេទ​លើ​ឧបករណ៍។" - "ធ្វើ​បច្ចុប្បន្នភាព" - "ចូល" - "ចូល​ដោយ​ប្រើ​ Google" - - "កម្មវិធី​​​ព្យាយាម​ប្រើ​កំណែ​មិនល្អ​របស់​សេវា​កម្ម​ឃ្លាំ​កម្មវិធី។" - "កម្មវិធី​ទាមទារ​​បើក​សេវាកម្ម​ឃ្លាំង​កម្មវិធី។" - "កម្មវិធី​ទាមទារ​ការ​ដំឡើង​សេវាកម្ម​ឃ្លាំង​កម្មវិធី។" - "កម្មវិធី​ទាមទារ​​ធ្វើ​បច្ចុប្បន្នភាព​សេវាកម្ម​ឃ្លាំង​កម្មវិធី។" - "កំហុស​សេវា​កម្ម​កម្សាន្ត Google" - "បាន​ស្នើ​ដោយ %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-ko/strings.xml b/platform/android/libs/google_play_services/res/values-ko/strings.xml deleted file mode 100644 index e37f1fd02a..0000000000 --- a/platform/android/libs/google_play_services/res/values-ko/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play 서비스 설치" - "휴대전화에 Google Play 서비스가 설치되어 있어야 이 앱이 실행됩니다." - "태블릿에 Google Play 서비스가 설치되어 있어야 이 앱이 실행됩니다." - "Google Play 서비스 설치" - "Google Play 서비스 사용" - "Google Play 서비스를 사용하도록 설정해야 이 앱이 작동합니다." - "Google Play 서비스 사용" - "Google Play 서비스 업데이트" - "Google Play 서비스를 업데이트해야만 이 앱이 실행됩니다." - "네트워크 오류" - "Google Play 서비스에 연결하려면 데이터 연결이 필요합니다." - "올바르지 않은 계정" - "지정한 계정이 이 기기에 존재하지 않습니다. 다른 계정을 선택하세요." - "Google Play 서비스에 알 수 없는 문제가 발생했습니다." - "Google Play 서비스" - "일부 사용자 애플리케이션에 필요한 Google Play 서비스가 사용자 기기에서 지원되지 않습니다. 기기 제조업체에 문의하시기 바랍니다." - "기기의 날짜가 잘못된 것 같습니다. 기기의 날짜를 확인해 주세요." - "업데이트" - "로그인" - "Google 계정으로 로그인" - - "애플리케이션에서 잘못된 버전의 Google Play 서비스를 사용하려고 했습니다." - "Google Play 서비스를 사용하도록 설정해야 하는 애플리케이션입니다." - "Google Play 서비스를 설치해야 하는 애플리케이션입니다." - "Google Play 서비스를 업데이트해야 하는 애플리케이션입니다." - "Google Play 서비스 오류" - "%1$s에서 요청" - diff --git a/platform/android/libs/google_play_services/res/values-lo-rLA/strings.xml b/platform/android/libs/google_play_services/res/values-lo-rLA/strings.xml deleted file mode 100644 index 32bcb0b92b..0000000000 --- a/platform/android/libs/google_play_services/res/values-lo-rLA/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "ຕິດຕັ້ງບໍລິການ Google Play" - "ແອັບຯນີ້ຈະບໍ່ສາມາດເຮັດວຽກໄດ້ໂດຍທີ່ບໍ່ມີບໍລິການ Google Play ເຊິ່ງຂາດຫາຍໄປໃນໂທລະສັບຂອງທ່ານ." - "ແອັບຯນີ້ຈະບໍ່ສາມາດເຮັດວຽກໄດ້ໂດຍທີ່ບໍ່ມີບໍລິການ Google Play ເຊິ່ງຂາດຫາຍໄປໃນແທັບເລັດຂອງທ່ານ." - "ຕິດຕັ້ງບໍລິການ Google Play" - "ເປີດໃຊ້ບໍລິການ Google Play" - "ແອັບຯນີ້ຈະບໍ່ສາມາດເຮັດວຽກໄດ້ຈົນກວ່າທ່ານຈະເປີດໃຊ້ບໍລິການ Google Play" - "ເປີດໃຊ້ບໍລິການ Google Play" - "ອັບເດດບໍລິການ Google Play" - "ແອັບຯນີ້ຈະບໍ່ສາມາດເຮັດວຽກໄດ້ຈົນກວ່າທ່ານຈະອັບເດດບໍລິການ Google Play." - "ເຄືອຂ່າຍຜິດພາດ" - "ຕ້ອງໃຊ້ການເຊື່ອມຕໍ່ອິນເຕີເນັດເພື່ອໃຊ້ Google Play Services." - "ບັນຊີບໍ່ຖືກຕ້ອງ" - "ບັນຊີທີ່ເລືອກບໍ່ມີໃນອຸປະກອນນີ້. ກະລຸນາເລືອກບັນຊີອື່ນ." - "ມີປັນຫາທີ່ບໍ່ຄາດຄິດໃນບໍລິການ Google Play." - "ບໍລິການ Google Play" - "ບໍລິການ Google Play ທີ່ບາງແອັບພລິເຄຊັນຂອງທ່ານຕ້ອງອາໄສນັ້ນ ບໍ່ຖືກຮອງຮັບໃນອຸປະກອນຂອງທ່ານ. ກະລຸນາຕິດຕໍ່ຜູ້ຜະລິດສຳລັບການແນະນຳ." - "ວັນທີຂອງອຸປະກອນບໍ່ຖືກຕ້ອງ. ກະລຸນາກວດສອບວັນທີຂອງອຸປະກອນຂອງທ່ານ." - "ອັບເດດ" - "ເຂົ້າສູ່ລະບົບ" - "ເຂົ້າສູ່ລະບົບດ້ວຍ Google" - - "ແອັບພລິເຄຊັນໄດ້ພະຍາຍາມໃຊ້ Google Play Services ເວີຊັນທີ່ບໍ່ສາມາດໃຊ້ໄດ້." - "ແອັບພລິເຄຊັນຕ້ອງການເປີດນຳໃຊ້ Google Play Services." - "ແອັບພລິເຄຊັນຕ້ອງການໃຫ້ຕິດຕັ້ງ Google Play Services." - "ແອັບພລິເຄຊັນຕ້ອງການອັບເດດ Google Play Services." - "ບໍລິການ Google Play ຜິດພາດ" - "ຮ້ອງຂໍໂດຍ %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-lt/strings.xml b/platform/android/libs/google_play_services/res/values-lt/strings.xml deleted file mode 100644 index 73de5fa9aa..0000000000 --- a/platform/android/libs/google_play_services/res/values-lt/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Gauti „Google Play“ paslaugų" - "Ši programa neveiks be „Google Play“ paslaugų, kurios neįdiegtos telefone." - "Ši programa neveiks be „Google Play“ paslaugų, kurios neįdiegtos planšetiniame kompiuteryje." - "Gauti „Google Play“ paslaugų" - "Įgalinti „Google Play“ paslaugas" - "Ši programa neveiks, jei neįgalinsite „Google Play“ paslaugų." - "Įgal. „Google Play“ paslaugas" - "Atnaujinti „Google Play“ paslaugas" - "Ši programa neveiks, jei neatnaujinsite „Google Play“ paslaugų." - "Tinklo klaida" - "Norint prisijungti prie „Google Play“ paslaugų reikia duomenų ryšio." - "Netinkama paskyra" - "Nurodytos paskyros šiame įrenginyje nėra. Pasirinkite kitą paskyrą." - "Nežinoma „Google Play“ paslaugų problema." - "„Google Play“ paslaugos" - "Jūsų įrenginys nepalaiko „Google Play“ paslaugų, kuriomis remiasi kai kurios programos. Jei reikia pagalbos, susisiekite su gamintoju." - "Įrenginyje nurodyta data neteisinga. Patikrinkite įrenginyje nurodytą datą." - "Atnaujinti" - "Prisij." - "Prisij. naud. „Google“" - - "Programa bandė naudotis netinkama „Google Play“ paslaugų versija." - "Norint naudoti programą būtina įgalinti „Google Play“ paslaugas." - "Norint naudoti programą būtina įdiegti „Google Play“ paslaugas." - "Norint naudoti programą būtina atnaujinti „Google Play“ paslaugas." - "„Google Play“ paslaugų klaida" - "Užklausą pateikė „%1$s“" - diff --git a/platform/android/libs/google_play_services/res/values-lv/strings.xml b/platform/android/libs/google_play_services/res/values-lv/strings.xml deleted file mode 100644 index 9e4b6ee6bd..0000000000 --- a/platform/android/libs/google_play_services/res/values-lv/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play pakalpojumu iegūšana" - "Lai šī lietotne darbotos, tālrunī ir jāinstalē Google Play pakalpojumi." - "Lai šī lietotne darbotos, planšetdatorā ir jāinstalē Google Play pakalpojumi." - "Iegūt Google Play pakalpojumus" - "Google Play pakalpojumu iespējošana" - "Lai šī lietotne darbotos, iespējojiet Google Play pakalpojumus." - "Iespējot Google Play pakalpojumus" - "Google Play pakalpojumu atjaunināšana" - "Lai šī lietotne darbotos, atjauniniet Google Play pakalpojumus." - "Tīkla kļūda" - "Lai izveidotu savienojumu ar Google Play pakalpojumiem, ir nepieciešams datu savienojums." - "Nederīgs konts" - "Norādītais konts šajā ierīcē nepastāv. Lūdzu, izvēlieties citu kontu." - "Nezināma problēma ar Google Play pakalpojumiem." - "Google Play pakalpojumi" - "Jūsu ierīce neatbalsta Google Play pakalpojumus, kuri nepieciešami dažu jūsu lietojumprogrammu darbībai. Lūdzu, sazinieties ar ražotāju, lai saņemtu palīdzību." - "Šķiet, ka ierīcē ir iestatīts nepareizs datums. Lūdzu, pārbaudiet ierīces datumu." - "Atjaunināt" - "Pierakst." - "Pierakstīties Google" - - "Lietojumpr. mēģināja izmantot nederīgu Google Play pakalp. versiju." - "Lai lietojumprogramma darbotos, ir jāiespējo Google Play pakalpojumi." - "Lai lietojumprogramma darbotos, ir jāinstalē Google Play pakalpojumi." - "Lai lietojumprogramma darbotos, jāatjaunina Google Play pakalpojumi." - "Google Play pakalpojumu kļūda" - "Pieprasījums no lietotnes %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-mn-rMN/strings.xml b/platform/android/libs/google_play_services/res/values-mn-rMN/strings.xml deleted file mode 100644 index 1743256a11..0000000000 --- a/platform/android/libs/google_play_services/res/values-mn-rMN/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play үйлчилгээ авах" - "Таны утсанд байхгүй байгаа Google Play үйлчилгээг идэвхжүүлж байж энэ апп-г ажиллуулах боломжтой." - "Таны таблетэд байхгүй Google Play үйлчилгээг идэвхжүүлж байж энэ апп-г ажиллуулах боломжтой." - "Google Play үйлчилгээ авах" - "Google Play үйлчилгээг идэвхжүүлэх" - "Та Google Play үйлчилгээг идэвхжүүлж байж энэ апп-г ажиллуулах боломжтой." - "Google Play үйлчилгээг идэвхжүүлэх" - "Google Play үйлчилгээг шинэчлэх" - "Та Google Play үйлчилгээг шинэчлэхгүй бол энэ апп ажиллах боломжгүй." - "Сүлжээний алдаа" - "Google Play үйлчилгээнд холбогдохын тулд дата холболт шаардлагатай." - "Буруу акаунт" - "Заасан акаунт энэ төхөөрөмж дээр байхгүй байна. Өөр акаунт сонгоно уу." - "Google Play үйлчилгээтэй холбоотой тодорхойгүй алдаа." - "Google Play үйлчилгээ" - "Таны зарим аппликешнүүдийн хамаардаг Google Play үйлчилгээ таны төхөөрөмжид дэмжигдэхгүй байна. Тусламж авахын тулд үйлдвэрлэгчтэй холбоо барина уу." - "Төхөөрөмжийн огноо буруу байгаа бололтой. Төхөөрөмжийн огноог шалгана уу." - "Шинэчлэх" - "Нэвтрэх" - "Google-р нэвтрэх:" - - "Аппликешн Google Play Үйлчилгээний муу хувилбарыг ашиглахыг оролдлоо." - "Аппликешн Google Play Үйлчилгээг идэвхжүүлсэн байхыг шаардана." - "Аппликешн Google Play Үйлчилгээг суулгахыг шаардана." - "Аппликешн Google Play Үйлчилгээг шинэчлэхийг шаардана." - "Google Play үйлчилгээний алдаа" - "Хүсэлт гаргасан %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-ms-rMY/strings.xml b/platform/android/libs/google_play_services/res/values-ms-rMY/strings.xml deleted file mode 100644 index 8e8a4b9b8a..0000000000 --- a/platform/android/libs/google_play_services/res/values-ms-rMY/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Dapatkan perkhidmatan Google Play" - "Apl ini tidak akan berfungsi tanpa perkhidmatan Google Play dan apl ini tiada pada telefon anda." - "Apl ini tidak akan berfungsi tanpa perkhidmatan Google Play dan apl ini tiada pada tablet anda." - "Dapatkan perkhidmatan Google Play" - "Dayakan perkhidmatan Google Play" - "Apl ini tidak akan berfungsi kecuali anda mendayakan perkhidmatan Google Play." - "Dayakan perkhidmatan Google Play" - "Kemas kini perkhidmatan Google Play" - "Apl ini tidak akan berfungsi kecuali anda mengemas kini perkhidmatan Google Play." - "Ralat Rangkaian" - "Sambungan data diperlukan untuk menyambung ke perkhidmatan Google Play." - "Akaun Tidak Sah" - "Akaun yang dinyatakan tidak wujud pada peranti ini. Sila pilih akaun yang lain." - "Isu tidak diketahui dengan perkhidmatan Google Play." - "Perkhidmatan Google Play" - "Peranti anda tidak menyokong perkhidmatan Google Play, sedangkan sesetengah aplikasi anda memerlukannya. Sila hubungi pengilang untuk bantuan." - "Tarikh pada peranti kelihatan tidak betul. Sila semak tarikh pada peranti." - "Kemas kini" - "Log masuk" - "Log masuk dengan Google" - - "Aplikasi cuba menggunakan versi Perkhidmatan Google Play yang rosak." - "Perkhidmatan Google Play perlu didayakan untuk menggunakan aplikasi." - "Perkhidmatan Google Play perlu dipasang untuk mengguankan aplikasi." - "Perkhidmatan Google Play perlu dikemas kini untuk menggunakan aplikasi." - "Ralat perkhidmatan Google Play" - "Diminta oleh %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-nb/strings.xml b/platform/android/libs/google_play_services/res/values-nb/strings.xml deleted file mode 100644 index 1e16bbb6d2..0000000000 --- a/platform/android/libs/google_play_services/res/values-nb/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Installer Google Play Tjenester" - "Denne appen kan ikke kjøres uten Google Play Tjenester, som ikke er installert på telefonen din." - "Denne appen kan ikke kjøres uten Google Play Tjenester, som ikke er installert på nettbrettet ditt." - "Installer Google Play Tjenester" - "Aktiver Google Play Tjenester" - "Denne appen fungerer ikke med mindre du aktiverer Google Play Tjenester." - "Aktiver Google Play Tjenester" - "Oppdater Google Play Tjenester" - "Denne appen kan ikke kjøres før du oppdaterer Google Play Tjenester." - "Nettverksfeil" - "Du må ha datatilkobling for å koble deg til Google Play-tjenester." - "Ugyldig konto" - "Den angitte kontoen finnes ikke på enheten. Velg en annen konto." - "Det oppsto et ukjent problem med Google Play Tjenester." - "Google Play-tjenester" - "Google Play Tjenester, som noen av appene er avhengige av, støttes ikke av enheten. Ta kontakt med produsenten for å få hjelp." - "Datoen på enheten ser ut til å være feil. Sjekk datoen på enheten." - "Oppdater" - "Logg på" - "Logg inn med Google" - - "En app prøvde å bruke en skadet versjon av Google Play Tjenester." - "En app krever Google Play Tjenester for å aktiveres." - "En app krever at Google Play Tjenester installeres." - "En app krever at Google Play Tjenester oppdateres." - "Google Play Tjenester-feil" - "Forespurt av %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-nl/strings.xml b/platform/android/libs/google_play_services/res/values-nl/strings.xml deleted file mode 100644 index f38db5fcdb..0000000000 --- a/platform/android/libs/google_play_services/res/values-nl/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play-services ophalen" - "Deze app kan niet worden uitgevoerd zonder Google Play-services die ontbreken op uw telefoon." - "Deze app kan niet worden uitgevoerd zonder Google Play-services die ontbreken op uw tablet." - "Google Play-services ophalen" - "Google Play-services inschakelen" - "Deze app werkt niet, tenzij u Google Play-services inschakelt." - "Google Play-services inschak." - "Google Play-services bijwerken" - "Deze app kan niet worden uitgevoerd, tenzij u Google Play-services bijwerkt." - "Netwerkfout" - "Er is een gegevensverbinding nodig om verbinding te kunnen maken met Google Play-services." - "Ongeldig account" - "Het gespecificeerde account bestaat niet op dit apparaat. Kies een ander account." - "Onbekend probleem met Google Play-services." - "Google Play-services" - "Google Play-services, dat vereist is voor een aantal van uw applicaties, wordt niet ondersteund door uw apparaat. Neem contact op met de fabrikant voor ondersteuning." - "De datum op het apparaat lijkt onjuist. Controleer de datum op het apparaat." - "Bijwerken" - "Inloggen" - "Inloggen met Google" - - "Onjuiste versie van Google Play-services wordt gebruikt." - "Google Play-services moet zijn ingeschakeld voor een applicatie." - "Google Play-services moet zijn geïnstalleerd voor een applicatie." - "Google Play-services moet worden geüpdatet voor een applicatie." - "Fout met Google Play-services" - "Aangevraagd door %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-pl/strings.xml b/platform/android/libs/google_play_services/res/values-pl/strings.xml deleted file mode 100644 index 5eba15ff3a..0000000000 --- a/platform/android/libs/google_play_services/res/values-pl/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Pobierz Usługi Google Play" - "Ta aplikacja nie będzie działać bez Usług Google Play, których nie masz na telefonie." - "Ta aplikacja nie będzie działać bez Usług Google Play, których nie masz na tablecie." - "Pobierz Usługi Google Play" - "Włącz Usługi Google Play" - "Ta aplikacja nie będzie działać, jeśli nie włączysz Usług Google Play." - "Włącz Usługi Google Play" - "Aktualizuj Usługi Google Play" - "Ta aplikacja nie będzie działać, jeśli nie zaktualizujesz Usług Google Play." - "Błąd sieci" - "Korzystanie z usług Google Play wymaga połączenia z internetem." - "Nieprawidłowe konto" - "Podanego konta nie ma na tym urządzeniu. Wybierz inne konto." - "Nieznany problem z Usługami Google Play." - "Usługi Google Play" - "Usługi Google Play, od których zależy działanie niektórych aplikacji, nie są obsługiwane na Twoim urządzeniu. Skontaktuj się z producentem, by uzyskać pomoc." - "Data ustawiona na urządzeniu wydaje się nieprawidłowa. Sprawdź datę ustawioną na urządzeniu." - "Aktualizuj" - "Zaloguj się" - "Zaloguj się przez Google" - - "Aplikacja próbowała skorzystać z nieprawidłowej wersji Usług Google Play." - "Aplikacja wymaga włączenia Usług Google Play." - "Aplikacja wymaga zainstalowania Usług Google Play." - "Aplikacja wymaga aktualizacji Usług Google Play." - "Błąd usług Google Play" - "Żądanie z aplikacji %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-pt-rBR/strings.xml b/platform/android/libs/google_play_services/res/values-pt-rBR/strings.xml deleted file mode 100644 index 6db462d709..0000000000 --- a/platform/android/libs/google_play_services/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Instale o Google Play Services" - "Este aplicativo não funciona sem o Google Play Services, que não está instalado em seu telefone." - "Este aplicativo não funciona sem o Google Play Services, que não está instalado em seu tablet." - "Instalar o Google Play Services" - "Ative o Google Play Services" - "Este aplicativo só funciona com o Google Play Services ativado." - "Ativar o Google Play Services" - "Atualize o Google Play Services" - "Este aplicativo só funciona com uma versão atualizada do Google Play Services." - "Erro na rede" - "É necessária uma conexão de dados para conectar ao Google Play Services." - "Conta inválida" - "A conta especificada não existe no dispositivo. Escolha outra conta." - "Problema desconhecido com o Google Play Services." - "Play Services" - "O Google Play Services, necessário para alguns dos aplicativos, não é compatível com seu dispositivo. Entre em contato com o fabricante para obter assistência." - "A data no dispositivo parece incorreta. Verifique a data no dispositivo." - "Atualizar" - "Login" - "Fazer login com o Google" - - "Um aplicativo tentou usar uma versão errada do Google Play Services." - "Um aplicativo requer a ativação do Google Play Services." - "Um aplicativo requer a instalação do Google Play Services." - "Um aplicativo requer a atualização do Google Play Services." - "Ocorreu um erro no Google Play Services" - "Solicitado por %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-pt-rPT/strings.xml b/platform/android/libs/google_play_services/res/values-pt-rPT/strings.xml deleted file mode 100644 index 0ceafcb95f..0000000000 --- a/platform/android/libs/google_play_services/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Obter serviços do Google Play" - "Esta aplicação não será executada sem os serviços do Google Play, que estão em falta no seu telemóvel." - "Esta aplicação não será executada sem os serviços do Google Play, que estão em falta no seu tablet." - "Obter serviços do Google Play" - "Ativar serviços do Google Play" - "Esta aplicação não funcionará enquanto não ativar os serviços do Google Play." - "Ativar serviços do Google Play" - "Atualizar serviços do Google Play" - "Esta aplicação não será executada enquanto não atualizar os serviços do Google Play." - "Erro de Rede" - "É necessária uma ligação de dados para se ligar aos Serviços do Google Play." - "Conta Inválida" - "A conta especificada não existe neste dispositivo. Escolha uma conta diferente." - "Problema desconhecido nos serviços do Google Play." - "Serviços do Google Play" - "Os serviços do Google Play, dos quais dependem algumas das suas aplicações, não são suportados pelo seu dispositivo. Contacte o fabricante para obter assistência." - "A data no dispositivo parece estar incorreta. Verifique a data no dispositivo." - "Atualizar" - "Inic. ses." - "Inic. sessão com o Google" - - "Aplicação tentou utiliz. versão incorreta dos Serviços do Google Play." - "Uma aplicação necessita da ativação dos Serviços do Google Play." - "Uma aplicação necessita da instalação dos Serviços do Google Play." - "Uma aplicação necessita da atualização dos Serviços do Google Play." - "Erro dos serviços do Google Play" - "Solicitado por %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-pt/strings.xml b/platform/android/libs/google_play_services/res/values-pt/strings.xml deleted file mode 100644 index 6db462d709..0000000000 --- a/platform/android/libs/google_play_services/res/values-pt/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Instale o Google Play Services" - "Este aplicativo não funciona sem o Google Play Services, que não está instalado em seu telefone." - "Este aplicativo não funciona sem o Google Play Services, que não está instalado em seu tablet." - "Instalar o Google Play Services" - "Ative o Google Play Services" - "Este aplicativo só funciona com o Google Play Services ativado." - "Ativar o Google Play Services" - "Atualize o Google Play Services" - "Este aplicativo só funciona com uma versão atualizada do Google Play Services." - "Erro na rede" - "É necessária uma conexão de dados para conectar ao Google Play Services." - "Conta inválida" - "A conta especificada não existe no dispositivo. Escolha outra conta." - "Problema desconhecido com o Google Play Services." - "Play Services" - "O Google Play Services, necessário para alguns dos aplicativos, não é compatível com seu dispositivo. Entre em contato com o fabricante para obter assistência." - "A data no dispositivo parece incorreta. Verifique a data no dispositivo." - "Atualizar" - "Login" - "Fazer login com o Google" - - "Um aplicativo tentou usar uma versão errada do Google Play Services." - "Um aplicativo requer a ativação do Google Play Services." - "Um aplicativo requer a instalação do Google Play Services." - "Um aplicativo requer a atualização do Google Play Services." - "Ocorreu um erro no Google Play Services" - "Solicitado por %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-ro/strings.xml b/platform/android/libs/google_play_services/res/values-ro/strings.xml deleted file mode 100644 index eb428964ec..0000000000 --- a/platform/android/libs/google_play_services/res/values-ro/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Descărcaţi Servicii Google Play" - "Această aplicaţie nu poate rula fără Servicii Google Play, care lipsesc de pe telefon." - "Această aplicaţie nu poate rula fără Servicii Google Play, care lipsesc de pe tabletă." - "Obţineţi Servicii Google Play" - "Activaţi Servicii Google Play" - "Această aplicaţie nu va funcţiona decât dacă activaţi Servicii Google Play." - "Activaţi Servicii Google Play" - "Actualizaţi Servicii Google Play" - "Această aplicaţie nu poate rula decât dacă actualizaţi Servicii Google Play." - "Eroare de reţea" - "Este necesară o conexiune de date pentru a vă conecta la serviciile Google Play." - "Cont nevalid" - "Contul menționat nu există pe acest dispozitiv. Alegeți alt cont." - "Problemă necunoscută privind Servicii Google Play." - "Servicii Google Play" - "Gadgetul nu acceptă serviciile Google Play, pe care se bazează unele dintre aplicații. Pentru asistență, contactați producătorul gadgetului." - "Data de pe dispozitiv pare să fie incorectă. Verificați data de pe dispozitiv." - "Actualizaţi" - "Conectați" - "Conectați-vă cu Google" - - "Aplicația a încercat să utilizeze o vers. Servicii Google Play greșită" - "O aplicație necesită activarea Serviciilor Google Play." - "O aplicație necesită instalarea Serviciilor Google Play." - "O aplicație necesită o actualizare pentru Servicii Google Play." - "Eroare Servicii Google Play" - "Solicitată de %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-ru/strings.xml b/platform/android/libs/google_play_services/res/values-ru/strings.xml deleted file mode 100644 index c784aae95c..0000000000 --- a/platform/android/libs/google_play_services/res/values-ru/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Установите Сервисы Google Play" - "Для работы этого приложения требуется установить Сервисы Google Play." - "Для работы этого приложения требуется установить Сервисы Google Play." - "Установить" - "Включите Сервисы Google Play" - "Для работы этого приложения требуется включить Сервисы Google Play." - "Включить" - "Обновите Сервисы Google Play" - "Для работы этого приложения требуется обновить Сервисы Google Play." - "Ошибка сети" - "Для работы с Google Play требуется подключение к сети." - "Недействительный аккаунт" - "Этого аккаунта нет на устройстве. Выберите другой." - "Неизвестная ошибка с Сервисами Google Play." - "Сервисы Google Play" - "Сервисы Google Play, необходимые для работы некоторых приложений, не поддерживаются на вашем устройстве. Обратитесь к производителю." - "Проверьте правильность даты, указанной на устройстве." - "Обновить" - "Войти" - "Войти в аккаунт Google" - - "Версия сервисов Google Play неисправна" - "Для работы приложения требуется включить сервисы Google Play" - "Для работы приложения требуется установить сервисы Google Play" - "Для работы приложения требуется обновить сервисы Google Play" - "Ошибка сервисов Google Play" - "Запрос от приложения \"%1$s\"" - diff --git a/platform/android/libs/google_play_services/res/values-sk/strings.xml b/platform/android/libs/google_play_services/res/values-sk/strings.xml deleted file mode 100644 index 125d87f6da..0000000000 --- a/platform/android/libs/google_play_services/res/values-sk/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Inštalovať služby Google Play" - "Na spustenie tejto aplikácie sa vyžadujú služby Google Play, ktoré v telefóne nemáte." - "Na spustenie tejto aplikácie sa vyžadujú služby Google Play, ktoré v tablete nemáte." - "Inštalovať služby Google Play" - "Povoliť služby Google Play" - "Táto aplikácia bude fungovať až po povolení služieb Google Play." - "Povoliť služby Google Play" - "Aktualizovať služby Google Play" - "Túto aplikáciu bude možné spustiť až po aktualizácii služieb Google Play." - "Chyba siete" - "Pripojenie k službám Google Play si vyžaduje dátové pripojenie." - "Neplatný účet" - "Zadaný účet v tomto zariadení neexistuje. Vyberte iný účet." - "Neznámy problém so službami Google Play." - "Služby Google Play" - "Niektoré vaše aplikácie vyžadujú služby Google Play, ktoré vo vašom zariadení nie sú podporované. Ak potrebujete pomoc, kontaktujte výrobcu." - "Dátum nastavený v zariadení sa zdá byť nesprávny. Skontrolujte ho." - "Aktualizovať" - "Prihlásiť sa" - "Prihlásiť sa do účtu Google" - - "Aplikácia sa pokúsila použiť nesprávnu verziu služieb Google Play." - "Aplikácia vyžaduje povolenie služieb Google Play." - "Aplikácia vyžaduje inštaláciu služieb Google Play." - "Aplikácia vyžaduje aktualizáciu služieb Google Play." - "Chyba služieb Google Play" - "Vyžiadané aplikáciou %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-sl/strings.xml b/platform/android/libs/google_play_services/res/values-sl/strings.xml deleted file mode 100644 index df5821f94c..0000000000 --- a/platform/android/libs/google_play_services/res/values-sl/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Namestite storitve Google Play" - "Ta aplikacija ne deluje brez storitev Google Play, ki jih ni v telefonu." - "Ta aplikacija ne deluje brez storitev Google Play, ki jih ni v tabličnem računalniku." - "Namestite storitve Google Play" - "Omogočite storitve Google Play" - "Aplikacija ne bo delovala, če ne omogočite storitev Google Play." - "Omogočite storitve Google Play" - "Posodobite storitve Google Play" - "Ta aplikacija ne deluje, če ne posodobite storitev Google Play." - "Omrežna napaka" - "Za povezavo s storitvami Google Play potrebujete internetno povezavo." - "Neveljaven račun" - "V tej napravi ne obstaja navedeni račun. Izberite drugega." - "Neznana težava s storitvami Google Play." - "Storitve Google Play" - "Vaša naprava na podpira storitev Google Play, ki jih potrebujejo nekatere od vaših aplikacij. Za pomoč se obrnite na izdelovalca." - "Videti je, da je datum v napravi napačen. Preverite ga." - "Posodobi" - "Prijava" - "Prijavite se v Google" - - "Aplikacija je poskusila uporabiti napačno različico Storitev Google Play." - "Za delovanje aplikacije morate omogočiti Storitve Google Play." - "Za delovanje aplikacije morate namestiti Storitve Google Play." - "Za delovanje aplikacije morate posodobiti Storitve Google Play." - "Napaka storitev Google Play" - "Zahtevala aplikacija %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-sr/strings.xml b/platform/android/libs/google_play_services/res/values-sr/strings.xml deleted file mode 100644 index ad0b549547..0000000000 --- a/platform/android/libs/google_play_services/res/values-sr/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Преузимање Google Play услуга" - "Ова апликација не може да се покрене без Google Play услуга, које недостају на телефону." - "Ова апликација не може да се покрене без Google Play услуга, које недостају на таблету." - "Преузми Google Play услуге" - "Омогућавање Google Play услуга" - "Ова апликација неће функционисати ако не омогућите Google Play услуге." - "Омогући Google Play услуге" - "Ажурирање Google Play услуга" - "Ова апликација не може да се покрене ако не ажурирате Google Play услуге." - "Грешка на мрежи" - "За повезивање са Google Play услугама потребна је веза за пренос података." - "Неважећи налог" - "Наведени налог не постоји на овом уређају. Одаберите други налог." - "Непознат проблем са Google Play услугама." - "Google Play услуге" - "Google Play услуге, које су потребне за функционисање неких од апликација, нису подржане на уређају. Контактирајте произвођача да бисте добили помоћ." - "Изгледа да су подаци на уређају нетачни. Проверите датум на уређају." - "Ажурирај" - "Пријави ме" - "Пријави ме преко Google-а" - - "Апликација је покушала да користи лошу верзију Google Play услуга." - "Апликација захтева да Google Play услуге буду омогућене." - "Апликација захтева инсталирање Google Play услуга." - "Апликација захтева ажурирање Google Play услуга." - "Грешка Google Play услуга" - "Захтева %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-sv/strings.xml b/platform/android/libs/google_play_services/res/values-sv/strings.xml deleted file mode 100644 index 6a10395f4b..0000000000 --- a/platform/android/libs/google_play_services/res/values-sv/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Hämta Google Play Tjänster" - "Den här appen kan inte köras utan Google Play Tjänster, som saknas på mobilen." - "Den här appen kan inte köras utan Google Play Tjänster, som saknas på surfplattan." - "Hämta Google Play Tjänster" - "Aktivera Google Play Tjänster" - "Du måste aktivera Google Play Tjänster för att den här appen ska fungera." - "Aktivera Google Play Tjänster" - "Uppdatera Google Play Tjänster" - "Du måste uppdatera Google Play Tjänster innan du kan köra den här appen." - "Nätverksfel" - "En dataanslutning krävs för att ansluta till Google Plays tjänster." - "Ogiltigt konto" - "Det angivna kontot finns inte på den här enheten. Välj ett annat konto." - "Okänt problem med Google Play Tjänster" - "Google Play-tjänster" - "Några av dina appar använder Google Play-tjänster som inte stöds av din enhet. Kontakta tillverkaren om du vill ha hjälp." - "Datumet på enheten verkar inte vara rätt. Kontrollera datumet på enheten." - "Uppdatera" - "Logga in" - "Logga in med Google" - - "En olämplig version av Google Play Tjänster anropades av en app." - "Google Play Tjänster måste aktiveras för en att app ska fungera." - "Google Play Tjänster måste installeras för att en app ska fungera." - "Google Play Tjänster måste uppdateras för en app ska fungera." - "Fel på Google Play Tjänster" - "Begärdes av %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-sw/strings.xml b/platform/android/libs/google_play_services/res/values-sw/strings.xml deleted file mode 100644 index 7f29bf5f2e..0000000000 --- a/platform/android/libs/google_play_services/res/values-sw/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Pata huduma za Google Play" - "Programu hii haiwezi kuendeshwa bila huduma za Google Play, ambazo hazipo kwenye simu yako." - "Programu hii haiwezi kufanya kazi bila huduma za Google Play, ambazo hazipatikani kwenye kompyuta kibao yako." - "Pata huduma za Google Play" - "Wezesha huduma za Google Play" - "Programu hii haitafanya kazi mpaka utakapowezesha huduma za Google Play." - "Wezesha huduma za Google Play" - "Sasisha huduma za Google Play" - "Programu hii haiwezi kuendeshwa mpaka utakaposasisha huduma za Google Play." - "Hitilafu ya Mtandao" - "Muunganisho wa data unahitajika ili kuunganisha kwenye huduma za Google Play." - "Akaunti Batili" - "Akaunti iliyobainishwa haipo kwenye kifaa hiki. Tafadhali chagua akaunti tofauti." - "Suala lisilojulikana na huduma za Google Play." - "Huduma za Google Play" - "Huduma za Google Play, ambazo baadhi ya programu zako zinategemea, si linganifu na kifaa chako. Tafadhali wasiliana na mtengenezaji kwa usaidizi." - "Inaeonekana tarehe ya kifaa sio sahihi. Tafadhali angalia tarehe ya kifaa." - "Sasisha" - "Ingia" - "Ingia ukitumia Google" - - "Programu ilijaribu kutumia toleo baya la Huduma za Google Play." - "Programu inahitaji Huduma za Google Play ili kuwashwa." - "Programu inahitaji usakinishaji wa Huduma za Google Play." - "Programu inahitaji sasisho la Huduma za Google Play." - "Hitilafu kwenye Huduma za Google Play" - "Imeombwa na %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-th/strings.xml b/platform/android/libs/google_play_services/res/values-th/strings.xml deleted file mode 100644 index 6f098fe801..0000000000 --- a/platform/android/libs/google_play_services/res/values-th/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "รับบริการ Google Play" - "แอปพลิเคชันนี้จะไม่ทำงานหากไม่มีบริการ Google Play ซึ่งไม่มีในโทรศัพท์ของคุณ" - "แอปพลิเคชันนี้จะไม่ทำงานหากไม่มีบริการ Google Play ซึ่งไม่มีในแท็บเล็ตของคุณ" - "รับบริการ Google Play" - "เปิดใช้งานบริการ Google Play" - "แอปพลิเคชันนี้จะไม่ทำงานจนกว่าคุณจะเปิดใช้งานบริการ Google Play" - "เปิดใช้งานบริการ Google Play" - "อัปเดตบริการ Google Play" - "แอปพลิเคชันนี้จะไม่ทำงานจนกว่าคุณจะอัปเดตบริการ Google Play" - "ข้อผิดพลาดของเครือข่าย" - "ต้องมีการเขื่อมต่อข้อมูลเพื่อเชื่อมต่อกับบริการ Google Play" - "บัญชีไม่ถูกต้อง" - "บัญชีที่ระบุไม่มีอยู่บนอุปกรณ์นี้ โปรดเลือกบัญชีอื่น" - "ปัญหาที่ไม่รู้จักของบริการ Google Play" - "บริการ Google Play" - "บริการ Google Play ซึ่งใช้งานในบางแอปพลิเคชัน ไม่ได้รับการสนับสนุนโดยอุปกรณ์ของคุณ โปรดติดต่อผู้ผลิตเพื่อขอรับความช่วยเหลือ" - "วันที่บนอุปกรณ์ไม่ถูกต้อง โปรดตรวจสอบวันที่บนอุปกรณ์" - "อัปเดต" - "ลงชื่อใช้" - "ลงชื่อเข้าใช้ด้วย Google" - - "แอปพลิเคชันหนึ่งพยายามใช้เวอร์ชันที่ไม่เหมาะสมของบริการ Google Play" - "แอปพลิเคชันหนึ่งจำเป็นต้องมีบริการ Google Play เพื่อเปิดใช้งาน" - "แอปพลิเคชันหนึ่งจำเป็นต้องมีการติดตั้งบริการ Google Play" - "แอปพลิเคชันหนึ่งจำเป็นต้องมีการอัปเดตสำหรับบริการ Google Play" - "ข้อผิดพลาดของบริการ Google Play" - "ขอโดย %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-tl/strings.xml b/platform/android/libs/google_play_services/res/values-tl/strings.xml deleted file mode 100644 index 337f73c0cd..0000000000 --- a/platform/android/libs/google_play_services/res/values-tl/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Kumuha ng mga serbisyo ng Google Play" - "Hindi tatakbo ang app na ito nang wala ang mga serbisyo ng Google Play, na wala sa iyong telepono." - "Hindi gagana ang app na ito nang wala ang mga serbisyo ng Google Play, na wala sa iyong tablet." - "Kumuha ng Google Play services" - "Paganahin ang Google Play services" - "Hindi gagana ang app na ito maliban kung papaganahin mo ang mga serbisyo ng Google Play." - "Enable Google Play services" - "I-update ang mga serbisyo ng Google Play" - "Hindi gagana ang app na ito maliban kung i-a-update mo ang mga serbisyo ng Google Play." - "May Error sa Network" - "Kailangan ng koneksyon ng data upang makakonekta sa mga serbisyo ng Google Play." - "Di-wasto ang Account" - "Hindi umiiral ang tinukoy na account sa device na ito. Mangyaring pumili ng ibang account." - "May hindi alam na isyu sa mga serbisyo ng Google Play." - "Mga serbisyo ng Google Play" - "Ang mga serbisyo ng Google Play, kung saan nakadepende ang ilan sa iyong mga application, ay hindi sinusuportahan ng iyong device. Mangyaring makipag-ugnay sa manufacturer para sa tulong." - "Mukhang hindi tama ang petsa sa device. Pakisuri ang petsa sa device." - "I-update" - "Sign in" - "Mag-sign in sa Google" - - "May app na sumubok ng maling bersyon ng Mga Serbisyo ng Google Play." - "Kailangan ng application na na-enable ang Mga Serbisyo ng Google Play." - "Kailangan ng application na ma-install ang Serbisyo ng Google Play." - "Kailangan ng application na i-update ang Mga Serbisyo ng Google Play." - "Error sa mga serbisyo ng Google Play" - "Hiniling ng %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-tr/strings.xml b/platform/android/libs/google_play_services/res/values-tr/strings.xml deleted file mode 100644 index 17e61e5fcb..0000000000 --- a/platform/android/libs/google_play_services/res/values-tr/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Google Play hizmetlerini edinin" - "Google Play Hizmetleri telefonunuzda yok ve bu uygulama Google Play Hizmetleri olmadan çalışmaz." - "Google Play Hizmetleri tabletinizde yok ve bu uygulama Google Play Hizmetleri olmadan çalışmaz." - "Google Play hizmetlerini edin" - "Google Play hizmetlerini etkinleştir" - "Bu uygulama, Google Play Hizmetleri etkinleştirilmeden çalışmaz" - "Google Play hizmetlerini etkinleştir" - "Google Play hizmetlerini güncelle" - "Bu uygulama Google Play Hizmetleri güncellenmeden çalışmaz." - "Ağ Hatası" - "Google Play hizmetlerine bağlanmak için bir veri bağlantısı gerekiyor." - "Geçersiz Hesap" - "Belirtilen hesap bu cihazda mevcut değil. Lütfen farklı bir hesap seçin." - "Google Play hizmetleriyle ilgili bilinmeyen sorun." - "Google Play hizmetleri" - "Cihazınız, uygulamalarınızdan bazıları için gerekli olan Google Play hizmetlerini desteklemiyor. Lütfen yardım için üreticiyle iletişim kurun." - "Cihazdaki tarih doğru görünmüyor. Lütfen cihazda ayarlı tarihi kontrol edin." - "Güncelle" - "Oturum aç" - "Google\'da oturum aç" - - "Bir uygulama, Google Play Hizmetleri\'nin bozuk bir sürümünü kullanmayı denedi." - "Bir uygulama, Google Play Hizmetleri\'nin etkin olmasını gerektiriyor." - "Bir uygulama, Google Play Hizmetleri\'nin yüklenmesini gerektiriyor." - "Bir uygulama, Google Play Hizmetleri için bir güncelleme gerektiriyor." - "Google Play hizmetleri hatası" - "İstekte bulunan: %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-uk/strings.xml b/platform/android/libs/google_play_services/res/values-uk/strings.xml deleted file mode 100644 index d657aea68d..0000000000 --- a/platform/android/libs/google_play_services/res/values-uk/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Установити Google Play Послуги" - "Ця програма не запуститься без Google Play Послуг, яких немає у вашому телефоні." - "Ця програма не запуститься без Google Play Послуг, яких немає на вашому планшетному ПК." - "Установити Google Play Послуги" - "Увімкнути Google Play Послуги" - "Ця програма не працюватиме, поки ви не ввімкнете Google Play Послуги." - "Увімкнути Google Play Послуги" - "Оновити Google Play Послуги" - "Ця програма не запуститься, поки ви не оновите Google Play Послуги." - "Помилка мережі" - "Для під’єднання до сервісів Google Play потрібне з’єднання з мережею." - "Недійсний обліковий запис" - "Указаний обліковий запис не існує на цьому пристрої. Виберіть інший обліковий запис." - "Google Play Послуги – невідома проблема." - "Сервіси Google Play" - "Ваш пристрій не підтримує Сервіси Google Play, від яких залежить робота деяких програм. Зверніться по допомогу до виробника." - "Схоже, на пристрої вказано неправильну дату. Перевірте її." - "Оновити" - "Увійти" - "Увійти в обл.запис Google" - - "Програма спробувала застосувати хибну версію Сервісів Google Play." - "Щоб програма працювала, потрібно ввімкнути Сервіси Google Play." - "Щоб програма працювала, потрібно встановити Сервіси Google Play." - "Щоб програма працювала, потрібно оновити Сервіси Google Play." - "Помилка Сервісів Google Play" - "Запит від програми %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-vi/strings.xml b/platform/android/libs/google_play_services/res/values-vi/strings.xml deleted file mode 100644 index a0434a08ac..0000000000 --- a/platform/android/libs/google_play_services/res/values-vi/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Cài đặt dịch vụ của Google Play" - "Ứng dụng này sẽ không chạy nếu không có dịch vụ của Google Play. Điện thoại của bạn bị thiếu dịch vụ này." - "Ứng dụng này sẽ không chạy nếu không có dịch vụ của Google Play. Máy tính bảng của bạn bị thiếu dịch vụ này." - "Cài đặt dịch vụ của Google Play" - "Bật dịch vụ của Google Play" - "Ứng dụng này sẽ không hoạt động trừ khi bạn bật dịch vụ của Google Play." - "Bật dịch vụ của Google Play" - "Cập nhật dịch vụ của Google Play" - "Ứng dụng này sẽ không chạy trừ khi bạn cập nhật dịch vụ của Google Play." - "Lỗi mạng" - "Cần có kết nối dữ liệu để kết nối với các dịch vụ của Google Play." - "Tài khoản không hợp lệ" - "Tài khoản đã chỉ định không tồn tại trên thiết bị này. Vui lòng chọn một tài khoản khác." - "Sự cố không xác định với dịch vụ của Google Play." - "Dịch vụ của Google Play" - "Các dịch vụ của Google Play mà một số ứng dụng của bạn dựa vào không được thiết bị của bạn hỗ trợ. Vui lòng liên hệ với nhà sản xuất để được hỗ trợ." - "Ngày trên thiết bị có vẻ không chính xác. Vui lòng kiểm tra ngày trên thiết bị." - "Cập nhật" - "Đăng nhập" - "Đăng nhập bằng Google" - - "Ứng dụng đã cố sử dụng phiên bản không đúng của Dịch vụ của Google Play." - "Ứng dụng yêu cầu Dịch vụ của Google Play phải được bật." - "Ứng dụng yêu cầu cài đặt Dịch vụ của Google Play." - "Ứng dụng yêu cầu cập nhật dành cho Dịch vụ Google Play." - "Lỗi dịch vụ của Google Play" - "Được yêu cầu bởi %1$s" - diff --git a/platform/android/libs/google_play_services/res/values-zh-rCN/strings.xml b/platform/android/libs/google_play_services/res/values-zh-rCN/strings.xml deleted file mode 100644 index 4339e3eb01..0000000000 --- a/platform/android/libs/google_play_services/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "获取 Google Play 服务" - "您的手机中没有 Google Play 服务,您必须先安装该服务才能运行此应用。" - "您的平板电脑中没有 Google Play 服务,您必须先安装该服务才能运行此应用。" - "获取 Google Play 服务" - "启用 Google Play 服务" - "您必须先启用 Google Play 服务才能运行此应用。" - "启用 Google Play 服务" - "更新 Google Play 服务" - "您必须先更新 Google Play 服务才能运行此应用。" - "网络错误" - "您必须有数据网络连接才能接入 Google Play 服务。" - "无效帐户" - "此设备上不存在指定的帐户,请选择其他帐户。" - "Google Play 服务出现未知问题。" - "Google Play 服务" - "您的设备不支持部分应用所依赖的 Google Play 服务。请与设备制造商联系,以寻求帮助。" - "设备上的日期似乎不正确,请在设备上检查日期。" - "更新" - "登录" - "使用 Google 帐户登录" - - "某个应用尝试使用的 Google Play 服务版本有误。" - "某个应用要求启用 Google Play 服务。" - "某个应用要求安装 Google Play 服务。" - "某个应用要求更新 Google Play 服务。" - "Google Play 服务出错" - "由“%1$s”发出" - diff --git a/platform/android/libs/google_play_services/res/values-zh-rHK/strings.xml b/platform/android/libs/google_play_services/res/values-zh-rHK/strings.xml deleted file mode 100644 index abe6cf1457..0000000000 --- a/platform/android/libs/google_play_services/res/values-zh-rHK/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "取得 Google Play 服務" - "您的手機未安裝 Google Play 服務,安裝後才能執行這個應用程式。" - "您的平板電腦未安裝 Google Play 服務,安裝後才能執行這個應用程式。" - "取得 Google Play 服務" - "啟用 Google Play 服務" - "您必須啟用 Google Play 服務,才能執行這個應用程式。" - "啟用 Google Play 服務" - "更新 Google Play 服務" - "您必須更新 Google Play 服務,才能執行這個應用程式。" - "網絡錯誤" - "要連接 Google Play 服務,必需數據連線。" - "無效的帳戶" - "這個裝置上沒有您指定的帳戶,請選擇其他帳戶。" - "Google Play 服務出現不明問題。" - "Google Play 服務" - "您的裝置不支援部分應用程式所需的 Google Play 服務。如需協助,請與您的裝置製造商聯絡。" - "裝置上的日期看來不正確,請檢查裝置上的日期。" - "更新" - "登入" - "登入 Google" - - "應用程式嘗試使用錯誤版本的「Google Play 服務」。" - "必須啟用「Google Play 服務」,才能使用應用程式。" - "必須安裝「Google Play 服務」,才能使用應用程式。" - "必須更新「Google Play 服務」,才能使用應用程式。" - "Google Play 服務錯誤" - "「%1$s」提出要求" - diff --git a/platform/android/libs/google_play_services/res/values-zh-rTW/strings.xml b/platform/android/libs/google_play_services/res/values-zh-rTW/strings.xml deleted file mode 100644 index a66018ab52..0000000000 --- a/platform/android/libs/google_play_services/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "取得 Google Play 服務" - "您的手機並未安裝 Google Play 服務,所以無法執行這個應用程式。" - "您的平板電腦並未安裝 Google Play 服務,所以無法執行這個應用程式。" - "取得 Google Play 服務" - "啟用 Google Play 服務" - "您必須啟用 Google Play 服務,這個應用程式才能運作。" - "啟用 Google Play 服務" - "更新 Google Play 服務" - "您必須更新 Google Play 服務,才能執行這個應用程式。" - "網路錯誤" - "需要數據連線才能連上 Google Play 服務。" - "無效的帳戶" - "這個裝置上沒有您所指定的帳戶,請選擇其他帳戶。" - "Google Play 服務發生不明問題。" - "Google Play 服務" - "您的裝置不支援部分應用程式所需的 Google Play 服務。如需協助,請與您的裝置製造商聯絡。" - "裝置上的日期似乎不正確,請檢查裝置上的日期。" - "更新" - "登入" - "使用 Google 帳戶登入" - - "應用程式嘗試使用的 Google Play 服務版本有誤。" - "應用程式需要啟用 Google Play 服務。" - "應用程式需要安裝 Google Play 服務。" - "應用程式需要更新 Google Play 服務。" - "Google Play 服務錯誤" - "提出要求的應用程式:%1$s" - diff --git a/platform/android/libs/google_play_services/res/values-zu/strings.xml b/platform/android/libs/google_play_services/res/values-zu/strings.xml deleted file mode 100644 index 572d9a52c4..0000000000 --- a/platform/android/libs/google_play_services/res/values-zu/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - "Thola amasevisi e-Google Play" - "Lolu hlelo lokusebenza ngeke lusebenze ngaphandle kwamasevisi e-Google Play, angekho efonini yakho." - "Lolu hlelo lokusebenza ngeke lusebenze ngaphandle kwamasevisi e-Google Play, angekho kuthebulethi yakho." - "Thola amasevisi e-Google Play" - "Nika amandla amasevisi e-Google Play" - "Lolu hlelo lokusebenza ngeke lusebenze ngaphandle nje kokuthi unike amandla amasevisi e-Google Play." - "Nika amandla amasevisi e-Google Play" - "Buyekeza amasevisi e-Google Play" - "Lolu hlelo lokusebenza ngeke lusebenze ngaphandle nje kokuthi ubuyekeze amasevisi e-Google Play." - "Iphutha lenethiwekhi" - "Kudingeka ukuxhumeka kwedatha ukuze kuxhunyekwe kumasevisi we-Google Play." - "I-Akhawunti engavumelekile" - "I-Akhawunti ecacisiwe ayikho kule divayisi. Sicela ukhethe i-akhawunti ehlukile." - "Indaba engaziwa yamasevisi we-Google Play" - "Amasevisi we-Google Play" - "Amasevisi we-Google Play, okungukuthi ezinye izinhlelo zakho zithembele kuwo, awasekelwe yidivayisi yakho. Sicela uxhumane nomkhiqizi ukuze uthole usizo." - "Idethi kudivayisi ibonakala ingalungile. Sicela uhlole idethi kudivayisi." - "Isibuyekezo" - "Ngena ngemvume" - "Ngena ngemvume nge-Google" - - "Uhlelo lokusebenza luzame ukusebenzisa inguqulo embi yamasevisi we-Google Play." - "Uhlelo lokusebenza ludinga amasevisi we-Google Play ukuze anikwe amandla." - "Uhlelo lokusebenza ludinga ukufakwa kwamasevisi we-Google Play." - "Uhlelo lokusebenza ludinga isibuyekezo samasevisi we-Google Play." - "Iphutha lamasevisi we-Google Play" - "Kucelwe yi-%1$s" - diff --git a/platform/android/libs/google_play_services/res/values/ads_attrs.xml b/platform/android/libs/google_play_services/res/values/ads_attrs.xml deleted file mode 100644 index 4e97a734a6..0000000000 --- a/platform/android/libs/google_play_services/res/values/ads_attrs.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/values/colors.xml b/platform/android/libs/google_play_services/res/values/colors.xml deleted file mode 100644 index 6b2740a509..0000000000 --- a/platform/android/libs/google_play_services/res/values/colors.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - @android:color/white - @android:color/white - #FFAAAAAA - @android:color/white - #FF737373 - @android:color/white - #FFAAAAAA - #FF737373 - #FFDD4B39 - #d2d2d2 - \ No newline at end of file diff --git a/platform/android/libs/google_play_services/res/values/maps_attrs.xml b/platform/android/libs/google_play_services/res/values/maps_attrs.xml deleted file mode 100644 index aaf65c529e..0000000000 --- a/platform/android/libs/google_play_services/res/values/maps_attrs.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/libs/google_play_services/res/values/strings.xml b/platform/android/libs/google_play_services/res/values/strings.xml deleted file mode 100644 index 3e8731e056..0000000000 --- a/platform/android/libs/google_play_services/res/values/strings.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Get Google Play services - - - This app won\'t run without Google Play services, which are missing from your phone. - - - This app won\'t run without Google Play services, which are missing from your tablet. - - - Get Google Play services - - - Enable Google Play services - - - This app won\'t work unless you enable Google Play services. - - - Enable Google Play services - - - Update Google Play services - - - This app won\'t run unless you update Google Play services. - - - Network Error - - - A data connection is required to connect to Google Play services. - - - Invalid Account - - - The specified account does not exist on this device. Please choose a different account. - - - Unknown issue with Google Play services. - - - Google Play services - - - Google Play services, which some of your applications rely on, is not supported by your device. Please contact the manufacturer for assistance. - - - The date on the device appears to be incorrect. Please check the date on the device. - - - Update - - - Sign in - - - Sign in with Google - - - - - - An application attempted to use a bad version of Google Play Services. - - - - An application requires Google Play Services to be enabled. - - - - An application requires installation of Google Play Services. - - - - An application requires an update for Google Play Services. - - - - Google Play services error - - - Requested by %1$s - - - - - Powered by Google - - - diff --git a/platform/android/libs/google_play_services/res/values/version.xml b/platform/android/libs/google_play_services/res/values/version.xml deleted file mode 100644 index 1e7fd4e7ef..0000000000 --- a/platform/android/libs/google_play_services/res/values/version.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 4323000 - diff --git a/platform/android/libs/google_play_services/src/android/UnusedStub.java b/platform/android/libs/google_play_services/src/android/UnusedStub.java deleted file mode 100644 index d546b0ba9f..0000000000 --- a/platform/android/libs/google_play_services/src/android/UnusedStub.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android; - -// Stub java file to make inclusion into some IDE's work. -public final class UnusedStub { - private UnusedStub() { } -} diff --git a/platform/android/libs/play_licensing/AndroidManifest.xml b/platform/android/libs/play_licensing/AndroidManifest.xml deleted file mode 100644 index c7849130c3..0000000000 --- a/platform/android/libs/play_licensing/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl b/platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl deleted file mode 100644 index c816558afc..0000000000 --- a/platform/android/libs/play_licensing/aidl/ILicenseResultListener.aidl +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.vending.licensing; - -// Android library projects do not yet support AIDL, so this has been -// precompiled into the src directory. -oneway interface ILicenseResultListener { - void verifyLicense(int responseCode, String signedData, String signature); -} diff --git a/platform/android/libs/play_licensing/aidl/ILicensingService.aidl b/platform/android/libs/play_licensing/aidl/ILicensingService.aidl deleted file mode 100644 index 664510ce0c..0000000000 --- a/platform/android/libs/play_licensing/aidl/ILicensingService.aidl +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.vending.licensing; - -import com.android.vending.licensing.ILicenseResultListener; - -// Android library projects do not yet support AIDL, so this has been -// precompiled into the src directory. -oneway interface ILicensingService { - void checkLicense(long nonce, String packageName, in ILicenseResultListener listener); -} diff --git a/platform/android/libs/play_licensing/build.xml b/platform/android/libs/play_licensing/build.xml deleted file mode 100644 index 0e800d6b9b..0000000000 --- a/platform/android/libs/play_licensing/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/libs/play_licensing/proguard-project.txt b/platform/android/libs/play_licensing/proguard-project.txt deleted file mode 100644 index f2fe1559a2..0000000000 --- a/platform/android/libs/play_licensing/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/platform/android/libs/play_licensing/project.properties b/platform/android/libs/play_licensing/project.properties deleted file mode 100644 index f28bc833e1..0000000000 --- a/platform/android/libs/play_licensing/project.properties +++ /dev/null @@ -1,12 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -android.library=true -# Project target. -target=android-15 diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java deleted file mode 100644 index ee12c68deb..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/AESObfuscator.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import com.google.android.vending.licensing.util.Base64; -import com.google.android.vending.licensing.util.Base64DecoderException; - -import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; -import java.security.spec.KeySpec; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * An Obfuscator that uses AES to encrypt data. - */ -public class AESObfuscator implements Obfuscator { - private static final String UTF8 = "UTF-8"; - private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC"; - private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; - private static final byte[] IV = - { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 }; - private static final String header = "com.android.vending.licensing.AESObfuscator-1|"; - - private Cipher mEncryptor; - private Cipher mDecryptor; - - /** - * @param salt an array of random bytes to use for each (un)obfuscation - * @param applicationId application identifier, e.g. the package name - * @param deviceId device identifier. Use as many sources as possible to - * create this unique identifier. - */ - public AESObfuscator(byte[] salt, String applicationId, String deviceId) { - try { - SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); - KeySpec keySpec = - new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); - SecretKey tmp = factory.generateSecret(keySpec); - SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); - mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM); - mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV)); - mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM); - mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV)); - } catch (GeneralSecurityException e) { - // This can't happen on a compatible Android device. - throw new RuntimeException("Invalid environment", e); - } - } - - public String obfuscate(String original, String key) { - if (original == null) { - return null; - } - try { - // Header is appended as an integrity check - return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8))); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Invalid environment", e); - } catch (GeneralSecurityException e) { - throw new RuntimeException("Invalid environment", e); - } - } - - public String unobfuscate(String obfuscated, String key) throws ValidationException { - if (obfuscated == null) { - return null; - } - try { - String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8); - // Check for presence of header. This serves as a final integrity check, for cases - // where the block size is correct during decryption. - int headerIndex = result.indexOf(header+key); - if (headerIndex != 0) { - throw new ValidationException("Header not found (invalid data or key)" + ":" + - obfuscated); - } - return result.substring(header.length()+key.length(), result.length()); - } catch (Base64DecoderException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (IllegalBlockSizeException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (BadPaddingException e) { - throw new ValidationException(e.getMessage() + ":" + obfuscated); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Invalid environment", e); - } - } -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java deleted file mode 100644 index 17cc7a7cfd..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/APKExpansionPolicy.java +++ /dev/null @@ -1,397 +0,0 @@ - -package com.google.android.vending.licensing; - -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - -/** - * Default policy. All policy decisions are based off of response data received - * from the licensing service. Specifically, the licensing server sends the - * following information: response validity period, error retry period, and - * error retry count. - *

- * These values will vary based on the the way the application is configured in - * the Android Market publishing console, such as whether the application is - * marked as free or is within its refund period, as well as how often an - * application is checking with the licensing service. - *

- * Developers who need more fine grained control over their application's - * licensing policy should implement a custom Policy. - */ -public class APKExpansionPolicy implements Policy { - - private static final String TAG = "APKExpansionPolicy"; - private static final String PREFS_FILE = "com.android.vending.licensing.APKExpansionPolicy"; - private static final String PREF_LAST_RESPONSE = "lastResponse"; - private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; - private static final String PREF_RETRY_UNTIL = "retryUntil"; - private static final String PREF_MAX_RETRIES = "maxRetries"; - private static final String PREF_RETRY_COUNT = "retryCount"; - private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; - private static final String DEFAULT_RETRY_UNTIL = "0"; - private static final String DEFAULT_MAX_RETRIES = "0"; - private static final String DEFAULT_RETRY_COUNT = "0"; - - private static final long MILLIS_PER_MINUTE = 60 * 1000; - - private long mValidityTimestamp; - private long mRetryUntil; - private long mMaxRetries; - private long mRetryCount; - private long mLastResponseTime = 0; - private int mLastResponse; - private PreferenceObfuscator mPreferences; - private Vector mExpansionURLs = new Vector(); - private Vector mExpansionFileNames = new Vector(); - private Vector mExpansionFileSizes = new Vector(); - - /** - * The design of the protocol supports n files. Currently the market can - * only deliver two files. To accommodate this, we have these two constants, - * but the order is the only relevant thing here. - */ - public static final int MAIN_FILE_URL_INDEX = 0; - public static final int PATCH_FILE_URL_INDEX = 1; - - /** - * @param context The context for the current application - * @param obfuscator An obfuscator to be used with preferences. - */ - public APKExpansionPolicy(Context context, Obfuscator obfuscator) { - // Import old values - SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); - mPreferences = new PreferenceObfuscator(sp, obfuscator); - mLastResponse = Integer.parseInt( - mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); - mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, - DEFAULT_VALIDITY_TIMESTAMP)); - mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); - mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); - mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); - } - - /** - * We call this to guarantee that we fetch a fresh policy from the server. - * This is to be used if the URL is invalid. - */ - public void resetPolicy() { - mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)); - setRetryUntil(DEFAULT_RETRY_UNTIL); - setMaxRetries(DEFAULT_MAX_RETRIES); - setRetryCount(Long.parseLong(DEFAULT_RETRY_COUNT)); - setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); - mPreferences.commit(); - } - - /** - * Process a new response from the license server. - *

- * This data will be used for computing future policy decisions. The - * following parameters are processed: - *

    - *
  • VT: the timestamp that the client should consider the response valid - * until - *
  • GT: the timestamp that the client should ignore retry errors until - *
  • GR: the number of retry errors that the client should ignore - *
- * - * @param response the result from validating the server response - * @param rawData the raw server response data - */ - public void processServerResponse(int response, - com.google.android.vending.licensing.ResponseData rawData) { - - // Update retry counter - if (response != Policy.RETRY) { - setRetryCount(0); - } else { - setRetryCount(mRetryCount + 1); - } - - if (response == Policy.LICENSED) { - // Update server policy data - Map extras = decodeExtras(rawData.extra); - mLastResponse = response; - setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE)); - Set keys = extras.keySet(); - for (String key : keys) { - if (key.equals("VT")) { - setValidityTimestamp(extras.get(key)); - } else if (key.equals("GT")) { - setRetryUntil(extras.get(key)); - } else if (key.equals("GR")) { - setMaxRetries(extras.get(key)); - } else if (key.startsWith("FILE_URL")) { - int index = Integer.parseInt(key.substring("FILE_URL".length())) - 1; - setExpansionURL(index, extras.get(key)); - } else if (key.startsWith("FILE_NAME")) { - int index = Integer.parseInt(key.substring("FILE_NAME".length())) - 1; - setExpansionFileName(index, extras.get(key)); - } else if (key.startsWith("FILE_SIZE")) { - int index = Integer.parseInt(key.substring("FILE_SIZE".length())) - 1; - setExpansionFileSize(index, Long.parseLong(extras.get(key))); - } - } - } else if (response == Policy.NOT_LICENSED) { - // Clear out stale policy data - setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); - setRetryUntil(DEFAULT_RETRY_UNTIL); - setMaxRetries(DEFAULT_MAX_RETRIES); - } - - setLastResponse(response); - mPreferences.commit(); - } - - /** - * Set the last license response received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param l the response - */ - private void setLastResponse(int l) { - mLastResponseTime = System.currentTimeMillis(); - mLastResponse = l; - mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); - } - - /** - * Set the current retry count and add to preferences. You must manually - * call PreferenceObfuscator.commit() to commit these changes to disk. - * - * @param c the new retry count - */ - private void setRetryCount(long c) { - mRetryCount = c; - mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); - } - - public long getRetryCount() { - return mRetryCount; - } - - /** - * Set the last validity timestamp (VT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param validityTimestamp the VT string received - */ - private void setValidityTimestamp(String validityTimestamp) { - Long lValidityTimestamp; - try { - lValidityTimestamp = Long.parseLong(validityTimestamp); - } catch (NumberFormatException e) { - // No response or not parseable, expire in one minute. - Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); - lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; - validityTimestamp = Long.toString(lValidityTimestamp); - } - - mValidityTimestamp = lValidityTimestamp; - mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); - } - - public long getValidityTimestamp() { - return mValidityTimestamp; - } - - /** - * Set the retry until timestamp (GT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param retryUntil the GT string received - */ - private void setRetryUntil(String retryUntil) { - Long lRetryUntil; - try { - lRetryUntil = Long.parseLong(retryUntil); - } catch (NumberFormatException e) { - // No response or not parseable, expire immediately - Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); - retryUntil = "0"; - lRetryUntil = 0l; - } - - mRetryUntil = lRetryUntil; - mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); - } - - public long getRetryUntil() { - return mRetryUntil; - } - - /** - * Set the max retries value (GR) as received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param maxRetries the GR string received - */ - private void setMaxRetries(String maxRetries) { - Long lMaxRetries; - try { - lMaxRetries = Long.parseLong(maxRetries); - } catch (NumberFormatException e) { - // No response or not parseable, expire immediately - Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); - maxRetries = "0"; - lMaxRetries = 0l; - } - - mMaxRetries = lMaxRetries; - mPreferences.putString(PREF_MAX_RETRIES, maxRetries); - } - - public long getMaxRetries() { - return mMaxRetries; - } - - /** - * Gets the count of expansion URLs. Since expansionURLs are not committed - * to preferences, this will return zero if there has been no LVL fetch - * in the current session. - * - * @return the number of expansion URLs. (0,1,2) - */ - public int getExpansionURLCount() { - return mExpansionURLs.size(); - } - - /** - * Gets the expansion URL. Since these URLs are not committed to - * preferences, this will always return null if there has not been an LVL - * fetch in the current session. - * - * @param index the index of the URL to fetch. This value will be either - * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX - * @param URL the URL to set - */ - public String getExpansionURL(int index) { - if (index < mExpansionURLs.size()) { - return mExpansionURLs.elementAt(index); - } - return null; - } - - /** - * Sets the expansion URL. Expansion URL's are not committed to preferences, - * but are instead intended to be stored when the license response is - * processed by the front-end. - * - * @param index the index of the expansion URL. This value will be either - * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX - * @param URL the URL to set - */ - public void setExpansionURL(int index, String URL) { - if (index >= mExpansionURLs.size()) { - mExpansionURLs.setSize(index + 1); - } - mExpansionURLs.set(index, URL); - } - - public String getExpansionFileName(int index) { - if (index < mExpansionFileNames.size()) { - return mExpansionFileNames.elementAt(index); - } - return null; - } - - public void setExpansionFileName(int index, String name) { - if (index >= mExpansionFileNames.size()) { - mExpansionFileNames.setSize(index + 1); - } - mExpansionFileNames.set(index, name); - } - - public long getExpansionFileSize(int index) { - if (index < mExpansionFileSizes.size()) { - return mExpansionFileSizes.elementAt(index); - } - return -1; - } - - public void setExpansionFileSize(int index, long size) { - if (index >= mExpansionFileSizes.size()) { - mExpansionFileSizes.setSize(index + 1); - } - mExpansionFileSizes.set(index, size); - } - - /** - * {@inheritDoc} This implementation allows access if either:
- *
    - *
  1. a LICENSED response was received within the validity period - *
  2. a RETRY response was received in the last minute, and we are under - * the RETRY count or in the RETRY period. - *
- */ - public boolean allowAccess() { - long ts = System.currentTimeMillis(); - if (mLastResponse == Policy.LICENSED) { - // Check if the LICENSED response occurred within the validity - // timeout. - if (ts <= mValidityTimestamp) { - // Cached LICENSED response is still valid. - return true; - } - } else if (mLastResponse == Policy.RETRY && - ts < mLastResponseTime + MILLIS_PER_MINUTE) { - // Only allow access if we are within the retry period or we haven't - // used up our - // max retries. - return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); - } - return false; - } - - private Map decodeExtras(String extras) { - Map results = new HashMap(); - try { - URI rawExtras = new URI("?" + extras); - List extraList = URLEncodedUtils.parse(rawExtras, "UTF-8"); - for (NameValuePair item : extraList) { - String name = item.getName(); - int i = 0; - while (results.containsKey(name)) { - name = item.getName() + ++i; - } - results.put(name, item.getValue()); - } - } catch (URISyntaxException e) { - Log.w(TAG, "Invalid syntax error while decoding extras data from server."); - } - return results; - } - -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java deleted file mode 100644 index e5c5e2d7ca..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/DeviceLimiter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Allows the developer to limit the number of devices using a single license. - *

- * The LICENSED response from the server contains a user identifier unique to - * the <application, user> pair. The developer can send this identifier - * to their own server along with some device identifier (a random number - * generated and stored once per application installation, - * {@link android.telephony.TelephonyManager#getDeviceId getDeviceId}, - * {@link android.provider.Settings.Secure#ANDROID_ID ANDROID_ID}, etc). - * The more sources used to identify the device, the harder it will be for an - * attacker to spoof. - *

- * The server can look at the <application, user, device id> tuple and - * restrict a user's application license to run on at most 10 different devices - * in a week (for example). We recommend not being too restrictive because a - * user might legitimately have multiple devices or be in the process of - * changing phones. This will catch egregious violations of multiple people - * sharing one license. - */ -public interface DeviceLimiter { - - /** - * Checks if this device is allowed to use the given user's license. - * - * @param userId the user whose license the server responded with - * @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs - */ - int isDeviceAllowed(String userId); -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java deleted file mode 100644 index d90d6eac7b..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicenseResultListener.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is auto-generated. DO NOT MODIFY. - * Original file: aidl/ILicenseResultListener.aidl - */ -package com.google.android.vending.licensing; -import java.lang.String; -import android.os.RemoteException; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Binder; -import android.os.Parcel; -public interface ILicenseResultListener extends android.os.IInterface -{ -/** Local-side IPC implementation stub class. */ -public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicenseResultListener -{ -private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicenseResultListener"; -/** Construct the stub at attach it to the interface. */ -public Stub() -{ -this.attachInterface(this, DESCRIPTOR); -} -/** - * Cast an IBinder object into an ILicenseResultListener interface, - * generating a proxy if needed. - */ -public static com.google.android.vending.licensing.ILicenseResultListener asInterface(android.os.IBinder obj) -{ -if ((obj==null)) { -return null; -} -android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); -if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicenseResultListener))) { -return ((com.google.android.vending.licensing.ILicenseResultListener)iin); -} -return new com.google.android.vending.licensing.ILicenseResultListener.Stub.Proxy(obj); -} -public android.os.IBinder asBinder() -{ -return this; -} -public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException -{ -switch (code) -{ -case INTERFACE_TRANSACTION: -{ -reply.writeString(DESCRIPTOR); -return true; -} -case TRANSACTION_verifyLicense: -{ -data.enforceInterface(DESCRIPTOR); -int _arg0; -_arg0 = data.readInt(); -java.lang.String _arg1; -_arg1 = data.readString(); -java.lang.String _arg2; -_arg2 = data.readString(); -this.verifyLicense(_arg0, _arg1, _arg2); -return true; -} -} -return super.onTransact(code, data, reply, flags); -} -private static class Proxy implements com.google.android.vending.licensing.ILicenseResultListener -{ -private android.os.IBinder mRemote; -Proxy(android.os.IBinder remote) -{ -mRemote = remote; -} -public android.os.IBinder asBinder() -{ -return mRemote; -} -public java.lang.String getInterfaceDescriptor() -{ -return DESCRIPTOR; -} -public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException -{ -android.os.Parcel _data = android.os.Parcel.obtain(); -try { -_data.writeInterfaceToken(DESCRIPTOR); -_data.writeInt(responseCode); -_data.writeString(signedData); -_data.writeString(signature); -mRemote.transact(Stub.TRANSACTION_verifyLicense, _data, null, IBinder.FLAG_ONEWAY); -} -finally { -_data.recycle(); -} -} -} -static final int TRANSACTION_verifyLicense = (IBinder.FIRST_CALL_TRANSACTION + 0); -} -public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException; -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java deleted file mode 100644 index 95599544e4..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ILicensingService.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is auto-generated. DO NOT MODIFY. - * Original file: aidl/ILicensingService.aidl - */ -package com.google.android.vending.licensing; -import java.lang.String; -import android.os.RemoteException; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Binder; -import android.os.Parcel; -public interface ILicensingService extends android.os.IInterface -{ -/** Local-side IPC implementation stub class. */ -public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicensingService -{ -private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicensingService"; -/** Construct the stub at attach it to the interface. */ -public Stub() -{ -this.attachInterface(this, DESCRIPTOR); -} -/** - * Cast an IBinder object into an ILicensingService interface, - * generating a proxy if needed. - */ -public static com.google.android.vending.licensing.ILicensingService asInterface(android.os.IBinder obj) -{ -if ((obj==null)) { -return null; -} -android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); -if (((iin!=null)&&(iin instanceof com.google.android.vending.licensing.ILicensingService))) { -return ((com.google.android.vending.licensing.ILicensingService)iin); -} -return new com.google.android.vending.licensing.ILicensingService.Stub.Proxy(obj); -} -public android.os.IBinder asBinder() -{ -return this; -} -public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException -{ -switch (code) -{ -case INTERFACE_TRANSACTION: -{ -reply.writeString(DESCRIPTOR); -return true; -} -case TRANSACTION_checkLicense: -{ -data.enforceInterface(DESCRIPTOR); -long _arg0; -_arg0 = data.readLong(); -java.lang.String _arg1; -_arg1 = data.readString(); -com.google.android.vending.licensing.ILicenseResultListener _arg2; -_arg2 = com.google.android.vending.licensing.ILicenseResultListener.Stub.asInterface(data.readStrongBinder()); -this.checkLicense(_arg0, _arg1, _arg2); -return true; -} -} -return super.onTransact(code, data, reply, flags); -} -private static class Proxy implements com.google.android.vending.licensing.ILicensingService -{ -private android.os.IBinder mRemote; -Proxy(android.os.IBinder remote) -{ -mRemote = remote; -} -public android.os.IBinder asBinder() -{ -return mRemote; -} -public java.lang.String getInterfaceDescriptor() -{ -return DESCRIPTOR; -} -public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException -{ -android.os.Parcel _data = android.os.Parcel.obtain(); -try { -_data.writeInterfaceToken(DESCRIPTOR); -_data.writeLong(nonce); -_data.writeString(packageName); -_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null))); -mRemote.transact(Stub.TRANSACTION_checkLicense, _data, null, IBinder.FLAG_ONEWAY); -} -finally { -_data.recycle(); -} -} -} -static final int TRANSACTION_checkLicense = (IBinder.FIRST_CALL_TRANSACTION + 0); -} -public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException; -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java deleted file mode 100644 index 8b53545e61..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseChecker.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import com.google.android.vending.licensing.util.Base64; -import com.google.android.vending.licensing.util.Base64DecoderException; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.RemoteException; -import android.provider.Settings.Secure; -import android.util.Log; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Queue; -import java.util.Set; - -/** - * Client library for Android Market license verifications. - *

- * The LicenseChecker is configured via a {@link Policy} which contains the - * logic to determine whether a user should have access to the application. For - * example, the Policy can define a threshold for allowable number of server or - * client failures before the library reports the user as not having access. - *

- * Must also provide the Base64-encoded RSA public key associated with your - * developer account. The public key is obtainable from the publisher site. - */ -public class LicenseChecker implements ServiceConnection { - private static final String TAG = "LicenseChecker"; - - private static final String KEY_FACTORY_ALGORITHM = "RSA"; - - // Timeout value (in milliseconds) for calls to service. - private static final int TIMEOUT_MS = 10 * 1000; - - private static final SecureRandom RANDOM = new SecureRandom(); - private static final boolean DEBUG_LICENSE_ERROR = true; - - private ILicensingService mService; - - private PublicKey mPublicKey; - private final Context mContext; - private final Policy mPolicy; - /** - * A handler for running tasks on a background thread. We don't want license - * processing to block the UI thread. - */ - private Handler mHandler; - private final String mPackageName; - private final String mVersionCode; - private final Set mChecksInProgress = new HashSet(); - private final Queue mPendingChecks = new LinkedList(); - - /** - * @param context a Context - * @param policy implementation of Policy - * @param encodedPublicKey Base64-encoded RSA public key - * @throws IllegalArgumentException if encodedPublicKey is invalid - */ - public LicenseChecker(Context context, Policy policy, String encodedPublicKey) { - mContext = context; - mPolicy = policy; - mPublicKey = generatePublicKey(encodedPublicKey); - mPackageName = mContext.getPackageName(); - mVersionCode = getVersionCode(context, mPackageName); - HandlerThread handlerThread = new HandlerThread("background thread"); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - } - - /** - * Generates a PublicKey instance from a string containing the - * Base64-encoded public key. - * - * @param encodedPublicKey Base64-encoded public key - * @throws IllegalArgumentException if encodedPublicKey is invalid - */ - private static PublicKey generatePublicKey(String encodedPublicKey) { - try { - byte[] decodedKey = Base64.decode(encodedPublicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); - - return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); - } catch (NoSuchAlgorithmException e) { - // This won't happen in an Android-compatible environment. - throw new RuntimeException(e); - } catch (Base64DecoderException e) { - Log.e(TAG, "Could not decode from Base64."); - throw new IllegalArgumentException(e); - } catch (InvalidKeySpecException e) { - Log.e(TAG, "Invalid key specification."); - throw new IllegalArgumentException(e); - } - } - - /** - * Checks if the user should have access to the app. Binds the service if necessary. - *

- * NOTE: This call uses a trivially obfuscated string (base64-encoded). For best security, - * we recommend obfuscating the string that is passed into bindService using another method - * of your own devising. - *

- * source string: "com.android.vending.licensing.ILicensingService" - *

- * @param callback - */ - public synchronized void checkAccess(LicenseCheckerCallback callback) { - // If we have a valid recent LICENSED response, we can skip asking - // Market. - if (mPolicy.allowAccess()) { - Log.i(TAG, "Using cached license response"); - callback.allow(Policy.LICENSED); - } else { - LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(), - callback, generateNonce(), mPackageName, mVersionCode); - - if (mService == null) { - Log.i(TAG, "Binding to licensing service."); - try { - boolean bindResult = mContext - .bindService( - new Intent( - new String( - Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))), - this, // ServiceConnection. - Context.BIND_AUTO_CREATE); - - if (bindResult) { - mPendingChecks.offer(validator); - } else { - Log.e(TAG, "Could not bind to service."); - handleServiceConnectionError(validator); - } - } catch (SecurityException e) { - callback.applicationError(LicenseCheckerCallback.ERROR_MISSING_PERMISSION); - } catch (Base64DecoderException e) { - e.printStackTrace(); - } - } else { - mPendingChecks.offer(validator); - runChecks(); - } - } - } - - private void runChecks() { - LicenseValidator validator; - while ((validator = mPendingChecks.poll()) != null) { - try { - Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName()); - mService.checkLicense( - validator.getNonce(), validator.getPackageName(), - new ResultListener(validator)); - mChecksInProgress.add(validator); - } catch (RemoteException e) { - Log.w(TAG, "RemoteException in checkLicense call.", e); - handleServiceConnectionError(validator); - } - } - } - - private synchronized void finishCheck(LicenseValidator validator) { - mChecksInProgress.remove(validator); - if (mChecksInProgress.isEmpty()) { - cleanupService(); - } - } - - private class ResultListener extends ILicenseResultListener.Stub { - private final LicenseValidator mValidator; - private Runnable mOnTimeout; - - public ResultListener(LicenseValidator validator) { - mValidator = validator; - mOnTimeout = new Runnable() { - public void run() { - Log.i(TAG, "Check timed out."); - handleServiceConnectionError(mValidator); - finishCheck(mValidator); - } - }; - startTimeout(); - } - - private static final int ERROR_CONTACTING_SERVER = 0x101; - private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; - private static final int ERROR_NON_MATCHING_UID = 0x103; - - // Runs in IPC thread pool. Post it to the Handler, so we can guarantee - // either this or the timeout runs. - public void verifyLicense(final int responseCode, final String signedData, - final String signature) { - mHandler.post(new Runnable() { - public void run() { - Log.i(TAG, "Received response."); - // Make sure it hasn't already timed out. - if (mChecksInProgress.contains(mValidator)) { - clearTimeout(); - mValidator.verify(mPublicKey, responseCode, signedData, signature); - finishCheck(mValidator); - } - if (DEBUG_LICENSE_ERROR) { - boolean logResponse; - String stringError = null; - switch (responseCode) { - case ERROR_CONTACTING_SERVER: - logResponse = true; - stringError = "ERROR_CONTACTING_SERVER"; - break; - case ERROR_INVALID_PACKAGE_NAME: - logResponse = true; - stringError = "ERROR_INVALID_PACKAGE_NAME"; - break; - case ERROR_NON_MATCHING_UID: - logResponse = true; - stringError = "ERROR_NON_MATCHING_UID"; - break; - default: - logResponse = false; - } - - if (logResponse) { - String android_id = Secure.getString(mContext.getContentResolver(), - Secure.ANDROID_ID); - Date date = new Date(); - Log.d(TAG, "Server Failure: " + stringError); - Log.d(TAG, "Android ID: " + android_id); - Log.d(TAG, "Time: " + date.toGMTString()); - } - } - - } - }); - } - - private void startTimeout() { - Log.i(TAG, "Start monitoring timeout."); - mHandler.postDelayed(mOnTimeout, TIMEOUT_MS); - } - - private void clearTimeout() { - Log.i(TAG, "Clearing timeout."); - mHandler.removeCallbacks(mOnTimeout); - } - } - - public synchronized void onServiceConnected(ComponentName name, IBinder service) { - mService = ILicensingService.Stub.asInterface(service); - runChecks(); - } - - public synchronized void onServiceDisconnected(ComponentName name) { - // Called when the connection with the service has been - // unexpectedly disconnected. That is, Market crashed. - // If there are any checks in progress, the timeouts will handle them. - Log.w(TAG, "Service unexpectedly disconnected."); - mService = null; - } - - /** - * Generates policy response for service connection errors, as a result of - * disconnections or timeouts. - */ - private synchronized void handleServiceConnectionError(LicenseValidator validator) { - mPolicy.processServerResponse(Policy.RETRY, null); - - if (mPolicy.allowAccess()) { - validator.getCallback().allow(Policy.RETRY); - } else { - validator.getCallback().dontAllow(Policy.RETRY); - } - } - - /** Unbinds service if necessary and removes reference to it. */ - private void cleanupService() { - if (mService != null) { - try { - mContext.unbindService(this); - } catch (IllegalArgumentException e) { - // Somehow we've already been unbound. This is a non-fatal - // error. - Log.e(TAG, "Unable to unbind from licensing service (already unbound)"); - } - mService = null; - } - } - - /** - * Inform the library that the context is about to be destroyed, so that any - * open connections can be cleaned up. - *

- * Failure to call this method can result in a crash under certain - * circumstances, such as during screen rotation if an Activity requests the - * license check or when the user exits the application. - */ - public synchronized void onDestroy() { - cleanupService(); - mHandler.getLooper().quit(); - } - - /** Generates a nonce (number used once). */ - private int generateNonce() { - return RANDOM.nextInt(); - } - - /** - * Get version code for the application package name. - * - * @param context - * @param packageName application package name - * @return the version code or empty string if package not found - */ - private static String getVersionCode(Context context, String packageName) { - try { - return String.valueOf(context.getPackageManager().getPackageInfo(packageName, 0). - versionCode); - } catch (NameNotFoundException e) { - Log.e(TAG, "Package not found. could not get version code."); - return ""; - } - } -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java deleted file mode 100644 index b250a7147b..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseCheckerCallback.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Callback for the license checker library. - *

- * Upon checking with the Market server and conferring with the {@link Policy}, - * the library calls the appropriate callback method to communicate the result. - *

- * The callback does not occur in the original checking thread. Your - * application should post to the appropriate handling thread or lock - * accordingly. - *

- * The reason that is passed back with allow/dontAllow is the base status handed - * to the policy for allowed/disallowing the license. Policy.RETRY will call - * allow or dontAllow depending on other statistics associated with the policy, - * while in most cases Policy.NOT_LICENSED will call dontAllow and - * Policy.LICENSED will Allow. - */ -public interface LicenseCheckerCallback { - - /** - * Allow use. App should proceed as normal. - * - * @param reason Policy.LICENSED or Policy.RETRY typically. (although in - * theory the policy can return Policy.NOT_LICENSED here as well) - */ - public void allow(int reason); - - /** - * Don't allow use. App should inform user and take appropriate action. - * - * @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory - * the policy can return Policy.LICENSED here as well --- - * perhaps the call to the LVL took too long, for example) - */ - public void dontAllow(int reason); - - /** Application error codes. */ - public static final int ERROR_INVALID_PACKAGE_NAME = 1; - public static final int ERROR_NON_MATCHING_UID = 2; - public static final int ERROR_NOT_MARKET_MANAGED = 3; - public static final int ERROR_CHECK_IN_PROGRESS = 4; - public static final int ERROR_INVALID_PUBLIC_KEY = 5; - public static final int ERROR_MISSING_PERMISSION = 6; - - /** - * Error in application code. Caller did not call or set up license checker - * correctly. Should be considered fatal. - */ - public void applicationError(int errorCode); -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java deleted file mode 100644 index 61d3c7e79e..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/LicenseValidator.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import com.google.android.vending.licensing.util.Base64; -import com.google.android.vending.licensing.util.Base64DecoderException; - -import android.text.TextUtils; -import android.util.Log; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; - -/** - * Contains data related to a licensing request and methods to verify - * and process the response. - */ -class LicenseValidator { - private static final String TAG = "LicenseValidator"; - - // Server response codes. - private static final int LICENSED = 0x0; - private static final int NOT_LICENSED = 0x1; - private static final int LICENSED_OLD_KEY = 0x2; - private static final int ERROR_NOT_MARKET_MANAGED = 0x3; - private static final int ERROR_SERVER_FAILURE = 0x4; - private static final int ERROR_OVER_QUOTA = 0x5; - - private static final int ERROR_CONTACTING_SERVER = 0x101; - private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; - private static final int ERROR_NON_MATCHING_UID = 0x103; - - private final Policy mPolicy; - private final LicenseCheckerCallback mCallback; - private final int mNonce; - private final String mPackageName; - private final String mVersionCode; - private final DeviceLimiter mDeviceLimiter; - - LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback, - int nonce, String packageName, String versionCode) { - mPolicy = policy; - mDeviceLimiter = deviceLimiter; - mCallback = callback; - mNonce = nonce; - mPackageName = packageName; - mVersionCode = versionCode; - } - - public LicenseCheckerCallback getCallback() { - return mCallback; - } - - public int getNonce() { - return mNonce; - } - - public String getPackageName() { - return mPackageName; - } - - private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; - - /** - * Verifies the response from server and calls appropriate callback method. - * - * @param publicKey public key associated with the developer account - * @param responseCode server response code - * @param signedData signed data from server - * @param signature server signature - */ - public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) { - String userId = null; - // Skip signature check for unsuccessful requests - ResponseData data = null; - if (responseCode == LICENSED || responseCode == NOT_LICENSED || - responseCode == LICENSED_OLD_KEY) { - // Verify signature. - try { - Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); - sig.initVerify(publicKey); - sig.update(signedData.getBytes()); - - if (!sig.verify(Base64.decode(signature))) { - Log.e(TAG, "Signature verification failed."); - handleInvalidResponse(); - return; - } - } catch (NoSuchAlgorithmException e) { - // This can't happen on an Android compatible device. - throw new RuntimeException(e); - } catch (InvalidKeyException e) { - handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY); - return; - } catch (SignatureException e) { - throw new RuntimeException(e); - } catch (Base64DecoderException e) { - Log.e(TAG, "Could not Base64-decode signature."); - handleInvalidResponse(); - return; - } - - // Parse and validate response. - try { - data = ResponseData.parse(signedData); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Could not parse response."); - handleInvalidResponse(); - return; - } - - if (data.responseCode != responseCode) { - Log.e(TAG, "Response codes don't match."); - handleInvalidResponse(); - return; - } - - if (data.nonce != mNonce) { - Log.e(TAG, "Nonce doesn't match."); - handleInvalidResponse(); - return; - } - - if (!data.packageName.equals(mPackageName)) { - Log.e(TAG, "Package name doesn't match."); - handleInvalidResponse(); - return; - } - - if (!data.versionCode.equals(mVersionCode)) { - Log.e(TAG, "Version codes don't match."); - handleInvalidResponse(); - return; - } - - // Application-specific user identifier. - userId = data.userId; - if (TextUtils.isEmpty(userId)) { - Log.e(TAG, "User identifier is empty."); - handleInvalidResponse(); - return; - } - } - - switch (responseCode) { - case LICENSED: - case LICENSED_OLD_KEY: - int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId); - handleResponse(limiterResponse, data); - break; - case NOT_LICENSED: - handleResponse(Policy.NOT_LICENSED, data); - break; - case ERROR_CONTACTING_SERVER: - Log.w(TAG, "Error contacting licensing server."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_SERVER_FAILURE: - Log.w(TAG, "An error has occurred on the licensing server."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_OVER_QUOTA: - Log.w(TAG, "Licensing server is refusing to talk to this device, over quota."); - handleResponse(Policy.RETRY, data); - break; - case ERROR_INVALID_PACKAGE_NAME: - handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME); - break; - case ERROR_NON_MATCHING_UID: - handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID); - break; - case ERROR_NOT_MARKET_MANAGED: - handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED); - break; - default: - Log.e(TAG, "Unknown response code for license check."); - handleInvalidResponse(); - } - } - - /** - * Confers with policy and calls appropriate callback method. - * - * @param response - * @param rawData - */ - private void handleResponse(int response, ResponseData rawData) { - // Update policy data and increment retry counter (if needed) - mPolicy.processServerResponse(response, rawData); - - // Given everything we know, including cached data, ask the policy if we should grant - // access. - if (mPolicy.allowAccess()) { - mCallback.allow(response); - } else { - mCallback.dontAllow(response); - } - } - - private void handleApplicationError(int code) { - mCallback.applicationError(code); - } - - private void handleInvalidResponse() { - mCallback.dontAllow(Policy.NOT_LICENSED); - } -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java deleted file mode 100644 index d87af3153f..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/NullDeviceLimiter.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * A DeviceLimiter that doesn't limit the number of devices that can use a - * given user's license. - *

- * Unless you have reason to believe that your application is being pirated - * by multiple users using the same license (signing in to Market as the same - * user), we recommend you use this implementation. - */ -public class NullDeviceLimiter implements DeviceLimiter { - - public int isDeviceAllowed(String userId) { - return Policy.LICENSED; - } -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java deleted file mode 100644 index b5d510d72d..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Obfuscator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Interface used as part of a {@link Policy} to allow application authors to obfuscate - * licensing data that will be stored into a SharedPreferences file. - *

- * Any transformation scheme must be reversable. Implementing classes may optionally implement an - * integrity check to further prevent modification to preference data. Implementing classes - * should use device-specific information as a key in the obfuscation algorithm to prevent - * obfuscated preferences from being shared among devices. - */ -public interface Obfuscator { - - /** - * Obfuscate a string that is being stored into shared preferences. - * - * @param original The data that is to be obfuscated. - * @param key The key for the data that is to be obfuscated. - * @return A transformed version of the original data. - */ - String obfuscate(String original, String key); - - /** - * Undo the transformation applied to data by the obfuscate() method. - * - * @param original The data that is to be obfuscated. - * @param key The key for the data that is to be obfuscated. - * @return A transformed version of the original data. - * @throws ValidationException Optionally thrown if a data integrity check fails. - */ - String unobfuscate(String obfuscated, String key) throws ValidationException; -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java deleted file mode 100644 index fa267fc71a..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/Policy.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Policy used by {@link LicenseChecker} to determine whether a user should have - * access to the application. - */ -public interface Policy { - - /** - * Change these values to make it more difficult for tools to automatically - * strip LVL protection from your APK. - */ - - /** - * LICENSED means that the server returned back a valid license response - */ - public static final int LICENSED = 0x0100; - /** - * NOT_LICENSED means that the server returned back a valid license response - * that indicated that the user definitively is not licensed - */ - public static final int NOT_LICENSED = 0x0231; - /** - * RETRY means that the license response was unable to be determined --- - * perhaps as a result of faulty networking - */ - public static final int RETRY = 0x0123; - - /** - * Provide results from contact with the license server. Retry counts are - * incremented if the current value of response is RETRY. Results will be - * used for any future policy decisions. - * - * @param response the result from validating the server response - * @param rawData the raw server response data, can be null for RETRY - */ - void processServerResponse(int response, ResponseData rawData); - - /** - * Check if the user should be allowed access to the application. - */ - boolean allowAccess(); -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java deleted file mode 100644 index 7c42bfc28a..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/PreferenceObfuscator.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import android.content.SharedPreferences; -import android.util.Log; - -/** - * An wrapper for SharedPreferences that transparently performs data obfuscation. - */ -public class PreferenceObfuscator { - - private static final String TAG = "PreferenceObfuscator"; - - private final SharedPreferences mPreferences; - private final Obfuscator mObfuscator; - private SharedPreferences.Editor mEditor; - - /** - * Constructor. - * - * @param sp A SharedPreferences instance provided by the system. - * @param o The Obfuscator to use when reading or writing data. - */ - public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) { - mPreferences = sp; - mObfuscator = o; - mEditor = null; - } - - public void putString(String key, String value) { - if (mEditor == null) { - mEditor = mPreferences.edit(); - } - String obfuscatedValue = mObfuscator.obfuscate(value, key); - mEditor.putString(key, obfuscatedValue); - } - - public String getString(String key, String defValue) { - String result; - String value = mPreferences.getString(key, null); - if (value != null) { - try { - result = mObfuscator.unobfuscate(value, key); - } catch (ValidationException e) { - // Unable to unobfuscate, data corrupt or tampered - Log.w(TAG, "Validation error while reading preference: " + key); - result = defValue; - } - } else { - // Preference not found - result = defValue; - } - return result; - } - - public void commit() { - if (mEditor != null) { - mEditor.commit(); - mEditor = null; - } - } -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java deleted file mode 100644 index 2adef3709e..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ResponseData.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import java.util.regex.Pattern; - -import android.text.TextUtils; - -/** - * ResponseData from licensing server. - */ -public class ResponseData { - - public int responseCode; - public int nonce; - public String packageName; - public String versionCode; - public String userId; - public long timestamp; - /** Response-specific data. */ - public String extra; - - /** - * Parses response string into ResponseData. - * - * @param responseData response data string - * @throws IllegalArgumentException upon parsing error - * @return ResponseData object - */ - public static ResponseData parse(String responseData) { - // Must parse out main response data and response-specific data. - int index = responseData.indexOf(':'); - String mainData, extraData; - if ( -1 == index ) { - mainData = responseData; - extraData = ""; - } else { - mainData = responseData.substring(0, index); - extraData = index >= responseData.length() ? "" : responseData.substring(index+1); - } - - String [] fields = TextUtils.split(mainData, Pattern.quote("|")); - if (fields.length < 6) { - throw new IllegalArgumentException("Wrong number of fields."); - } - - ResponseData data = new ResponseData(); - data.extra = extraData; - data.responseCode = Integer.parseInt(fields[0]); - data.nonce = Integer.parseInt(fields[1]); - data.packageName = fields[2]; - data.versionCode = fields[3]; - // Application-specific user identifier. - data.userId = fields[4]; - data.timestamp = Long.parseLong(fields[5]); - - return data; - } - - @Override - public String toString() { - return TextUtils.join("|", new Object [] { responseCode, nonce, packageName, versionCode, - userId, timestamp }); - } -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java deleted file mode 100644 index fbf8cf6d00..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ServerManagedPolicy.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - -/** - * Default policy. All policy decisions are based off of response data received - * from the licensing service. Specifically, the licensing server sends the - * following information: response validity period, error retry period, and - * error retry count. - *

- * These values will vary based on the the way the application is configured in - * the Android Market publishing console, such as whether the application is - * marked as free or is within its refund period, as well as how often an - * application is checking with the licensing service. - *

- * Developers who need more fine grained control over their application's - * licensing policy should implement a custom Policy. - */ -public class ServerManagedPolicy implements Policy { - - private static final String TAG = "ServerManagedPolicy"; - private static final String PREFS_FILE = "com.android.vending.licensing.ServerManagedPolicy"; - private static final String PREF_LAST_RESPONSE = "lastResponse"; - private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; - private static final String PREF_RETRY_UNTIL = "retryUntil"; - private static final String PREF_MAX_RETRIES = "maxRetries"; - private static final String PREF_RETRY_COUNT = "retryCount"; - private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; - private static final String DEFAULT_RETRY_UNTIL = "0"; - private static final String DEFAULT_MAX_RETRIES = "0"; - private static final String DEFAULT_RETRY_COUNT = "0"; - - private static final long MILLIS_PER_MINUTE = 60 * 1000; - - private long mValidityTimestamp; - private long mRetryUntil; - private long mMaxRetries; - private long mRetryCount; - private long mLastResponseTime = 0; - private int mLastResponse; - private PreferenceObfuscator mPreferences; - - /** - * @param context The context for the current application - * @param obfuscator An obfuscator to be used with preferences. - */ - public ServerManagedPolicy(Context context, Obfuscator obfuscator) { - // Import old values - SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); - mPreferences = new PreferenceObfuscator(sp, obfuscator); - mLastResponse = Integer.parseInt( - mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); - mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, - DEFAULT_VALIDITY_TIMESTAMP)); - mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); - mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); - mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); - } - - /** - * Process a new response from the license server. - *

- * This data will be used for computing future policy decisions. The - * following parameters are processed: - *

    - *
  • VT: the timestamp that the client should consider the response - * valid until - *
  • GT: the timestamp that the client should ignore retry errors until - *
  • GR: the number of retry errors that the client should ignore - *
- * - * @param response the result from validating the server response - * @param rawData the raw server response data - */ - public void processServerResponse(int response, ResponseData rawData) { - - // Update retry counter - if (response != Policy.RETRY) { - setRetryCount(0); - } else { - setRetryCount(mRetryCount + 1); - } - - if (response == Policy.LICENSED) { - // Update server policy data - Map extras = decodeExtras(rawData.extra); - mLastResponse = response; - setValidityTimestamp(extras.get("VT")); - setRetryUntil(extras.get("GT")); - setMaxRetries(extras.get("GR")); - } else if (response == Policy.NOT_LICENSED) { - // Clear out stale policy data - setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); - setRetryUntil(DEFAULT_RETRY_UNTIL); - setMaxRetries(DEFAULT_MAX_RETRIES); - } - - setLastResponse(response); - mPreferences.commit(); - } - - /** - * Set the last license response received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param l the response - */ - private void setLastResponse(int l) { - mLastResponseTime = System.currentTimeMillis(); - mLastResponse = l; - mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); - } - - /** - * Set the current retry count and add to preferences. You must manually - * call PreferenceObfuscator.commit() to commit these changes to disk. - * - * @param c the new retry count - */ - private void setRetryCount(long c) { - mRetryCount = c; - mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); - } - - public long getRetryCount() { - return mRetryCount; - } - - /** - * Set the last validity timestamp (VT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param validityTimestamp the VT string received - */ - private void setValidityTimestamp(String validityTimestamp) { - Long lValidityTimestamp; - try { - lValidityTimestamp = Long.parseLong(validityTimestamp); - } catch (NumberFormatException e) { - // No response or not parsable, expire in one minute. - Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); - lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; - validityTimestamp = Long.toString(lValidityTimestamp); - } - - mValidityTimestamp = lValidityTimestamp; - mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); - } - - public long getValidityTimestamp() { - return mValidityTimestamp; - } - - /** - * Set the retry until timestamp (GT) received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param retryUntil the GT string received - */ - private void setRetryUntil(String retryUntil) { - Long lRetryUntil; - try { - lRetryUntil = Long.parseLong(retryUntil); - } catch (NumberFormatException e) { - // No response or not parsable, expire immediately - Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); - retryUntil = "0"; - lRetryUntil = 0l; - } - - mRetryUntil = lRetryUntil; - mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); - } - - public long getRetryUntil() { - return mRetryUntil; - } - - /** - * Set the max retries value (GR) as received from the server and add to - * preferences. You must manually call PreferenceObfuscator.commit() to - * commit these changes to disk. - * - * @param maxRetries the GR string received - */ - private void setMaxRetries(String maxRetries) { - Long lMaxRetries; - try { - lMaxRetries = Long.parseLong(maxRetries); - } catch (NumberFormatException e) { - // No response or not parsable, expire immediately - Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); - maxRetries = "0"; - lMaxRetries = 0l; - } - - mMaxRetries = lMaxRetries; - mPreferences.putString(PREF_MAX_RETRIES, maxRetries); - } - - public long getMaxRetries() { - return mMaxRetries; - } - - /** - * {@inheritDoc} - * - * This implementation allows access if either:
- *
    - *
  1. a LICENSED response was received within the validity period - *
  2. a RETRY response was received in the last minute, and we are under - * the RETRY count or in the RETRY period. - *
- */ - public boolean allowAccess() { - long ts = System.currentTimeMillis(); - if (mLastResponse == Policy.LICENSED) { - // Check if the LICENSED response occurred within the validity timeout. - if (ts <= mValidityTimestamp) { - // Cached LICENSED response is still valid. - return true; - } - } else if (mLastResponse == Policy.RETRY && - ts < mLastResponseTime + MILLIS_PER_MINUTE) { - // Only allow access if we are within the retry period or we haven't used up our - // max retries. - return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); - } - return false; - } - - private Map decodeExtras(String extras) { - Map results = new HashMap(); - try { - URI rawExtras = new URI("?" + extras); - List extraList = URLEncodedUtils.parse(rawExtras, "UTF-8"); - for (NameValuePair item : extraList) { - results.put(item.getName(), item.getValue()); - } - } catch (URISyntaxException e) { - Log.w(TAG, "Invalid syntax error while decoding extras data from server."); - } - return results; - } - -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java deleted file mode 100644 index d8d83b4e4b..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/StrictPolicy.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Non-caching policy. All requests will be sent to the licensing service, - * and no local caching is performed. - *

- * Using a non-caching policy ensures that there is no local preference data - * for malicious users to tamper with. As a side effect, applications - * will not be permitted to run while offline. Developers should carefully - * weigh the risks of using this Policy over one which implements caching, - * such as ServerManagedPolicy. - *

- * Access to the application is only allowed if a LICESNED response is. - * received. All other responses (including RETRY) will deny access. - */ -public class StrictPolicy implements Policy { - - private int mLastResponse; - - public StrictPolicy() { - // Set default policy. This will force the application to check the policy on launch. - mLastResponse = Policy.RETRY; - } - - /** - * Process a new response from the license server. Since we aren't - * performing any caching, this equates to reading the LicenseResponse. - * Any ResponseData provided is ignored. - * - * @param response the result from validating the server response - * @param rawData the raw server response data - */ - public void processServerResponse(int response, ResponseData rawData) { - mLastResponse = response; - } - - /** - * {@inheritDoc} - * - * This implementation allows access if and only if a LICENSED response - * was received the last time the server was contacted. - */ - public boolean allowAccess() { - return (mLastResponse == Policy.LICENSED); - } - -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java deleted file mode 100644 index ee4df47c68..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/ValidationException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.vending.licensing; - -/** - * Indicates that an error occurred while validating the integrity of data managed by an - * {@link Obfuscator}.} - */ -public class ValidationException extends Exception { - public ValidationException() { - super(); - } - - public ValidationException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java deleted file mode 100644 index a0d2779af2..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64.java +++ /dev/null @@ -1,570 +0,0 @@ -// Portions copyright 2002, Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.android.vending.licensing.util; - -// This code was converted from code at http://iharder.sourceforge.net/base64/ -// Lots of extraneous features were removed. -/* The original code said: - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit - * http://iharder.net/xmlizable - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rharder@usa.net - * @version 1.3 - */ - -/** - * Base64 converter class. This code is not a full-blown MIME encoder; - * it simply converts binary data to base64 data and back. - * - *

Note {@link CharBase64} is a GWT-compatible implementation of this - * class. - */ -public class Base64 { - /** Specify encoding (value is {@code true}). */ - public final static boolean ENCODE = true; - - /** Specify decoding (value is {@code false}). */ - public final static boolean DECODE = false; - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte) '='; - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte) '\n'; - - /** - * The 64 valid Base64 values. - */ - private final static byte[] ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '+', (byte) '/'}; - - /** - * The 64 valid web safe Base64 values. - */ - private final static byte[] WEBSAFE_ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '-', (byte) '_'}; - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - /** The web safe decodabet */ - private final static byte[] WEBSAFE_DECODABET = - {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 - 62, // Dash '-' sign at decimal 45 - -9, -9, // Decimal 46-47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91-94 - 63, // Underscore '_' at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - // Indicates white space in encoding - private final static byte WHITE_SPACE_ENC = -5; - // Indicates equals sign in encoding - private final static byte EQUALS_SIGN_ENC = -1; - - /** Defeats instantiation. */ - private Base64() { - } - - /* ******** E N C O D I N G M E T H O D S ******** */ - - /** - * Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param alphabet is the encoding alphabet - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4(byte[] source, int srcOffset, - int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index alphabet - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = - (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; - return destination; - case 2: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - case 1: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - default: - return destination; - } // end switch - } // end encode3to4 - - /** - * Encodes a byte array into Base64 notation. - * Equivalent to calling - * {@code encodeBytes(source, 0, source.length)} - * - * @param source The data to convert - * @since 1.4 - */ - public static String encode(byte[] source) { - return encode(source, 0, source.length, ALPHABET, true); - } - - /** - * Encodes a byte array into web safe Base64 notation. - * - * @param source The data to convert - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - */ - public static String encodeWebSafe(byte[] source, boolean doPadding) { - return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param alphabet is the encoding alphabet - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - * @since 1.4 - */ - public static String encode(byte[] source, int off, int len, byte[] alphabet, - boolean doPadding) { - byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); - int outLen = outBuff.length; - - // If doPadding is false, set length to truncate '=' - // padding characters - while (doPadding == false && outLen > 0) { - if (outBuff[outLen - 1] != '=') { - break; - } - outLen -= 1; - } - - return new String(outBuff, 0, outLen); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param alphabet is the encoding alphabet - * @param maxLineLength maximum length of one line. - * @return the BASE64-encoded byte array - */ - public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, - int maxLineLength) { - int lenDiv3 = (len + 2) / 3; // ceil(len / 3) - int len43 = lenDiv3 * 4; - byte[] outBuff = new byte[len43 // Main 4:3 - + (len43 / maxLineLength)]; // New lines - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - - // The following block of code is the same as - // encode3to4( source, d + off, 3, outBuff, e, alphabet ); - // but inlined for faster encoding (~20% improvement) - int inBuff = - ((source[d + off] << 24) >>> 8) - | ((source[d + 1 + off] << 24) >>> 16) - | ((source[d + 2 + off] << 24) >>> 24); - outBuff[e] = alphabet[(inBuff >>> 18)]; - outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; - - lineLength += 4; - if (lineLength == maxLineLength) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // end for: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e, alphabet); - - lineLength += 4; - if (lineLength == maxLineLength) { - // Add a last newline - outBuff[e + 4] = NEW_LINE; - e++; - } - e += 4; - } - - assert (e == outBuff.length); - return outBuff; - } - - - /* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param decodabet the decodabet for decoding Base64 content - * @return the number of decoded bytes converted - * @since 1.3 - */ - private static int decode4to3(byte[] source, int srcOffset, - byte[] destination, int destOffset, byte[] decodabet) { - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Example: DkL= - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } else { - // Example: DkLE - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) - | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - return 3; - } - } // end decodeToBytes - - - /** - * Decodes data from Base64 notation. - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - * @since 1.4 - */ - public static byte[] decode(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decode(bytes, 0, bytes.length); - } - - /** - * Decodes data from web safe Base64 notation. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decodeWebSafe(bytes, 0, bytes.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source) throws Base64DecoderException { - return decode(source, 0, source.length); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded data. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(byte[] source) - throws Base64DecoderException { - return decodeWebSafe(source, 0, source.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, DECODABET); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded byte array. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - */ - public static byte[] decodeWebSafe(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, WEBSAFE_DECODABET); - } - - /** - * Decodes Base64 content using the supplied decodabet and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param decodabet the decodabet for decoding Base64 content - * @return decoded data - */ - public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) - throws Base64DecoderException { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for (i = 0; i < len; i++) { - sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits - sbiDecode = decodabet[sbiCrop]; - - if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better - if (sbiDecode >= EQUALS_SIGN_ENC) { - // An equals sign (for padding) must not occur at position 0 or 1 - // and must be the last byte[s] in the encoded value - if (sbiCrop == EQUALS_SIGN) { - int bytesLeft = len - i; - byte lastByte = (byte) (source[len - 1 + off] & 0x7f); - if (b4Posn == 0 || b4Posn == 1) { - throw new Base64DecoderException( - "invalid padding byte '=' at byte offset " + i); - } else if ((b4Posn == 3 && bytesLeft > 2) - || (b4Posn == 4 && bytesLeft > 1)) { - throw new Base64DecoderException( - "padding byte '=' falsely signals end of encoded value " - + "at offset " + i); - } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { - throw new Base64DecoderException( - "encoded value has invalid trailing byte"); - } - break; - } - - b4[b4Posn++] = sbiCrop; - if (b4Posn == 4) { - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - b4Posn = 0; - } - } - } else { - throw new Base64DecoderException("Bad Base64 input character at " + i - + ": " + source[i + off] + "(decimal)"); - } - } - - // Because web safe encoding allows non padding base64 encodes, we - // need to pad the rest of the b4 buffer with equal signs when - // b4Posn != 0. There can be at most 2 equal signs at the end of - // four characters, so the b4 buffer must have two or three - // characters. This also catches the case where the input is - // padded with EQUALS_SIGN - if (b4Posn != 0) { - if (b4Posn == 1) { - throw new Base64DecoderException("single trailing character at offset " - + (len - 1)); - } - b4[b4Posn++] = EQUALS_SIGN; - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - } - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } -} diff --git a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java b/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java deleted file mode 100644 index 1aef1b54b8..0000000000 --- a/platform/android/libs/play_licensing/src/com/google/android/vending/licensing/util/Base64DecoderException.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2002, Google, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.android.vending.licensing.util; - -/** - * Exception thrown when encountering an invalid Base64 input character. - * - * @author nelson - */ -public class Base64DecoderException extends Exception { - public Base64DecoderException() { - super(); - } - - public Base64DecoderException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} diff --git a/platform/android/project.properties.template b/platform/android/project.properties.template deleted file mode 100644 index 00cacd72bc..0000000000 --- a/platform/android/project.properties.template +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -#android.library=true -target=android-19 -- cgit v1.2.3