diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..564a3ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.gradle +.DS_Store +.idea +build/ +local.properties +localhost/ +obj/ +*.iml +Gemfile.lock +_site/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..86c559a --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# Android Stickers Apps for WhatsApp + +## Overview + +If you would like to design your own stickers for WhatsApp, you can package them in an Android app. You will need to distribute your app via the Google Play Store or another mechanism. Users who download and install your sticker app will be able to add your stickers to their WhatsApp sticker picker/tray, and start sending those stickers from within WhatsApp. A separate app is necessary and it will reside on your phone's home screen just like any other app. Stickers on WhatsApp must be legal, authorized, and acceptable. Learn more about acceptable use of our services at https://www.whatsapp.com/legal/#terms-of-service. + +The sample code provides a simple way for you to drop in your sticker art and build an Android app with minimal development or coding experience needed. For advanced developers looking to make richer sticker apps, refer to the section [Advanced Development](#advanced-development) below. + +We recommend you create a version of your sticker app for iOS as well to give users of WhatsApp on iOS an opportunity to download your sticker app as well. +## Sticker art and app requirements +We recommend you refer to the FAQ at https://faq.whatsapp.com/general/26000226 for complete details on how to properly create your sticker art. This FAQ also contains a sample PSD that demonstrates best practices for composing legible, rich sticker art. + +* A sticker is an image that has a transparent background and can be sent in a + WhatsApp chat +* Stickers are organized into "packs". Your app can contain anywhere from 1 to + 10 packs. Users must explicitly add each pack to WhatsApp one-by-one, so your + app should list each pack separately and each pack must have its own + affordance to add it to WhatsApp (do not try to create "add all packs" + operations). +* Each sticker pack must have a minimum of 3 stickers and a maximum of 30 + stickers +* Stickers must be exactly 512 x 512 pixels +* Stickers will render on a variety of backgrounds, white, black, colored, patterned, etc. Test your sticker art on a variety of backgrounds. For this reason, we recommend you add a 8px #FFFFFF stroke to the outside of each sticker. See the sample PSD referenced at https://faq.whatsapp.com/general/26000226 for more details. +* Stickers must be in the [WebP format](https://developers.google.com/speed/webp). Currently, animated WebP or animated stickers are not supported. See the section [Converting to WebP](#converting-to-webp) below for information on how to create WebP files. +* Each sticker must be less than 100KB. See the section [Tips for Reducing File Size](#tips-for-reducing-file-size) below. +* Sticker Picker/Tray Icon + * Provide an image that will be used to represent your sticker pack in the WhatsApp sticker picker/tray + * This image should be 96 x 96 pixels + * Max file size of 50KB + +### Tips for reducing file size +We recommend reducing the size of each of your stickers. For reference, many of the stickers provided within WhatsApp are around 15KB each. The smaller your stickers, the faster they will send and the less data your users will have to spend sending them or downloading your app. Depending on your art and the type of graphics you've created, one of these two methods may result in smaller file sizes so we recommend experimenting with both. + +* The first method involves optimizing your PNGs using a PNG optimizer tool. If you're on MacOS, use https://pngmini.com. We recommend using Median Cut and adjusting the colors bar to reduce the size. If you're on Windows, use https://nukesaq88.github.io/Pngyu to optimize your PNGs. Then, convert them to WebP using the methods described in the [Converting to WebP](#converting-to-webp) section. +* The second method involves saving or converting your stickers as WebP while experimenting with the WebP export settings trying to optimize the images. You should try setting the quality of your WebP output to something lower than 100% and experiment with a quality level that gets you the smallest file size possible without noticeable image degradation. Each of the methods described in [Converting to WebP](#converting-to-webp) for exporting your files to WebP offer ways to control your resolution. + +### Converting to WebP +Your sticker art must be in the WebP format. We recommend using the tools you're most comfortable with to draw and compose your art, and converting them to WebP using one of a few different tools: + +* Sketch for Mac lets you export files as WebP. Open your sticker art file in Sketch, select a layer, multiple layers, or an artboard, and select Make Exportable in the bottom right. Pick your format as WebP, select Export, and then select the quality/resolution. +* [Android Studio](https://developer.android.com/studio/) allows you to convert PNGs to WebP. Simply create a new project in Android Studio, open your PNG and right click on the image and select convert to WebP ([https://developer.android.com/studio/write/convert-webp](https://developer.android.com/studio/write/convert-webp)). Make sure you uncheck the box next to "Skip images with transparency/alpha channel" in the export flow. +* You can install a [plugin](https://github.com/fnordware/AdobeWebM#download) for Photoshop that converts to WebP +* Use [cwebp](https://developers.google.com/speed/webp/), a command line tool + +## How to create a sticker app + +### Overview +If you would like to create a sticker app using the sample app, you only have to minimally modify the sample app to get up and running quickly. + +* After downloading this repo, open the entire folder for the sample app in [Android Studio](https://developer.android.com/studio/). If you are new to Android development, visit https://developer.android.com/training/basics/firstapp/index.html for more information on setting up your Android development environment. +* Navigate to SampleStickerApp/app/src/main/assets in Android Studio. +* Inside the assets folder, folder 1 contains a number of sample sticker art files. Replace these with your own sticker files. +* Also replace the sample tray icon PNG with your own tray icon. +* If you'd like to have more than 1 sticker pack in your app, simply create a folder named "2" or "3", etc. within the assets folder and place your art and tray icon in there. + +### Modifying the contents.json file +You must also modify the contents.json file in SampleStickerApp/app/src/main/assets. Replace the values of the metadata with your own. A few notes: + +* `name`: the sticker pack's name (128 characters max) +* `identifier`: The identifier should be unique and can be alphanumeric: a-z, A-Z, 0-9, and the following characters are also allowed "_", "-", "." and " ". The identifier should be less than 128 characters. +* `publisher`: name of the publisher of the pack (128 characters max) +* Replace the "image_file" value with the file name of your sticker image. It must have both the file name and extension. The ordering of the files in the JSON will dictate the ordering of your stickers in your pack. +* `android_play_store_link` and `ios_app_store_link` (optional fields): here you can put the URL to your sticker app in the Google Play Store and Apple App Store (if you have an iOS version of your sticker app). If you provide these URLs, users who receive a sticker from your app in WhatsApp can tap on it to view your sticker app in the respective App Stores. On Android, the URL follows the format https://play.google.com/store/apps/details?id=com.example where "com.example" is your app's package name. +* `emoji` (optional): add up to a maximum of three emoji for each sticker file. Select emoji that best describe or represent that sticker file. For example, if the sticker is portraying love, you may choose to add a heart emoji like 💕. If your sticker portrays pizza, you may want to add the pizza slice emoji 🍕. In the future, WhatsApp will support a search function for stickers and tagging your sticker files with emoji will enable that. The sticker picker/tray in WhatsApp today already categorizes stickers into emotion categories (love, happy, sad, and angry) and it does this based on the emoji you tag your stickers with. + +The following fields are optional: `ios_app_store_link`, `android_app_store_link`, `publisher_website`, `privacy_policy_website`, `license_agreement_website`, `emoji` + +If your app has more than 1 sticker pack, you will need to reference it in contents.json. Simply create a second array within the "sticker_packs" section of the JSON and include all the metadata (name, identifier, etc) along with all the references to the sticker files. + +### Build the sample app +Before building your app, you will need to do the following: + +* Make sure to change the app's icon (i.e. launcher icon) that will be displayed on the home screens of users who install your app. The icons are contained in SampleStickerApp/app/src/main/res in each of the folders beginning with mipmap (e.g. mipmap-xhdpi or mipmap-xxxhdpi). For a simple way to create these icons, you can use Android Image Asset Studio which is built into Android Studio. See https://developer.android.com/studio/write/image-asset-studio#access for more information on how to run this tool and read the section [here](https://developer.android.com/studio/write/image-asset-studio#create-adaptive) for information on how to use the tool to create your app's launcher icons. +* Change your apps name in strings.xml (SampleStickerApp/app/src/main/res/values/strings.xml). This is the name users will see for your app on their phone. +* In addition, the application id (e.g. com.whatsapp) need to be changed. Note that you need to specify a unique application id that does not exist in play store. For more information on how to set your application ID, visit https://developer.android.com/studio/build/application-id. +* Change the applicationId in build.gradle (SampleStickerApp/app/build.gradle) +* For developers that are familiar with package name, you can change the package name, but it is not required. The package name will not be visible once the app is built. + +Make sure to run and test your sticker app. For help on building your app, visit https://developer.android.com/studio/run/. The app will run some checks. If there are problems, you will see the error in [logcat](https://developer.android.com/studio/debug/am-logcat.html). If there are no errors, the app will launch successfully displaying the sticker packs you have included. + +## Submit your app +You need to build a release version of your app for submission to the Google Play Store. Click Build > Generate Signed Bundle/APK. For more information, visit https://developer.android.com/studio/publish/app-signing#sign-apk. Note that Android Studio saves the APKs you build in project-name/module-name/build/outputs/apk. For more information on building your app, visit https://developer.android.com/studio/run/. + +Importantly, when naming your app, it is strongly advised you do *not* use "WhatsApp" anywhere in the name of your app or in the name field of your app listing on the Google Play Store. However, when preparing your app for submission in Google Play Store, you'll have the option to add description associated with your app and it's okay to mention WhatsApp in the description. WhatsApp can also launch the Google Play Store and perform a search for other sticker pack apps. To help your app appear in this list, also add the keyword WAStickerApps to app's description when setting up your app in the Google Play Store console. You can use additional keywords, but make sure you at least use this one. + +To submit your app to the Google Play Store, follow the instructions here: https://developer.android.com/distribute/best-practices/launch/. + +## Advanced development +For advanced developers looking to make richer sticker apps, follow the instructions below. + +### Overview + +Sticker apps communicate with WhatsApp as follows: + +* Your app should provide a `ContentProvider` (the sample app provides an example) to share the sticker pack information to WhatsApp. The `ContentProvider` shares information about the sticker pack's name, publisher, identifier and everything else that is listed in `contents.json` file. It also allows WhatsApp to fetch actual sticker files from the `ContentProvider`. The `ContentProvider` is identified by its authority. And a sticker pack is identified by the combination of the authority and identifier. +* Your app should send an intent to launch WhatsApp's activity. The intent contains three pieces of information: the `ContentProvider` authority, the pack identifier of the pack that user wants to add, and the sticker pack name. Once the user confirms that they want to add that sticker pack to WhatsApp, WhatsApp will remember the pair of authority and identifier and will load the pack's stickers in the WhatsApp sticker picker/tray. + +### ContentProvider +The ContentProvider in the sample app is [StickerContentProvider](app/src/main/java/com/example/samplestickerapp/StickerContentProvider.java). The ContentProvider provides 4 APIs: + +1. `/metadata`, this returns information about all the sticker packs in your app. Replace `` with the actual authority string. In the sample app, it is `com.geekofia.whatsappstickers.stickercontentprovider` +2. `/metadata/`, this returns information about a single pack. Replace `` with the actual identifier of the pack. In the sample app, it is `1`. +3. `/stickers/`, this returns information about the stickers in a pack. The returned information includes the sticker file name and emoji associated with the sticker. +4. `/stickers_asset//`, this returns the binary information of the sticker: `AssetFileDescriptor`, which points to the asset file for the sticker. Replace `` with the actual sticker file name that should be fetched. + +The ContentProvider needs to have a read permission of `com.whatsapp.sticker.READ` in `AndroidManifest.xml`. It also needs to be exported and enabled. See below for an example: + + +### Intent +It is required that users explicitly add a sticker pack to WhatsApp, so your app must provide a UI element to allow users to add a pack (for example, a button labeled "Add to WhatsApp" as in the sample app). +In the sample app, both [StickerPackListActivity](app/src/main/java/com/example/samplestickerapp/StickerPackListActivity.java) and [StickerPackDetailsActivity](app/src/main/java/com/example/samplestickerapp/StickerPackDetailsActivity.java) contains code to launch the intent after the user presses the Add to WhatsApp button. The user must then confirm they want to add the pack via the alert box presented by WhatsApp. + + Intent intent = new Intent(); + intent.setAction("com.whatsapp.intent.action.ENABLE_STICKER_PACK"); + intent.putExtra("sticker_pack_id", identifier); //identifier is the pack's identifier in contents.json file + intent.putExtra("sticker_pack_authority", authority); //authority is the ContentProvider's authority. In the case of the sample app it is BuildConfig.CONTENT_PROVIDER_AUTHORITY. + intent.putExtra("sticker_pack_name", stickerPackName); //stickerPackName is the name of the sticker pack. + try { + startActivityForResult(intent, 200); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.error_adding_sticker_pack, Toast.LENGTH_LONG).show(); + } + +### Check if pack is added (optional) +Sticker apps can check to see if a sticker pack it provides has been added to WhatsApp by the user. This is useful if you'd like to display different UI to users. For example if the pack is not added, you should present a button to add it to WhatsApp, but if the pack is added, you can remove the add button and inform the user the pack is already added to WhatsApp. + +A `ContentProvider` provides information to sticker apps on whether an app is added to WhatsApp or not. This provider authority is `com.whatsapp.provider.sticker_whitelist_check` for the WhatsApp consumer app; `com.whatsapp.w4b.provider.sticker_whitelist_check` for the WhatsApp Business app. + +In order to query this ContentProvider, you need to provide the following query: +`content://com.whatsapp.provider.sticker_whitelist_check/is_whitelisted?authority='replace with authority of your sticker content provider'&identifier='replace with identifier of the pack'` + +The result is in a row corresponding to the column named `result`. The value will be either `0`, meaning the pack is not added to WhatsApp, or `1`, meaning the pack has been added. If the returned result is null, the query was invalid or the version of WhatsApp installed by the user is too old to support stickers. See the class in the sample app: [WhitelistCheck](app/src/main/java/com/example/samplestickerapp/WhitelistCheck.java). This class provides ways to do the query. You can call `WhitelistCheck.isWhitelisted(Context context, String identifier)`. The identifier should match the identifier of the pack you want to query. The authority is automatically filled in as the authority of your sticker app's ContentProvider. + +Note that a pack can be added to either the main WhatsApp app, WhatsApp Business, or both. It is recommended you continue to present a button to add the pack to WhatsApp if the sticker pack is not added to one or more of the apps. Refer to the class [WhitelistCheck](app/src/main/java/com/example/samplestickerapp/WhitelistCheck.java) for sample logic. + +Your app can only query whether the packs it provides have been added and it can't check for information about sticker packs from other apps. \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..ed70e9f --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,104 @@ +apply plugin: 'com.android.application' + +android { + //the compression of webp file during build causes problem with FileDescriptor in ContentProvider. + aaptOptions { + noCompress "webp" + } + signingConfigs { + signing_config { + keyAlias 'abc' + keyPassword '123' + } + } + compileSdkVersion 28 + defaultConfig { + applicationId "com.geekofia.whatsappstickers" + minSdkVersion 15 + targetSdkVersion 28 + versionCode 1 + versionName "1.2" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + def contentProviderAuthority = applicationId + ".stickercontentprovider" + // Creates a placeholder property to use in the manifest. + manifestPlaceholders = + [contentProviderAuthority: contentProviderAuthority] + // Adds a new field for the authority to the BuildConfig class. + buildConfigField("String", + "CONTENT_PROVIDER_AUTHORITY", + "\"${contentProviderAuthority}\"") + } + buildTypes { + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +task checkDebug { + doLast { + println("checkDebug") + if (android.defaultConfig.applicationId.startsWith("com.whatsapp")) { + throw new GradleException("applicationId in defaultConfig cannot start with com.whatsapp, please change your applicationId in app/build.gradle"); + } + checkApplicationIdInDebug() + } +} + +private void checkApplicationIdInDebug() { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + def ignoreApplicationIdCheck = properties.getProperty('ignoreApplicationIdCheck') + if (ignoreApplicationIdCheck == null) { + if (android.defaultConfig.applicationId.equals("com.example.samplestickerapp")) { + throw new GradleException("Your applicationId is currently com.example.samplestickerapp, please change your applicationId to a different string in app/build.gradle in line 16"); + } + } else { + println("application id check ignored") + } +} + + +task checkRelease { + doLast { + println("checkRelease") + if (android.defaultConfig.applicationId.startsWith("com.example")) { + throw new GradleException("applicationId in defaultConfig cannot start with com.example, please change your applicationId in app/build.gradle"); + } + } +} + +tasks.whenTaskAdded { task -> + println(task.name) + if (task.name.contains("assembleDebug")) { + task.dependsOn checkDebug + } + if (task.name.contains("assembleRelease")) { + task.dependsOn checkRelease + } +} + + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.android.support:recyclerview-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.facebook.fresco:fresco:1.10.0' + implementation 'com.facebook.fresco:webpsupport:1.10.0' + implementation 'com.facebook.fresco:animated-webp:1.10.0' + implementation 'com.facebook.fresco:webpsupport:1.10.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# 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 *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2940897 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/animals/01_animals.webp b/app/src/main/assets/animals/01_animals.webp new file mode 100644 index 0000000..601edb4 Binary files /dev/null and b/app/src/main/assets/animals/01_animals.webp differ diff --git a/app/src/main/assets/animals/02_animals.webp b/app/src/main/assets/animals/02_animals.webp new file mode 100644 index 0000000..770bcc3 Binary files /dev/null and b/app/src/main/assets/animals/02_animals.webp differ diff --git a/app/src/main/assets/animals/03_animals.webp b/app/src/main/assets/animals/03_animals.webp new file mode 100644 index 0000000..9ede56c Binary files /dev/null and b/app/src/main/assets/animals/03_animals.webp differ diff --git a/app/src/main/assets/animals/04_animals.webp b/app/src/main/assets/animals/04_animals.webp new file mode 100644 index 0000000..7e0de8e Binary files /dev/null and b/app/src/main/assets/animals/04_animals.webp differ diff --git a/app/src/main/assets/animals/05_animals.webp b/app/src/main/assets/animals/05_animals.webp new file mode 100644 index 0000000..91de6b9 Binary files /dev/null and b/app/src/main/assets/animals/05_animals.webp differ diff --git a/app/src/main/assets/animals/06_animals.webp b/app/src/main/assets/animals/06_animals.webp new file mode 100644 index 0000000..fe291ab Binary files /dev/null and b/app/src/main/assets/animals/06_animals.webp differ diff --git a/app/src/main/assets/animals/07_animals.webp b/app/src/main/assets/animals/07_animals.webp new file mode 100644 index 0000000..3b8a4ed Binary files /dev/null and b/app/src/main/assets/animals/07_animals.webp differ diff --git a/app/src/main/assets/animals/08_animals.webp b/app/src/main/assets/animals/08_animals.webp new file mode 100644 index 0000000..4159bf9 Binary files /dev/null and b/app/src/main/assets/animals/08_animals.webp differ diff --git a/app/src/main/assets/animals/09_animals.webp b/app/src/main/assets/animals/09_animals.webp new file mode 100644 index 0000000..287ec10 Binary files /dev/null and b/app/src/main/assets/animals/09_animals.webp differ diff --git a/app/src/main/assets/animals/10_animals.webp b/app/src/main/assets/animals/10_animals.webp new file mode 100644 index 0000000..287869d Binary files /dev/null and b/app/src/main/assets/animals/10_animals.webp differ diff --git a/app/src/main/assets/animals/11_animals.webp b/app/src/main/assets/animals/11_animals.webp new file mode 100644 index 0000000..40af9ff Binary files /dev/null and b/app/src/main/assets/animals/11_animals.webp differ diff --git a/app/src/main/assets/animals/12_animals.webp b/app/src/main/assets/animals/12_animals.webp new file mode 100644 index 0000000..9ecf05d Binary files /dev/null and b/app/src/main/assets/animals/12_animals.webp differ diff --git a/app/src/main/assets/animals/13_animals.webp b/app/src/main/assets/animals/13_animals.webp new file mode 100644 index 0000000..eea99c8 Binary files /dev/null and b/app/src/main/assets/animals/13_animals.webp differ diff --git a/app/src/main/assets/animals/14_animals.webp b/app/src/main/assets/animals/14_animals.webp new file mode 100644 index 0000000..94f97d4 Binary files /dev/null and b/app/src/main/assets/animals/14_animals.webp differ diff --git a/app/src/main/assets/animals/15_animals.webp b/app/src/main/assets/animals/15_animals.webp new file mode 100644 index 0000000..fc35acb Binary files /dev/null and b/app/src/main/assets/animals/15_animals.webp differ diff --git a/app/src/main/assets/animals/16_animals.webp b/app/src/main/assets/animals/16_animals.webp new file mode 100644 index 0000000..038348d Binary files /dev/null and b/app/src/main/assets/animals/16_animals.webp differ diff --git a/app/src/main/assets/animals/17_animals.webp b/app/src/main/assets/animals/17_animals.webp new file mode 100644 index 0000000..3d8f0fa Binary files /dev/null and b/app/src/main/assets/animals/17_animals.webp differ diff --git a/app/src/main/assets/animals/18_animals.webp b/app/src/main/assets/animals/18_animals.webp new file mode 100644 index 0000000..062f84f Binary files /dev/null and b/app/src/main/assets/animals/18_animals.webp differ diff --git a/app/src/main/assets/animals/19_animals.webp b/app/src/main/assets/animals/19_animals.webp new file mode 100644 index 0000000..b1b3468 Binary files /dev/null and b/app/src/main/assets/animals/19_animals.webp differ diff --git a/app/src/main/assets/animals/20_animals.webp b/app/src/main/assets/animals/20_animals.webp new file mode 100644 index 0000000..9565988 Binary files /dev/null and b/app/src/main/assets/animals/20_animals.webp differ diff --git a/app/src/main/assets/animals/tray_Animals.png b/app/src/main/assets/animals/tray_Animals.png new file mode 100644 index 0000000..47e7306 Binary files /dev/null and b/app/src/main/assets/animals/tray_Animals.png differ diff --git a/app/src/main/assets/contents.json b/app/src/main/assets/contents.json new file mode 100644 index 0000000..eb3eee5 --- /dev/null +++ b/app/src/main/assets/contents.json @@ -0,0 +1,439 @@ +{ + "android_play_store_link": "", + "ios_app_store_link": "", + "sticker_packs": [ + { + "identifier": "animals", + "name": "Animals", + "publisher": "Chandan Kumar Mandal", + "tray_image_file": "tray_Animals.png", + "publisher_email":"chankruze@gmail.com", + "publisher_website": "https://geekofia.wordpress.com", + "privacy_policy_website": "", + "license_agreement_website": "", + "stickers": [ + { + "image_file": "01_animals.webp" + }, + { + "image_file": "02_animals.webp" + }, + { + "image_file": "03_animals.webp" + }, + { + "image_file": "04_animals.webp" + }, + { + "image_file": "05_animals.webp" + }, + { + "image_file": "06_animals.webp" + }, + { + "image_file": "07_animals.webp" + }, + { + "image_file": "08_animals.webp" + }, + { + "image_file": "09_animals.webp" + }, + { + "image_file": "10_animals.webp" + }, + { + "image_file": "11_animals.webp" + }, + { + "image_file": "12_animals.webp" + }, + { + "image_file": "13_animals.webp" + }, + { + "image_file": "14_animals.webp" + }, + { + "image_file": "15_animals.webp" + }, + { + "image_file": "16_animals.webp" + }, + { + "image_file": "17_animals.webp" + }, + { + "image_file": "18_animals.webp" + }, + { + "image_file": "19_animals.webp" + }, + { + "image_file": "20_animals.webp" + } + ] + }, + { + "identifier": "cups", + "name": "Cuppy", + "publisher": "Jane Doe", + "tray_image_file": "tray_Cuppy.png", + "publisher_email":"", + "publisher_website": "", + "privacy_policy_website": "", + "license_agreement_website": "", + "stickers": [ + { + "image_file": "01_Cuppy_smile.webp", + "emojis": ["☕","🙂"] + }, + { + "image_file": "02_Cuppy_lol.webp", + "emojis": ["😄","😀"] + }, + { + "image_file": "03_Cuppy_rofl.webp", + "emojis": ["😆","😂"] + }, + { + "image_file": "04_Cuppy_sad.webp", + "emojis": ["😩","😰"] + }, + { + "image_file": "05_Cuppy_cry.webp", + "emojis": ["😭","💧"] + }, + { + "image_file": "06_Cuppy_love.webp", + "emojis": ["😍","♥"] + }, + { + "image_file": "07_Cuppy_hate.webp", + "emojis": ["💔","👎"] + }, + { + "image_file": "08_Cuppy_lovewithmug.webp", + "emojis": ["😍","💑"] + }, + { + "image_file": "09_Cuppy_lovewithcookie.webp", + "emojis": ["😘","🍪"] + }, + { + "image_file": "10_Cuppy_hmm.webp", + "emojis": ["🤔","😐"] + }, + { + "image_file": "11_Cuppy_upset.webp", + "emojis": ["😱","😵"] + }, + { + "image_file": "12_Cuppy_angry.webp", + "emojis": ["😡","😠"] + }, + { + "image_file": "13_Cuppy_curious.webp", + "emojis": ["❓","🤔"] + }, + { + "image_file": "14_Cuppy_weird.webp", + "emojis": ["🌈","😜"] + }, + { + "image_file": "15_Cuppy_bluescreen.webp", + "emojis": ["💻","😩"] + }, + { + "image_file": "16_Cuppy_angry.webp", + "emojis": ["😡","😤"] + }, + { + "image_file": "17_Cuppy_tired.webp", + "emojis": ["😩","😨"] + }, + { + "image_file": "18_Cuppy_workhard.webp", + "emojis": ["💻","🌃"] + }, + { + "image_file": "19_Cuppy_shine.webp", + "emojis": ["🎉","✨"] + }, + { + "image_file": "20_Cuppy_disgusting.webp", + "emojis": ["🤮","👎"] + }, + { + "image_file": "21_Cuppy_hi.webp", + "emojis": ["🖐","🙋"] + }, + { + "image_file": "22_Cuppy_bye.webp", + "emojis": ["🖐","👋"] + }, + { + "image_file": "23_Cuppy_greentea.webp", + "emojis": ["🍵","😌"] + }, + { + "image_file": "24_Cuppy_phone.webp", + "emojis": ["📱","😦"] + }, + { + "image_file": "25_Cuppy_battery.webp", + "emojis": ["🔋","😵"] + } + ] + }, + { + "identifier": "great_mind", + "name": "Great Mind", + "publisher": "Chandan Kumar Mandal", + "tray_image_file": "tray_Great_Mind.png", + "publisher_email":"chankruze@gmail.com", + "publisher_website": "https://geekofia.wordpress.com", + "privacy_policy_website": "", + "license_agreement_website": "", + "stickers": [ + { + "image_file": "01_great_mind.webp" + }, + { + "image_file": "02_great_mind.webp" + }, + { + "image_file": "03_great_mind.webp" + }, + { + "image_file": "04_great_mind.webp" + }, + { + "image_file": "05_great_mind.webp" + }, + { + "image_file": "06_great_mind.webp" + }, + { + "image_file": "07_great_mind.webp" + }, + { + "image_file": "08_great_mind.webp" + }, + { + "image_file": "09_great_mind.webp" + }, + { + "image_file": "10_great_mind.webp" + }, + { + "image_file": "11_great_mind.webp" + }, + { + "image_file": "12_great_mind.webp" + }, + { + "image_file": "13_great_mind.webp" + }, + { + "image_file": "14_great_mind.webp" + }, + { + "image_file": "15_great_mind.webp" + }, + { + "image_file": "16_great_mind.webp" + }, + { + "image_file": "17_great_mind.webp" + }, + { + "image_file": "18_great_mind.webp" + }, + { + "image_file": "19_great_mind.webp" + }, + { + "image_file": "20_great_mind.webp" + }, + { + "image_file": "21_great_mind.webp" + }, + { + "image_file": "22_great_mind.webp" + }, + { + "image_file": "23_great_mind.webp" + } + ] + }, + { + "identifier": "gurl", + "name": "Gurl", + "publisher": "Chandan Kumar Mandal", + "tray_image_file": "tray_Gurl.png", + "publisher_email":"chankruze@gmail.com", + "publisher_website": "https://geekofia.wordpress.com", + "privacy_policy_website": "", + "license_agreement_website": "", + "stickers": [ + { + "image_file": "01_gurl.webp" + }, + { + "image_file": "02_gurl.webp" + }, + { + "image_file": "03_gurl.webp" + }, + { + "image_file": "04_gurl.webp" + }, + { + "image_file": "05_gurl.webp" + }, + { + "image_file": "06_gurl.webp" + }, + { + "image_file": "07_gurl.webp" + }, + { + "image_file": "08_gurl.webp" + }, + { + "image_file": "09_gurl.webp" + }, + { + "image_file": "10_gurl.webp" + }, + { + "image_file": "11_gurl.webp" + }, + { + "image_file": "12_gurl.webp" + } + ] + }, + { + "identifier": "misc", + "name": "Miscellaneous", + "publisher": "Chandan Kumar Mandal", + "tray_image_file": "tray_Miscellaneous.png", + "publisher_email":"chankruze@gmail.com", + "publisher_website": "https://geekofia.wordpress.com", + "privacy_policy_website": "", + "license_agreement_website": "", + "stickers": [ + { + "image_file": "01_misc.webp" + }, + { + "image_file": "02_misc.webp" + }, + { + "image_file": "03_misc.webp" + }, + { + "image_file": "04_misc.webp" + }, + { + "image_file": "05_misc.webp" + }, + { + "image_file": "06_misc.webp" + }, + { + "image_file": "07_misc.webp" + }, + { + "image_file": "08_misc.webp" + }, + { + "image_file": "09_misc.webp" + }, + { + "image_file": "10_misc.webp" + }, + { + "image_file": "11_misc.webp" + }, + { + "image_file": "12_misc.webp" + }, + { + "image_file": "13_misc.webp" + }, + { + "image_file": "14_misc.webp" + }, + { + "image_file": "15_misc.webp" + }, + { + "image_file": "16_misc.webp" + }, + { + "image_file": "17_misc.webp" + }, + { + "image_file": "18_misc.webp" + }, + { + "image_file": "19_misc.webp" + }, + { + "image_file": "20_misc.webp" + } + ] + }, + { + "identifier": "pewdiepie", + "name": "PewDiePie", + "publisher": "Chandan Kumar Mandal", + "tray_image_file": "tray_PewDiePie.png", + "publisher_email":"chankruze@gmail.com", + "publisher_website": "https://geekofia.wordpress.com", + "privacy_policy_website": "", + "license_agreement_website": "", + "stickers": [ + { + "image_file": "01_pew.webp" + }, + { + "image_file": "02_pew.webp" + }, + { + "image_file": "03_pew.webp" + }, + { + "image_file": "04_pew.webp" + }, + { + "image_file": "05_pew.webp" + }, + { + "image_file": "06_pew.webp" + }, + { + "image_file": "07_pew.webp" + }, + { + "image_file": "08_pew.webp" + }, + { + "image_file": "09_pew.webp" + }, + { + "image_file": "10_pew.webp" + }, + { + "image_file": "11_pew.webp" + }, + { + "image_file": "12_pew.webp" + } + ] + } + ] +} diff --git a/app/src/main/assets/cups/01_Cuppy_smile.webp b/app/src/main/assets/cups/01_Cuppy_smile.webp new file mode 100644 index 0000000..b6f1b1f Binary files /dev/null and b/app/src/main/assets/cups/01_Cuppy_smile.webp differ diff --git a/app/src/main/assets/cups/02_Cuppy_lol.webp b/app/src/main/assets/cups/02_Cuppy_lol.webp new file mode 100644 index 0000000..7d53dc1 Binary files /dev/null and b/app/src/main/assets/cups/02_Cuppy_lol.webp differ diff --git a/app/src/main/assets/cups/03_Cuppy_rofl.webp b/app/src/main/assets/cups/03_Cuppy_rofl.webp new file mode 100644 index 0000000..8edc275 Binary files /dev/null and b/app/src/main/assets/cups/03_Cuppy_rofl.webp differ diff --git a/app/src/main/assets/cups/04_Cuppy_sad.webp b/app/src/main/assets/cups/04_Cuppy_sad.webp new file mode 100644 index 0000000..e86a55d Binary files /dev/null and b/app/src/main/assets/cups/04_Cuppy_sad.webp differ diff --git a/app/src/main/assets/cups/05_Cuppy_cry.webp b/app/src/main/assets/cups/05_Cuppy_cry.webp new file mode 100644 index 0000000..d5bb6da Binary files /dev/null and b/app/src/main/assets/cups/05_Cuppy_cry.webp differ diff --git a/app/src/main/assets/cups/06_Cuppy_love.webp b/app/src/main/assets/cups/06_Cuppy_love.webp new file mode 100644 index 0000000..3d7bcc4 Binary files /dev/null and b/app/src/main/assets/cups/06_Cuppy_love.webp differ diff --git a/app/src/main/assets/cups/07_Cuppy_hate.webp b/app/src/main/assets/cups/07_Cuppy_hate.webp new file mode 100644 index 0000000..ca1b2a3 Binary files /dev/null and b/app/src/main/assets/cups/07_Cuppy_hate.webp differ diff --git a/app/src/main/assets/cups/08_Cuppy_lovewithmug.webp b/app/src/main/assets/cups/08_Cuppy_lovewithmug.webp new file mode 100644 index 0000000..0ed82d0 Binary files /dev/null and b/app/src/main/assets/cups/08_Cuppy_lovewithmug.webp differ diff --git a/app/src/main/assets/cups/09_Cuppy_lovewithcookie.webp b/app/src/main/assets/cups/09_Cuppy_lovewithcookie.webp new file mode 100644 index 0000000..882ae9a Binary files /dev/null and b/app/src/main/assets/cups/09_Cuppy_lovewithcookie.webp differ diff --git a/app/src/main/assets/cups/10_Cuppy_hmm.webp b/app/src/main/assets/cups/10_Cuppy_hmm.webp new file mode 100644 index 0000000..cb4d8f9 Binary files /dev/null and b/app/src/main/assets/cups/10_Cuppy_hmm.webp differ diff --git a/app/src/main/assets/cups/11_Cuppy_upset.webp b/app/src/main/assets/cups/11_Cuppy_upset.webp new file mode 100644 index 0000000..b04de4b Binary files /dev/null and b/app/src/main/assets/cups/11_Cuppy_upset.webp differ diff --git a/app/src/main/assets/cups/12_Cuppy_angry.webp b/app/src/main/assets/cups/12_Cuppy_angry.webp new file mode 100644 index 0000000..a5207db Binary files /dev/null and b/app/src/main/assets/cups/12_Cuppy_angry.webp differ diff --git a/app/src/main/assets/cups/13_Cuppy_curious.webp b/app/src/main/assets/cups/13_Cuppy_curious.webp new file mode 100644 index 0000000..6443bcc Binary files /dev/null and b/app/src/main/assets/cups/13_Cuppy_curious.webp differ diff --git a/app/src/main/assets/cups/14_Cuppy_weird.webp b/app/src/main/assets/cups/14_Cuppy_weird.webp new file mode 100644 index 0000000..bc8bc27 Binary files /dev/null and b/app/src/main/assets/cups/14_Cuppy_weird.webp differ diff --git a/app/src/main/assets/cups/15_Cuppy_bluescreen.webp b/app/src/main/assets/cups/15_Cuppy_bluescreen.webp new file mode 100644 index 0000000..b00db70 Binary files /dev/null and b/app/src/main/assets/cups/15_Cuppy_bluescreen.webp differ diff --git a/app/src/main/assets/cups/16_Cuppy_angry.webp b/app/src/main/assets/cups/16_Cuppy_angry.webp new file mode 100644 index 0000000..724e8bf Binary files /dev/null and b/app/src/main/assets/cups/16_Cuppy_angry.webp differ diff --git a/app/src/main/assets/cups/17_Cuppy_tired.webp b/app/src/main/assets/cups/17_Cuppy_tired.webp new file mode 100644 index 0000000..0e3173f Binary files /dev/null and b/app/src/main/assets/cups/17_Cuppy_tired.webp differ diff --git a/app/src/main/assets/cups/18_Cuppy_workhard.webp b/app/src/main/assets/cups/18_Cuppy_workhard.webp new file mode 100644 index 0000000..9250400 Binary files /dev/null and b/app/src/main/assets/cups/18_Cuppy_workhard.webp differ diff --git a/app/src/main/assets/cups/19_Cuppy_shine.webp b/app/src/main/assets/cups/19_Cuppy_shine.webp new file mode 100644 index 0000000..39a2211 Binary files /dev/null and b/app/src/main/assets/cups/19_Cuppy_shine.webp differ diff --git a/app/src/main/assets/cups/20_Cuppy_disgusting.webp b/app/src/main/assets/cups/20_Cuppy_disgusting.webp new file mode 100644 index 0000000..b770988 Binary files /dev/null and b/app/src/main/assets/cups/20_Cuppy_disgusting.webp differ diff --git a/app/src/main/assets/cups/21_Cuppy_hi.webp b/app/src/main/assets/cups/21_Cuppy_hi.webp new file mode 100644 index 0000000..44afb1c Binary files /dev/null and b/app/src/main/assets/cups/21_Cuppy_hi.webp differ diff --git a/app/src/main/assets/cups/22_Cuppy_bye.webp b/app/src/main/assets/cups/22_Cuppy_bye.webp new file mode 100644 index 0000000..9f38410 Binary files /dev/null and b/app/src/main/assets/cups/22_Cuppy_bye.webp differ diff --git a/app/src/main/assets/cups/23_Cuppy_greentea.webp b/app/src/main/assets/cups/23_Cuppy_greentea.webp new file mode 100644 index 0000000..72d3f4e Binary files /dev/null and b/app/src/main/assets/cups/23_Cuppy_greentea.webp differ diff --git a/app/src/main/assets/cups/24_Cuppy_phone.webp b/app/src/main/assets/cups/24_Cuppy_phone.webp new file mode 100644 index 0000000..5495457 Binary files /dev/null and b/app/src/main/assets/cups/24_Cuppy_phone.webp differ diff --git a/app/src/main/assets/cups/25_Cuppy_battery.webp b/app/src/main/assets/cups/25_Cuppy_battery.webp new file mode 100644 index 0000000..cb39692 Binary files /dev/null and b/app/src/main/assets/cups/25_Cuppy_battery.webp differ diff --git a/app/src/main/assets/cups/tray_Cuppy.png b/app/src/main/assets/cups/tray_Cuppy.png new file mode 100644 index 0000000..d6a31c4 Binary files /dev/null and b/app/src/main/assets/cups/tray_Cuppy.png differ diff --git a/app/src/main/assets/great_mind/01_great_mind.webp b/app/src/main/assets/great_mind/01_great_mind.webp new file mode 100644 index 0000000..1253579 Binary files /dev/null and b/app/src/main/assets/great_mind/01_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/02_great_mind.webp b/app/src/main/assets/great_mind/02_great_mind.webp new file mode 100644 index 0000000..e122d30 Binary files /dev/null and b/app/src/main/assets/great_mind/02_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/03_great_mind.webp b/app/src/main/assets/great_mind/03_great_mind.webp new file mode 100644 index 0000000..6bd6cfd Binary files /dev/null and b/app/src/main/assets/great_mind/03_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/04_great_mind.webp b/app/src/main/assets/great_mind/04_great_mind.webp new file mode 100644 index 0000000..a30c506 Binary files /dev/null and b/app/src/main/assets/great_mind/04_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/05_great_mind.webp b/app/src/main/assets/great_mind/05_great_mind.webp new file mode 100644 index 0000000..04e115e Binary files /dev/null and b/app/src/main/assets/great_mind/05_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/06_great_mind.webp b/app/src/main/assets/great_mind/06_great_mind.webp new file mode 100644 index 0000000..f6c995f Binary files /dev/null and b/app/src/main/assets/great_mind/06_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/07_great_mind.webp b/app/src/main/assets/great_mind/07_great_mind.webp new file mode 100644 index 0000000..d940150 Binary files /dev/null and b/app/src/main/assets/great_mind/07_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/08_great_mind.webp b/app/src/main/assets/great_mind/08_great_mind.webp new file mode 100644 index 0000000..cbbf6fc Binary files /dev/null and b/app/src/main/assets/great_mind/08_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/09_great_mind.webp b/app/src/main/assets/great_mind/09_great_mind.webp new file mode 100644 index 0000000..92f7816 Binary files /dev/null and b/app/src/main/assets/great_mind/09_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/10_great_mind.webp b/app/src/main/assets/great_mind/10_great_mind.webp new file mode 100644 index 0000000..94bee61 Binary files /dev/null and b/app/src/main/assets/great_mind/10_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/11_great_mind.webp b/app/src/main/assets/great_mind/11_great_mind.webp new file mode 100644 index 0000000..ab64b30 Binary files /dev/null and b/app/src/main/assets/great_mind/11_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/12_great_mind.webp b/app/src/main/assets/great_mind/12_great_mind.webp new file mode 100644 index 0000000..25b6f35 Binary files /dev/null and b/app/src/main/assets/great_mind/12_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/13_great_mind.webp b/app/src/main/assets/great_mind/13_great_mind.webp new file mode 100644 index 0000000..55c730e Binary files /dev/null and b/app/src/main/assets/great_mind/13_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/14_great_mind.webp b/app/src/main/assets/great_mind/14_great_mind.webp new file mode 100644 index 0000000..af96102 Binary files /dev/null and b/app/src/main/assets/great_mind/14_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/15_great_mind.webp b/app/src/main/assets/great_mind/15_great_mind.webp new file mode 100644 index 0000000..446cb90 Binary files /dev/null and b/app/src/main/assets/great_mind/15_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/16_great_mind.webp b/app/src/main/assets/great_mind/16_great_mind.webp new file mode 100644 index 0000000..1e7597d Binary files /dev/null and b/app/src/main/assets/great_mind/16_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/17_great_mind.webp b/app/src/main/assets/great_mind/17_great_mind.webp new file mode 100644 index 0000000..d469455 Binary files /dev/null and b/app/src/main/assets/great_mind/17_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/18_great_mind.webp b/app/src/main/assets/great_mind/18_great_mind.webp new file mode 100644 index 0000000..6245589 Binary files /dev/null and b/app/src/main/assets/great_mind/18_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/19_great_mind.webp b/app/src/main/assets/great_mind/19_great_mind.webp new file mode 100644 index 0000000..ad52321 Binary files /dev/null and b/app/src/main/assets/great_mind/19_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/20_great_mind.webp b/app/src/main/assets/great_mind/20_great_mind.webp new file mode 100644 index 0000000..68f01fd Binary files /dev/null and b/app/src/main/assets/great_mind/20_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/21_great_mind.webp b/app/src/main/assets/great_mind/21_great_mind.webp new file mode 100644 index 0000000..d4d4add Binary files /dev/null and b/app/src/main/assets/great_mind/21_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/22_great_mind.webp b/app/src/main/assets/great_mind/22_great_mind.webp new file mode 100644 index 0000000..ea4f56b Binary files /dev/null and b/app/src/main/assets/great_mind/22_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/23_great_mind.webp b/app/src/main/assets/great_mind/23_great_mind.webp new file mode 100644 index 0000000..4e8e233 Binary files /dev/null and b/app/src/main/assets/great_mind/23_great_mind.webp differ diff --git a/app/src/main/assets/great_mind/tray_Great_Mind.png b/app/src/main/assets/great_mind/tray_Great_Mind.png new file mode 100644 index 0000000..3ddac56 Binary files /dev/null and b/app/src/main/assets/great_mind/tray_Great_Mind.png differ diff --git a/app/src/main/assets/gurl/01_gurl.webp b/app/src/main/assets/gurl/01_gurl.webp new file mode 100644 index 0000000..b41df98 Binary files /dev/null and b/app/src/main/assets/gurl/01_gurl.webp differ diff --git a/app/src/main/assets/gurl/02_gurl.webp b/app/src/main/assets/gurl/02_gurl.webp new file mode 100644 index 0000000..d51dbd8 Binary files /dev/null and b/app/src/main/assets/gurl/02_gurl.webp differ diff --git a/app/src/main/assets/gurl/03_gurl.webp b/app/src/main/assets/gurl/03_gurl.webp new file mode 100644 index 0000000..8ca1535 Binary files /dev/null and b/app/src/main/assets/gurl/03_gurl.webp differ diff --git a/app/src/main/assets/gurl/04_gurl.webp b/app/src/main/assets/gurl/04_gurl.webp new file mode 100644 index 0000000..f6bba87 Binary files /dev/null and b/app/src/main/assets/gurl/04_gurl.webp differ diff --git a/app/src/main/assets/gurl/05_gurl.webp b/app/src/main/assets/gurl/05_gurl.webp new file mode 100644 index 0000000..6a41594 Binary files /dev/null and b/app/src/main/assets/gurl/05_gurl.webp differ diff --git a/app/src/main/assets/gurl/06_gurl.webp b/app/src/main/assets/gurl/06_gurl.webp new file mode 100644 index 0000000..1298d49 Binary files /dev/null and b/app/src/main/assets/gurl/06_gurl.webp differ diff --git a/app/src/main/assets/gurl/07_gurl.webp b/app/src/main/assets/gurl/07_gurl.webp new file mode 100644 index 0000000..352514a Binary files /dev/null and b/app/src/main/assets/gurl/07_gurl.webp differ diff --git a/app/src/main/assets/gurl/08_gurl.webp b/app/src/main/assets/gurl/08_gurl.webp new file mode 100644 index 0000000..6c722e2 Binary files /dev/null and b/app/src/main/assets/gurl/08_gurl.webp differ diff --git a/app/src/main/assets/gurl/09_gurl.webp b/app/src/main/assets/gurl/09_gurl.webp new file mode 100644 index 0000000..0bf3854 Binary files /dev/null and b/app/src/main/assets/gurl/09_gurl.webp differ diff --git a/app/src/main/assets/gurl/10_gurl.webp b/app/src/main/assets/gurl/10_gurl.webp new file mode 100644 index 0000000..04c83a3 Binary files /dev/null and b/app/src/main/assets/gurl/10_gurl.webp differ diff --git a/app/src/main/assets/gurl/11_gurl.webp b/app/src/main/assets/gurl/11_gurl.webp new file mode 100644 index 0000000..0a891a6 Binary files /dev/null and b/app/src/main/assets/gurl/11_gurl.webp differ diff --git a/app/src/main/assets/gurl/12_gurl.webp b/app/src/main/assets/gurl/12_gurl.webp new file mode 100644 index 0000000..0f7824f Binary files /dev/null and b/app/src/main/assets/gurl/12_gurl.webp differ diff --git a/app/src/main/assets/gurl/tray_Gurl.png b/app/src/main/assets/gurl/tray_Gurl.png new file mode 100644 index 0000000..4e94d61 Binary files /dev/null and b/app/src/main/assets/gurl/tray_Gurl.png differ diff --git a/app/src/main/assets/misc/01_misc.webp b/app/src/main/assets/misc/01_misc.webp new file mode 100644 index 0000000..c54e0e3 Binary files /dev/null and b/app/src/main/assets/misc/01_misc.webp differ diff --git a/app/src/main/assets/misc/02_misc.webp b/app/src/main/assets/misc/02_misc.webp new file mode 100644 index 0000000..ecd17da Binary files /dev/null and b/app/src/main/assets/misc/02_misc.webp differ diff --git a/app/src/main/assets/misc/03_misc.webp b/app/src/main/assets/misc/03_misc.webp new file mode 100644 index 0000000..ede5e70 Binary files /dev/null and b/app/src/main/assets/misc/03_misc.webp differ diff --git a/app/src/main/assets/misc/04_misc.webp b/app/src/main/assets/misc/04_misc.webp new file mode 100644 index 0000000..1fd021f Binary files /dev/null and b/app/src/main/assets/misc/04_misc.webp differ diff --git a/app/src/main/assets/misc/05_misc.webp b/app/src/main/assets/misc/05_misc.webp new file mode 100644 index 0000000..399086d Binary files /dev/null and b/app/src/main/assets/misc/05_misc.webp differ diff --git a/app/src/main/assets/misc/06_misc.webp b/app/src/main/assets/misc/06_misc.webp new file mode 100644 index 0000000..f8c55ef Binary files /dev/null and b/app/src/main/assets/misc/06_misc.webp differ diff --git a/app/src/main/assets/misc/07_misc.webp b/app/src/main/assets/misc/07_misc.webp new file mode 100644 index 0000000..369be36 Binary files /dev/null and b/app/src/main/assets/misc/07_misc.webp differ diff --git a/app/src/main/assets/misc/08_misc.webp b/app/src/main/assets/misc/08_misc.webp new file mode 100644 index 0000000..25c3e7b Binary files /dev/null and b/app/src/main/assets/misc/08_misc.webp differ diff --git a/app/src/main/assets/misc/09_misc.webp b/app/src/main/assets/misc/09_misc.webp new file mode 100644 index 0000000..91e186a Binary files /dev/null and b/app/src/main/assets/misc/09_misc.webp differ diff --git a/app/src/main/assets/misc/10_misc.webp b/app/src/main/assets/misc/10_misc.webp new file mode 100644 index 0000000..26ce0f1 Binary files /dev/null and b/app/src/main/assets/misc/10_misc.webp differ diff --git a/app/src/main/assets/misc/11_misc.webp b/app/src/main/assets/misc/11_misc.webp new file mode 100644 index 0000000..78a340a Binary files /dev/null and b/app/src/main/assets/misc/11_misc.webp differ diff --git a/app/src/main/assets/misc/12_misc.webp b/app/src/main/assets/misc/12_misc.webp new file mode 100644 index 0000000..90d4ce4 Binary files /dev/null and b/app/src/main/assets/misc/12_misc.webp differ diff --git a/app/src/main/assets/misc/13_misc.webp b/app/src/main/assets/misc/13_misc.webp new file mode 100644 index 0000000..92b29a4 Binary files /dev/null and b/app/src/main/assets/misc/13_misc.webp differ diff --git a/app/src/main/assets/misc/14_misc.webp b/app/src/main/assets/misc/14_misc.webp new file mode 100644 index 0000000..d636a55 Binary files /dev/null and b/app/src/main/assets/misc/14_misc.webp differ diff --git a/app/src/main/assets/misc/15_misc.webp b/app/src/main/assets/misc/15_misc.webp new file mode 100644 index 0000000..e0d6a5e Binary files /dev/null and b/app/src/main/assets/misc/15_misc.webp differ diff --git a/app/src/main/assets/misc/16_misc.webp b/app/src/main/assets/misc/16_misc.webp new file mode 100644 index 0000000..dc4886d Binary files /dev/null and b/app/src/main/assets/misc/16_misc.webp differ diff --git a/app/src/main/assets/misc/17_misc.webp b/app/src/main/assets/misc/17_misc.webp new file mode 100644 index 0000000..9480b39 Binary files /dev/null and b/app/src/main/assets/misc/17_misc.webp differ diff --git a/app/src/main/assets/misc/18_misc.webp b/app/src/main/assets/misc/18_misc.webp new file mode 100644 index 0000000..527c3cd Binary files /dev/null and b/app/src/main/assets/misc/18_misc.webp differ diff --git a/app/src/main/assets/misc/19_misc.webp b/app/src/main/assets/misc/19_misc.webp new file mode 100644 index 0000000..efd1f83 Binary files /dev/null and b/app/src/main/assets/misc/19_misc.webp differ diff --git a/app/src/main/assets/misc/20_misc.webp b/app/src/main/assets/misc/20_misc.webp new file mode 100644 index 0000000..151780d Binary files /dev/null and b/app/src/main/assets/misc/20_misc.webp differ diff --git a/app/src/main/assets/misc/tray_Miscellaneous.png b/app/src/main/assets/misc/tray_Miscellaneous.png new file mode 100644 index 0000000..7a6b55f Binary files /dev/null and b/app/src/main/assets/misc/tray_Miscellaneous.png differ diff --git a/app/src/main/assets/pewdiepie/01_pew.webp b/app/src/main/assets/pewdiepie/01_pew.webp new file mode 100644 index 0000000..bf2956a Binary files /dev/null and b/app/src/main/assets/pewdiepie/01_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/02_pew.webp b/app/src/main/assets/pewdiepie/02_pew.webp new file mode 100644 index 0000000..830a84d Binary files /dev/null and b/app/src/main/assets/pewdiepie/02_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/03_pew.webp b/app/src/main/assets/pewdiepie/03_pew.webp new file mode 100644 index 0000000..7baf3f3 Binary files /dev/null and b/app/src/main/assets/pewdiepie/03_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/04_pew.webp b/app/src/main/assets/pewdiepie/04_pew.webp new file mode 100644 index 0000000..38de62e Binary files /dev/null and b/app/src/main/assets/pewdiepie/04_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/05_pew.webp b/app/src/main/assets/pewdiepie/05_pew.webp new file mode 100644 index 0000000..0d352cd Binary files /dev/null and b/app/src/main/assets/pewdiepie/05_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/06_pew.webp b/app/src/main/assets/pewdiepie/06_pew.webp new file mode 100644 index 0000000..a6019b3 Binary files /dev/null and b/app/src/main/assets/pewdiepie/06_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/07_pew.webp b/app/src/main/assets/pewdiepie/07_pew.webp new file mode 100644 index 0000000..06123fb Binary files /dev/null and b/app/src/main/assets/pewdiepie/07_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/08_pew.webp b/app/src/main/assets/pewdiepie/08_pew.webp new file mode 100644 index 0000000..7081e1f Binary files /dev/null and b/app/src/main/assets/pewdiepie/08_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/09_pew.webp b/app/src/main/assets/pewdiepie/09_pew.webp new file mode 100644 index 0000000..44b9b5f Binary files /dev/null and b/app/src/main/assets/pewdiepie/09_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/10_pew.webp b/app/src/main/assets/pewdiepie/10_pew.webp new file mode 100644 index 0000000..7a7e87e Binary files /dev/null and b/app/src/main/assets/pewdiepie/10_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/11_pew.webp b/app/src/main/assets/pewdiepie/11_pew.webp new file mode 100644 index 0000000..7a12d26 Binary files /dev/null and b/app/src/main/assets/pewdiepie/11_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/12_pew.webp b/app/src/main/assets/pewdiepie/12_pew.webp new file mode 100644 index 0000000..c716df0 Binary files /dev/null and b/app/src/main/assets/pewdiepie/12_pew.webp differ diff --git a/app/src/main/assets/pewdiepie/tray_PewDiePie.png b/app/src/main/assets/pewdiepie/tray_PewDiePie.png new file mode 100644 index 0000000..0396361 Binary files /dev/null and b/app/src/main/assets/pewdiepie/tray_PewDiePie.png differ diff --git a/app/src/main/java/com/geekofia/whatsappstickers/AddStickerPackActivity.java b/app/src/main/java/com/geekofia/whatsappstickers/AddStickerPackActivity.java new file mode 100644 index 0000000..f84be1c --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/AddStickerPackActivity.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.app.Activity; +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.widget.Toast; + +import com.geekofia.whatsappstickers.BuildConfig; +import com.geekofia.whatsappstickers.R; + +public abstract class AddStickerPackActivity extends BaseActivity { + public static final int ADD_PACK = 200; + + protected void addStickerPackToWhatsApp(String identifier, String stickerPackName) { + Intent intent = new Intent(); + intent.setAction("com.whatsapp.intent.action.ENABLE_STICKER_PACK"); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_ID, identifier); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_AUTHORITY, BuildConfig.CONTENT_PROVIDER_AUTHORITY); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_NAME, stickerPackName); + try { + startActivityForResult(intent, ADD_PACK); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.error_adding_sticker_pack, Toast.LENGTH_LONG).show(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == ADD_PACK) { + if (resultCode == Activity.RESULT_CANCELED) { + if (data != null) { + final String validationError = data.getStringExtra("validation_error"); + if (validationError != null) { + if (BuildConfig.DEBUG) { + //validation error should be shown to developer only, not users. + MessageDialogFragment.newInstance(R.string.title_validation_error, validationError).show(getSupportFragmentManager(), "validation error"); + } + Log.e("AddStickerPackActivity", "Validation failed:" + validationError); + } + } else { + new StickerPackNotAddedMessageFragment().show(getSupportFragmentManager(), "sticker_pack_not_added"); + } + } + } + } + + public static final class StickerPackNotAddedMessageFragment extends DialogFragment { + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()) + .setMessage(R.string.add_pack_fail_prompt_update_whatsapp) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, (dialog, which) -> dismiss()) + .setNeutralButton(R.string.add_pack_fail_prompt_update_play_link, (dialog, which) -> launchWhatsAppPlayStorePage()); + + return dialogBuilder.create(); + } + + private void launchWhatsAppPlayStorePage() { + if (getActivity() != null) { + final PackageManager packageManager = getActivity().getPackageManager(); + final boolean whatsAppInstalled = WhitelistCheck.isPackageInstalled(WhitelistCheck.CONSUMER_WHATSAPP_PACKAGE_NAME, packageManager); + final boolean smbAppInstalled = WhitelistCheck.isPackageInstalled(WhitelistCheck.SMB_WHATSAPP_PACKAGE_NAME, packageManager); + final String playPackageLinkPrefix = "http://play.google.com/store/apps/details?id="; + if (whatsAppInstalled && smbAppInstalled) { + launchPlayStoreWithUri("https://play.google.com/store/apps/developer?id=WhatsApp+Inc."); + } else if (whatsAppInstalled) { + launchPlayStoreWithUri(playPackageLinkPrefix + WhitelistCheck.CONSUMER_WHATSAPP_PACKAGE_NAME); + } else if (smbAppInstalled) { + launchPlayStoreWithUri(playPackageLinkPrefix + WhitelistCheck.SMB_WHATSAPP_PACKAGE_NAME); + } + } + } + + private void launchPlayStoreWithUri(String uriString) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(uriString)); + intent.setPackage("com.android.vending"); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Toast.makeText(getActivity(), R.string.cannot_find_play_store, Toast.LENGTH_LONG).show(); + } + } + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/BaseActivity.java b/app/src/main/java/com/geekofia/whatsappstickers/BaseActivity.java new file mode 100644 index 0000000..02c0359 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/BaseActivity.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; + +public abstract class BaseActivity extends AppCompatActivity { + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + public static final class MessageDialogFragment extends DialogFragment { + private static final String ARG_TITLE_ID = "title_id"; + private static final String ARG_MESSAGE = "message"; + + public static DialogFragment newInstance(@StringRes int titleId, String message) { + DialogFragment fragment = new MessageDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putInt(ARG_TITLE_ID, titleId); + arguments.putString(ARG_MESSAGE, message); + fragment.setArguments(arguments); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + @StringRes final int title = getArguments().getInt(ARG_TITLE_ID); + String message = getArguments().getString(ARG_MESSAGE); + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()) + .setMessage(message) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, (dialog, which) -> dismiss()); + + if (title != 0) { + dialogBuilder.setTitle(title); + } + return dialogBuilder.create(); + } + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/BottomFadingRecyclerView.java b/app/src/main/java/com/geekofia/whatsappstickers/BottomFadingRecyclerView.java new file mode 100644 index 0000000..e46905c --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/BottomFadingRecyclerView.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + +public class BottomFadingRecyclerView extends RecyclerView { + public BottomFadingRecyclerView(Context context) { + super(context); + } + + public BottomFadingRecyclerView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public BottomFadingRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected float getTopFadingEdgeStrength() { + return 0.0f; + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/ContentFileParser.java b/app/src/main/java/com/geekofia/whatsappstickers/ContentFileParser.java new file mode 100644 index 0000000..5075c84 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/ContentFileParser.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.JsonReader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +class ContentFileParser { + + private static final int LIMIT_EMOJI_COUNT = 2; + + @NonNull + static List parseStickerPacks(@NonNull InputStream contentsInputStream) throws IOException, IllegalStateException { + try (JsonReader reader = new JsonReader(new InputStreamReader(contentsInputStream))) { + return readStickerPacks(reader); + } + } + + @NonNull + private static List readStickerPacks(@NonNull JsonReader reader) throws IOException, IllegalStateException { + List stickerPackList = new ArrayList<>(); + String androidPlayStoreLink = null; + String iosAppStoreLink = null; + reader.beginObject(); + while (reader.hasNext()) { + String key = reader.nextName(); + if ("android_play_store_link".equals(key)) { + androidPlayStoreLink = reader.nextString(); + } else if ("ios_app_store_link".equals(key)) { + iosAppStoreLink = reader.nextString(); + } else if ("sticker_packs".equals(key)) { + reader.beginArray(); + while (reader.hasNext()) { + StickerPack stickerPack = readStickerPack(reader); + stickerPackList.add(stickerPack); + } + reader.endArray(); + } else { + throw new IllegalStateException("unknown field in json: " + key); + } + } + reader.endObject(); + if (stickerPackList.size() == 0) { + throw new IllegalStateException("sticker pack list cannot be empty"); + } + for (StickerPack stickerPack : stickerPackList) { + stickerPack.setAndroidPlayStoreLink(androidPlayStoreLink); + stickerPack.setIosAppStoreLink(iosAppStoreLink); + } + return stickerPackList; + } + + @NonNull + private static StickerPack readStickerPack(@NonNull JsonReader reader) throws IOException, IllegalStateException { + reader.beginObject(); + String identifier = null; + String name = null; + String publisher = null; + String trayImageFile = null; + String publisherEmail = null; + String publisherWebsite = null; + String privacyPolicyWebsite = null; + String licenseAgreementWebsite = null; + List stickerList = null; + while (reader.hasNext()) { + String key = reader.nextName(); + switch (key) { + case "identifier": + identifier = reader.nextString(); + break; + case "name": + name = reader.nextString(); + break; + case "publisher": + publisher = reader.nextString(); + break; + case "tray_image_file": + trayImageFile = reader.nextString(); + break; + case "publisher_email": + publisherEmail = reader.nextString(); + break; + case "publisher_website": + publisherWebsite = reader.nextString(); + break; + case "privacy_policy_website": + privacyPolicyWebsite = reader.nextString(); + break; + case "license_agreement_website": + licenseAgreementWebsite = reader.nextString(); + break; + case "stickers": + stickerList = readStickers(reader); + break; + default: + reader.skipValue(); + } + } + if (TextUtils.isEmpty(identifier)) { + throw new IllegalStateException("identifier cannot be empty"); + } + if (TextUtils.isEmpty(name)) { + throw new IllegalStateException("name cannot be empty"); + } + if (TextUtils.isEmpty(publisher)) { + throw new IllegalStateException("publisher cannot be empty"); + } + if (TextUtils.isEmpty(trayImageFile)) { + throw new IllegalStateException("tray_image_file cannot be empty"); + } + if (stickerList == null || stickerList.size() == 0) { + throw new IllegalStateException("sticker list is empty"); + } + if (identifier.contains("..") || identifier.contains("/")) { + throw new IllegalStateException("identifier should not contain .. or / to prevent directory traversal"); + } + reader.endObject(); + final StickerPack stickerPack = new StickerPack(identifier, name, publisher, trayImageFile, publisherEmail, publisherWebsite, privacyPolicyWebsite, licenseAgreementWebsite); + stickerPack.setStickers(stickerList); + return stickerPack; + } + + @NonNull + private static List readStickers(@NonNull JsonReader reader) throws IOException, IllegalStateException { + reader.beginArray(); + List stickerList = new ArrayList<>(); + + while (reader.hasNext()) { + reader.beginObject(); + String imageFile = null; + List emojis = new ArrayList<>(LIMIT_EMOJI_COUNT); + while (reader.hasNext()) { + final String key = reader.nextName(); + if ("image_file".equals(key)) { + imageFile = reader.nextString(); + } else if ("emojis".equals(key)) { + reader.beginArray(); + while (reader.hasNext()) { + String emoji = reader.nextString(); + emojis.add(emoji); + } + reader.endArray(); + } else { + throw new IllegalStateException("unknown field in json: " + key); + } + } + reader.endObject(); + if (TextUtils.isEmpty(imageFile)) { + throw new IllegalStateException("sticker image_file cannot be empty"); + } + if (!imageFile.endsWith(".webp")) { + throw new IllegalStateException("image file for stickers should be webp files, image file is: " + imageFile); + } + if (imageFile.contains("..") || imageFile.contains("/")) { + throw new IllegalStateException("the file name should not contain .. or / to prevent directory traversal, image file is:" + imageFile); + } + stickerList.add(new Sticker(imageFile, emojis)); + } + reader.endArray(); + return stickerList; + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/EntryActivity.java b/app/src/main/java/com/geekofia/whatsappstickers/EntryActivity.java new file mode 100644 index 0000000..bb78ceb --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/EntryActivity.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.Log; +import android.util.Pair; +import android.view.View; +import android.widget.TextView; + +import com.geekofia.whatsappstickers.R; +import com.facebook.drawee.backends.pipeline.Fresco; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +public class EntryActivity extends BaseActivity { + private View progressBar; + private LoadListAsyncTask loadListAsyncTask; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Fresco.initialize(this); + setContentView(R.layout.activity_entry); + overridePendingTransition(0, 0); + if (getSupportActionBar() != null) { + getSupportActionBar().hide(); + } + progressBar = findViewById(R.id.entry_activity_progress); + loadListAsyncTask = new LoadListAsyncTask(this); + loadListAsyncTask.execute(); + } + + private void showStickerPack(ArrayList stickerPackList) { + progressBar.setVisibility(View.GONE); + if (stickerPackList.size() > 1) { + final Intent intent = new Intent(this, StickerPackListActivity.class); + intent.putParcelableArrayListExtra(StickerPackListActivity.EXTRA_STICKER_PACK_LIST_DATA, stickerPackList); + startActivity(intent); + finish(); + overridePendingTransition(0, 0); + } else { + final Intent intent = new Intent(this, StickerPackDetailsActivity.class); + intent.putExtra(StickerPackDetailsActivity.EXTRA_SHOW_UP_BUTTON, false); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_DATA, stickerPackList.get(0)); + startActivity(intent); + finish(); + overridePendingTransition(0, 0); + } + } + + private void showErrorMessage(String errorMessage) { + progressBar.setVisibility(View.GONE); + Log.e("EntryActivity", "error fetching sticker packs, " + errorMessage); + final TextView errorMessageTV = findViewById(R.id.error_message); + errorMessageTV.setText(getString(R.string.error_message, errorMessage)); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (loadListAsyncTask != null && !loadListAsyncTask.isCancelled()) { + loadListAsyncTask.cancel(true); + } + } + + static class LoadListAsyncTask extends AsyncTask>> { + private final WeakReference contextWeakReference; + + LoadListAsyncTask(EntryActivity activity) { + this.contextWeakReference = new WeakReference<>(activity); + } + + @Override + protected Pair> doInBackground(Void... voids) { + ArrayList stickerPackList; + try { + final Context context = contextWeakReference.get(); + if (context != null) { + stickerPackList = StickerPackLoader.fetchStickerPacks(context); + if (stickerPackList.size() == 0) { + return new Pair<>("could not find any packs", null); + } + for (StickerPack stickerPack : stickerPackList) { + StickerPackValidator.verifyStickerPackValidity(context, stickerPack); + } + return new Pair<>(null, stickerPackList); + } else { + return new Pair<>("could not fetch sticker packs", null); + } + } catch (Exception e) { + Log.e("EntryActivity", "error fetching sticker packs", e); + return new Pair<>(e.getMessage(), null); + } + } + + @Override + protected void onPostExecute(Pair> stringListPair) { + + final EntryActivity entryActivity = contextWeakReference.get(); + if (entryActivity != null) { + if (stringListPair.first != null) { + entryActivity.showErrorMessage(stringListPair.first); + } else { + entryActivity.showStickerPack(stringListPair.second); + } + } + } + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/Sticker.java b/app/src/main/java/com/geekofia/whatsappstickers/Sticker.java new file mode 100644 index 0000000..8d32597 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/Sticker.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +class Sticker implements Parcelable { + String imageFileName; + List emojis; + long size; + + Sticker(String imageFileName, List emojis) { + this.imageFileName = imageFileName; + this.emojis = emojis; + } + + protected Sticker(Parcel in) { + imageFileName = in.readString(); + emojis = in.createStringArrayList(); + size = in.readLong(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Sticker createFromParcel(Parcel in) { + return new Sticker(in); + } + + @Override + public Sticker[] newArray(int size) { + return new Sticker[size]; + } + }; + + public void setSize(long size) { + this.size = size; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(imageFileName); + dest.writeStringList(emojis); + dest.writeLong(size); + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerContentProvider.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerContentProvider.java new file mode 100644 index 0000000..21c841c --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerContentProvider.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import com.geekofia.whatsappstickers.BuildConfig; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class StickerContentProvider extends ContentProvider { + + /** + * Do not change the strings listed below, as these are used by WhatsApp. And changing these will break the interface between sticker app and WhatsApp. + */ + public static final String STICKER_PACK_IDENTIFIER_IN_QUERY = "sticker_pack_identifier"; + public static final String STICKER_PACK_NAME_IN_QUERY = "sticker_pack_name"; + public static final String STICKER_PACK_PUBLISHER_IN_QUERY = "sticker_pack_publisher"; + public static final String STICKER_PACK_ICON_IN_QUERY = "sticker_pack_icon"; + public static final String ANDROID_APP_DOWNLOAD_LINK_IN_QUERY = "android_play_store_link"; + public static final String IOS_APP_DOWNLOAD_LINK_IN_QUERY = "ios_app_download_link"; + public static final String PUBLISHER_EMAIL = "sticker_pack_publisher_email"; + public static final String PUBLISHER_WEBSITE = "sticker_pack_publisher_website"; + public static final String PRIVACY_POLICY_WEBSITE = "sticker_pack_privacy_policy_website"; + public static final String LICENSE_AGREENMENT_WEBSITE = "sticker_pack_license_agreement_website"; + + public static final String STICKER_FILE_NAME_IN_QUERY = "sticker_file_name"; + public static final String STICKER_FILE_EMOJI_IN_QUERY = "sticker_emoji"; + public static final String CONTENT_FILE_NAME = "contents.json"; + + public static Uri AUTHORITY_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.METADATA).build(); + + /** + * Do not change the values in the UriMatcher because otherwise, WhatsApp will not be able to fetch the stickers from the ContentProvider. + */ + private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + static final String METADATA = "metadata"; + private static final int METADATA_CODE = 1; + + private static final int METADATA_CODE_FOR_SINGLE_PACK = 2; + + static final String STICKERS = "stickers"; + private static final int STICKERS_CODE = 3; + + static final String STICKERS_ASSET = "stickers_asset"; + private static final int STICKERS_ASSET_CODE = 4; + + private static final int STICKER_PACK_TRAY_ICON_CODE = 5; + + private List stickerPackList; + + @Override + public boolean onCreate() { + final String authority = BuildConfig.CONTENT_PROVIDER_AUTHORITY; + if (!authority.startsWith(Objects.requireNonNull(getContext()).getPackageName())) { + throw new IllegalStateException("your authority (" + authority + ") for the content provider should start with your package name: " + getContext().getPackageName()); + } + + //the call to get the metadata for the sticker packs. + MATCHER.addURI(authority, METADATA, METADATA_CODE); + + //the call to get the metadata for single sticker pack. * represent the identifier + MATCHER.addURI(authority, METADATA + "/*", METADATA_CODE_FOR_SINGLE_PACK); + + //gets the list of stickers for a sticker pack, * respresent the identifier. + MATCHER.addURI(authority, STICKERS + "/*", STICKERS_CODE); + + for (StickerPack stickerPack : getStickerPackList()) { + MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + stickerPack.trayImageFile, STICKER_PACK_TRAY_ICON_CODE); + for (Sticker sticker : stickerPack.getStickers()) { + MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + sticker.imageFileName, STICKERS_ASSET_CODE); + } + } + + return true; + } + + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + final int code = MATCHER.match(uri); + if (code == METADATA_CODE) { + return getPackForAllStickerPacks(uri); + } else if (code == METADATA_CODE_FOR_SINGLE_PACK) { + return getCursorForSingleStickerPack(uri); + } else if (code == STICKERS_CODE) { + return getStickersForAStickerPack(uri); + } else { + throw new IllegalArgumentException("Unknown URI: " + uri); + } + } + + @Nullable + @Override + public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode) { + final int matchCode = MATCHER.match(uri); + if (matchCode == STICKERS_ASSET_CODE || matchCode == STICKER_PACK_TRAY_ICON_CODE) { + return getImageAsset(uri); + } + return null; + } + + + @Override + public String getType(@NonNull Uri uri) { + final int matchCode = MATCHER.match(uri); + switch (matchCode) { + case METADATA_CODE: + return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA; + case METADATA_CODE_FOR_SINGLE_PACK: + return "vnd.android.cursor.item/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + METADATA; + case STICKERS_CODE: + return "vnd.android.cursor.dir/vnd." + BuildConfig.CONTENT_PROVIDER_AUTHORITY + "." + STICKERS; + case STICKERS_ASSET_CODE: + return "image/webp"; + case STICKER_PACK_TRAY_ICON_CODE: + return "image/png"; + default: + throw new IllegalArgumentException("Unknown URI: " + uri); + } + } + + private synchronized void readContentFile(@NonNull Context context) { + try (InputStream contentsInputStream = context.getAssets().open(CONTENT_FILE_NAME)) { + stickerPackList = ContentFileParser.parseStickerPacks(contentsInputStream); + } catch (IOException | IllegalStateException e) { + throw new RuntimeException(CONTENT_FILE_NAME + " file has some issues: " + e.getMessage(), e); + } + } + + public List getStickerPackList() { + if (stickerPackList == null) { + readContentFile(Objects.requireNonNull(getContext())); + } + return stickerPackList; + } + + private Cursor getPackForAllStickerPacks(@NonNull Uri uri) { + return getStickerPackInfo(uri, getStickerPackList()); + } + + private Cursor getCursorForSingleStickerPack(@NonNull Uri uri) { + final String identifier = uri.getLastPathSegment(); + for (StickerPack stickerPack : getStickerPackList()) { + if (identifier.equals(stickerPack.identifier)) { + return getStickerPackInfo(uri, Collections.singletonList(stickerPack)); + } + } + + return getStickerPackInfo(uri, new ArrayList<>()); + } + + @NonNull + private Cursor getStickerPackInfo(@NonNull Uri uri, @NonNull List stickerPackList) { + MatrixCursor cursor = new MatrixCursor( + new String[]{ + STICKER_PACK_IDENTIFIER_IN_QUERY, + STICKER_PACK_NAME_IN_QUERY, + STICKER_PACK_PUBLISHER_IN_QUERY, + STICKER_PACK_ICON_IN_QUERY, + ANDROID_APP_DOWNLOAD_LINK_IN_QUERY, + IOS_APP_DOWNLOAD_LINK_IN_QUERY, + PUBLISHER_EMAIL, + PUBLISHER_WEBSITE, + PRIVACY_POLICY_WEBSITE, + LICENSE_AGREENMENT_WEBSITE + }); + for (StickerPack stickerPack : stickerPackList) { + MatrixCursor.RowBuilder builder = cursor.newRow(); + builder.add(stickerPack.identifier); + builder.add(stickerPack.name); + builder.add(stickerPack.publisher); + builder.add(stickerPack.trayImageFile); + builder.add(stickerPack.androidPlayStoreLink); + builder.add(stickerPack.iosAppStoreLink); + builder.add(stickerPack.publisherEmail); + builder.add(stickerPack.publisherWebsite); + builder.add(stickerPack.privacyPolicyWebsite); + builder.add(stickerPack.licenseAgreementWebsite); + } + cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri); + return cursor; + } + + @NonNull + private Cursor getStickersForAStickerPack(@NonNull Uri uri) { + final String identifier = uri.getLastPathSegment(); + MatrixCursor cursor = new MatrixCursor(new String[]{STICKER_FILE_NAME_IN_QUERY, STICKER_FILE_EMOJI_IN_QUERY}); + for (StickerPack stickerPack : getStickerPackList()) { + if (identifier.equals(stickerPack.identifier)) { + for (Sticker sticker : stickerPack.getStickers()) { + cursor.addRow(new Object[]{sticker.imageFileName, TextUtils.join(",", sticker.emojis)}); + } + } + } + cursor.setNotificationUri(Objects.requireNonNull(getContext()).getContentResolver(), uri); + return cursor; + } + + private AssetFileDescriptor getImageAsset(Uri uri) throws IllegalArgumentException { + AssetManager am = Objects.requireNonNull(getContext()).getAssets(); + final List pathSegments = uri.getPathSegments(); + if (pathSegments.size() != 3) { + throw new IllegalArgumentException("path segments should be 3, uri is: " + uri); + } + String fileName = pathSegments.get(pathSegments.size() - 1); + final String identifier = pathSegments.get(pathSegments.size() - 2); + if (TextUtils.isEmpty(identifier)) { + throw new IllegalArgumentException("identifier is empty, uri: " + uri); + } + if (TextUtils.isEmpty(fileName)) { + throw new IllegalArgumentException("file name is empty, uri: " + uri); + } + //making sure the file that is trying to be fetched is in the list of stickers. + for (StickerPack stickerPack : getStickerPackList()) { + if (identifier.equals(stickerPack.identifier)) { + if (fileName.equals(stickerPack.trayImageFile)) { + return fetchFile(uri, am, fileName, identifier); + } else { + for (Sticker sticker : stickerPack.getStickers()) { + if (fileName.equals(sticker.imageFileName)) { + return fetchFile(uri, am, fileName, identifier); + } + } + } + } + } + return null; + } + + private AssetFileDescriptor fetchFile(@NonNull Uri uri, @NonNull AssetManager am, @NonNull String fileName, @NonNull String identifier) { + try { + return am.openFd(identifier + "/" + fileName); + } catch (IOException e) { + Log.e(Objects.requireNonNull(getContext()).getPackageName(), "IOException when getting asset file, uri:" + uri, e); + return null; + } + } + + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + throw new UnsupportedOperationException("Not supported"); + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPack.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPack.java new file mode 100644 index 0000000..31c51a5 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPack.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +class StickerPack implements Parcelable { + String identifier; + String name; + String publisher; + String trayImageFile; + final String publisherEmail; + final String publisherWebsite; + final String privacyPolicyWebsite; + final String licenseAgreementWebsite; + + String iosAppStoreLink; + private List stickers; + private long totalSize; + String androidPlayStoreLink; + private boolean isWhitelisted; + + StickerPack(String identifier, String name, String publisher, String trayImageFile, String publisherEmail, String publisherWebsite, String privacyPolicyWebsite, String licenseAgreementWebsite) { + this.identifier = identifier; + this.name = name; + this.publisher = publisher; + this.trayImageFile = trayImageFile; + this.publisherEmail = publisherEmail; + this.publisherWebsite = publisherWebsite; + this.privacyPolicyWebsite = privacyPolicyWebsite; + this.licenseAgreementWebsite = licenseAgreementWebsite; + } + + void setIsWhitelisted(boolean isWhitelisted) { + this.isWhitelisted = isWhitelisted; + } + + boolean getIsWhitelisted() { + return isWhitelisted; + } + + protected StickerPack(Parcel in) { + identifier = in.readString(); + name = in.readString(); + publisher = in.readString(); + trayImageFile = in.readString(); + publisherEmail = in.readString(); + publisherWebsite = in.readString(); + privacyPolicyWebsite = in.readString(); + licenseAgreementWebsite = in.readString(); + iosAppStoreLink = in.readString(); + stickers = in.createTypedArrayList(Sticker.CREATOR); + totalSize = in.readLong(); + androidPlayStoreLink = in.readString(); + isWhitelisted = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public StickerPack createFromParcel(Parcel in) { + return new StickerPack(in); + } + + @Override + public StickerPack[] newArray(int size) { + return new StickerPack[size]; + } + }; + + void setStickers(List stickers) { + this.stickers = stickers; + totalSize = 0; + for (Sticker sticker : stickers) { + totalSize += sticker.size; + } + } + + public void setAndroidPlayStoreLink(String androidPlayStoreLink) { + this.androidPlayStoreLink = androidPlayStoreLink; + } + + public void setIosAppStoreLink(String iosAppStoreLink) { + this.iosAppStoreLink = iosAppStoreLink; + } + + public List getStickers() { + return stickers; + } + + public long getTotalSize() { + return totalSize; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(identifier); + dest.writeString(name); + dest.writeString(publisher); + dest.writeString(trayImageFile); + dest.writeString(publisherEmail); + dest.writeString(publisherWebsite); + dest.writeString(privacyPolicyWebsite); + dest.writeString(licenseAgreementWebsite); + dest.writeString(iosAppStoreLink); + dest.writeTypedList(stickers); + dest.writeLong(totalSize); + dest.writeString(androidPlayStoreLink); + dest.writeByte((byte) (isWhitelisted ? 1 : 0)); + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPackDetailsActivity.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackDetailsActivity.java new file mode 100644 index 0000000..22409fa --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackDetailsActivity.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.format.Formatter; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.ImageView; +import android.widget.TextView; + +import com.geekofia.whatsappstickers.R; + +import java.lang.ref.WeakReference; + +public class StickerPackDetailsActivity extends AddStickerPackActivity { + + /** + * Do not change below values of below 3 lines as this is also used by WhatsApp + */ + public static final String EXTRA_STICKER_PACK_ID = "sticker_pack_id"; + public static final String EXTRA_STICKER_PACK_AUTHORITY = "sticker_pack_authority"; + public static final String EXTRA_STICKER_PACK_NAME = "sticker_pack_name"; + + public static final String EXTRA_STICKER_PACK_WEBSITE = "sticker_pack_website"; + public static final String EXTRA_STICKER_PACK_EMAIL = "sticker_pack_email"; + public static final String EXTRA_STICKER_PACK_PRIVACY_POLICY = "sticker_pack_privacy_policy"; + public static final String EXTRA_STICKER_PACK_TRAY_ICON = "sticker_pack_tray_icon"; + public static final String EXTRA_SHOW_UP_BUTTON = "show_up_button"; + public static final String EXTRA_STICKER_PACK_DATA = "sticker_pack"; + + private RecyclerView recyclerView; + private GridLayoutManager layoutManager; + private StickerPreviewAdapter stickerPreviewAdapter; + private int numColumns; + private View addButton; + private View alreadyAddedText; + private StickerPack stickerPack; + private View divider; + private WhiteListCheckAsyncTask whiteListCheckAsyncTask; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.sticker_pack_details); + boolean showUpButton = getIntent().getBooleanExtra(EXTRA_SHOW_UP_BUTTON, false); + stickerPack = getIntent().getParcelableExtra(EXTRA_STICKER_PACK_DATA); + TextView packNameTextView = findViewById(R.id.pack_name); + TextView packPublisherTextView = findViewById(R.id.author); + ImageView packTrayIcon = findViewById(R.id.tray_image); + TextView packSizeTextView = findViewById(R.id.pack_size); + + addButton = findViewById(R.id.add_to_whatsapp_button); + alreadyAddedText = findViewById(R.id.already_added_text); + layoutManager = new GridLayoutManager(this, 1); + recyclerView = findViewById(R.id.sticker_list); + recyclerView.setLayoutManager(layoutManager); + recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(pageLayoutListener); + recyclerView.addOnScrollListener(dividerScrollListener); + divider = findViewById(R.id.divider); + if (stickerPreviewAdapter == null) { + stickerPreviewAdapter = new StickerPreviewAdapter(getLayoutInflater(), R.drawable.sticker_error, getResources().getDimensionPixelSize(R.dimen.sticker_pack_details_image_size), getResources().getDimensionPixelSize(R.dimen.sticker_pack_details_image_padding), stickerPack); + recyclerView.setAdapter(stickerPreviewAdapter); + } + packNameTextView.setText(stickerPack.name); + packPublisherTextView.setText(stickerPack.publisher); + packTrayIcon.setImageURI(StickerPackLoader.getStickerAssetUri(stickerPack.identifier, stickerPack.trayImageFile)); + packSizeTextView.setText(Formatter.formatShortFileSize(this, stickerPack.getTotalSize())); + addButton.setOnClickListener(v -> addStickerPackToWhatsApp(stickerPack.identifier, stickerPack.name)); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(showUpButton); + getSupportActionBar().setTitle(showUpButton ? R.string.title_activity_sticker_pack_details_multiple_pack : R.string.title_activity_sticker_pack_details_single_pack); + } + } + + private void launchInfoActivity(String publisherWebsite, String publisherEmail, String privacyPolicyWebsite, String trayIconUriString) { + Intent intent = new Intent(StickerPackDetailsActivity.this, StickerPackInfoActivity.class); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_ID, stickerPack.identifier); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_WEBSITE, publisherWebsite); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_EMAIL, publisherEmail); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_PRIVACY_POLICY, privacyPolicyWebsite); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_TRAY_ICON, trayIconUriString); + startActivity(intent); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.toolbar, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_info && stickerPack != null) { + final String publisherWebsite = stickerPack.publisherWebsite; + final String publisherEmail = stickerPack.publisherEmail; + final String privacyPolicyWebsite = stickerPack.privacyPolicyWebsite; + Uri trayIconUri = StickerPackLoader.getStickerAssetUri(stickerPack.identifier, stickerPack.trayImageFile); + launchInfoActivity(publisherWebsite, publisherEmail, privacyPolicyWebsite, trayIconUri.toString()); + return true; + } + return super.onOptionsItemSelected(item); + } + + + + private final ViewTreeObserver.OnGlobalLayoutListener pageLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + setNumColumns(recyclerView.getWidth() / recyclerView.getContext().getResources().getDimensionPixelSize(R.dimen.sticker_pack_details_image_size)); + } + }; + + private void setNumColumns(int numColumns) { + if (this.numColumns != numColumns) { + layoutManager.setSpanCount(numColumns); + this.numColumns = numColumns; + if (stickerPreviewAdapter != null) { + stickerPreviewAdapter.notifyDataSetChanged(); + } + } + } + + private final RecyclerView.OnScrollListener dividerScrollListener = new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) { + super.onScrollStateChanged(recyclerView, newState); + updateDivider(recyclerView); + } + + @Override + public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { + super.onScrolled(recyclerView, dx, dy); + updateDivider(recyclerView); + } + + private void updateDivider(RecyclerView recyclerView) { + boolean showDivider = recyclerView.computeVerticalScrollOffset() > 0; + if (divider != null) { + divider.setVisibility(showDivider ? View.VISIBLE : View.INVISIBLE); + } + } + }; + + @Override + protected void onResume() { + super.onResume(); + whiteListCheckAsyncTask = new WhiteListCheckAsyncTask(this); + whiteListCheckAsyncTask.execute(stickerPack); + } + + @Override + protected void onPause() { + super.onPause(); + if (whiteListCheckAsyncTask != null && !whiteListCheckAsyncTask.isCancelled()) { + whiteListCheckAsyncTask.cancel(true); + } + } + + private void updateAddUI(Boolean isWhitelisted) { + if (isWhitelisted) { + addButton.setVisibility(View.GONE); + alreadyAddedText.setVisibility(View.VISIBLE); + } else { + addButton.setVisibility(View.VISIBLE); + alreadyAddedText.setVisibility(View.GONE); + } + } + + static class WhiteListCheckAsyncTask extends AsyncTask { + private final WeakReference stickerPackDetailsActivityWeakReference; + + WhiteListCheckAsyncTask(StickerPackDetailsActivity stickerPackListActivity) { + this.stickerPackDetailsActivityWeakReference = new WeakReference<>(stickerPackListActivity); + } + + @Override + protected final Boolean doInBackground(StickerPack... stickerPacks) { + StickerPack stickerPack = stickerPacks[0]; + final StickerPackDetailsActivity stickerPackDetailsActivity = stickerPackDetailsActivityWeakReference.get(); + //noinspection SimplifiableIfStatement + if (stickerPackDetailsActivity == null) { + return false; + } + return WhitelistCheck.isWhitelisted(stickerPackDetailsActivity, stickerPack.identifier); + } + + @Override + protected void onPostExecute(Boolean isWhitelisted) { + final StickerPackDetailsActivity stickerPackDetailsActivity = stickerPackDetailsActivityWeakReference.get(); + if (stickerPackDetailsActivity != null) { + stickerPackDetailsActivity.updateAddUI(isWhitelisted); + } + } + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPackInfoActivity.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackInfoActivity.java new file mode 100644 index 0000000..3d0259a --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackInfoActivity.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.Intent; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.DrawableRes; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import com.geekofia.whatsappstickers.R; + +import java.io.FileNotFoundException; +import java.io.InputStream; + +public class StickerPackInfoActivity extends BaseActivity { + + private static final String TAG = "StickerPackInfoActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sticker_pack_info); + + final String trayIconUriString = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_TRAY_ICON); + final String website = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_WEBSITE); + final String email = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_EMAIL); + final String privacyPolicy = getIntent().getStringExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_PRIVACY_POLICY); + + final TextView trayIcon = findViewById(R.id.tray_icon); + try { + final InputStream inputStream = getContentResolver().openInputStream(Uri.parse(trayIconUriString)); + final BitmapDrawable trayDrawable = new BitmapDrawable(getResources(), inputStream); + final Drawable emailDrawable = getDrawableForAllAPIs(R.drawable.sticker_3rdparty_email); + trayDrawable.setBounds(new Rect(0, 0, emailDrawable.getIntrinsicWidth(), emailDrawable.getIntrinsicHeight())); + trayIcon.setCompoundDrawables(trayDrawable, null, null, null); + } catch (FileNotFoundException e) { + Log.e(TAG, "could not find the uri for the tray image:" + trayIconUriString); + } + + final TextView viewWebpage = findViewById(R.id.view_webpage); + if (TextUtils.isEmpty(website)) { + viewWebpage.setVisibility(View.GONE); + } else { + viewWebpage.setOnClickListener(v -> launchWebpage(website)); + } + + final TextView sendEmail = findViewById(R.id.send_email); + if (TextUtils.isEmpty(email)) { + sendEmail.setVisibility(View.GONE); + } else { + sendEmail.setOnClickListener(v -> launchEmailClient(email)); + } + + final TextView viewPrivacyPolicy = findViewById(R.id.privacy_policy); + if (TextUtils.isEmpty(privacyPolicy)) { + viewPrivacyPolicy.setVisibility(View.GONE); + } else { + viewPrivacyPolicy.setOnClickListener(v -> launchWebpage(privacyPolicy)); + } + } + + private void launchEmailClient(String email) { + Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts( + "mailto", email, null)); + emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{email}); + startActivity(Intent.createChooser(emailIntent, getResources().getString(R.string.info_send_email_to_prompt))); + } + + private void launchWebpage(String website) { + Uri uri = Uri.parse(website); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + } + + private Drawable getDrawableForAllAPIs(@DrawableRes int id) { + if (Build.VERSION.SDK_INT >= 21) { + return getDrawable(id); + } else { + return getResources().getDrawable(id); + } + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListActivity.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListActivity.java new file mode 100644 index 0000000..3dc5d4a --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListActivity.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; + +import com.geekofia.whatsappstickers.R; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +public class StickerPackListActivity extends AddStickerPackActivity { + public static final String EXTRA_STICKER_PACK_LIST_DATA = "sticker_pack_list"; + private static final int STICKER_PREVIEW_DISPLAY_LIMIT = 5; + private LinearLayoutManager packLayoutManager; + private RecyclerView packRecyclerView; + private StickerPackListAdapter allStickerPacksListAdapter; + private WhiteListCheckAsyncTask whiteListCheckAsyncTask; + private ArrayList stickerPackList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sticker_pack_list); + packRecyclerView = findViewById(R.id.sticker_pack_list); + stickerPackList = getIntent().getParcelableArrayListExtra(EXTRA_STICKER_PACK_LIST_DATA); + showStickerPackList(stickerPackList); + } + + @Override + protected void onResume() { + super.onResume(); + whiteListCheckAsyncTask = new WhiteListCheckAsyncTask(this); + whiteListCheckAsyncTask.execute(stickerPackList.toArray(new StickerPack[stickerPackList.size()])); + } + + @Override + protected void onPause() { + super.onPause(); + if (whiteListCheckAsyncTask != null && !whiteListCheckAsyncTask.isCancelled()) { + whiteListCheckAsyncTask.cancel(true); + } + } + + private void showStickerPackList(List stickerPackList) { + allStickerPacksListAdapter = new StickerPackListAdapter(stickerPackList, onAddButtonClickedListener); + packRecyclerView.setAdapter(allStickerPacksListAdapter); + packLayoutManager = new LinearLayoutManager(this); + packLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration( + packRecyclerView.getContext(), + packLayoutManager.getOrientation() + ); + packRecyclerView.addItemDecoration(dividerItemDecoration); + packRecyclerView.setLayoutManager(packLayoutManager); + packRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(this::recalculateColumnCount); + } + + + private final StickerPackListAdapter.OnAddButtonClickedListener onAddButtonClickedListener = pack -> { + addStickerPackToWhatsApp(pack.identifier, pack.name); + }; + + private void recalculateColumnCount() { + final int previewSize = getResources().getDimensionPixelSize(R.dimen.sticker_pack_list_item_preview_image_size); + int firstVisibleItemPosition = packLayoutManager.findFirstVisibleItemPosition(); + StickerPackListItemViewHolder viewHolder = (StickerPackListItemViewHolder) packRecyclerView.findViewHolderForAdapterPosition(firstVisibleItemPosition); + if (viewHolder != null) { + final int max = Math.max(viewHolder.imageRowView.getMeasuredWidth() / previewSize, 1); + int numColumns = Math.min(STICKER_PREVIEW_DISPLAY_LIMIT, max); + allStickerPacksListAdapter.setMaxNumberOfStickersInARow(numColumns); + } + } + + + static class WhiteListCheckAsyncTask extends AsyncTask> { + private final WeakReference stickerPackListActivityWeakReference; + + WhiteListCheckAsyncTask(StickerPackListActivity stickerPackListActivity) { + this.stickerPackListActivityWeakReference = new WeakReference<>(stickerPackListActivity); + } + + @Override + protected final List doInBackground(StickerPack... stickerPackArray) { + final StickerPackListActivity stickerPackListActivity = stickerPackListActivityWeakReference.get(); + if (stickerPackListActivity == null) { + return Arrays.asList(stickerPackArray); + } + for (StickerPack stickerPack : stickerPackArray) { + stickerPack.setIsWhitelisted(WhitelistCheck.isWhitelisted(stickerPackListActivity, stickerPack.identifier)); + } + return Arrays.asList(stickerPackArray); + } + + @Override + protected void onPostExecute(List stickerPackList) { + final StickerPackListActivity stickerPackListActivity = stickerPackListActivityWeakReference.get(); + if (stickerPackListActivity != null) { + stickerPackListActivity.allStickerPacksListAdapter.setStickerPackList(stickerPackList); + stickerPackListActivity.allStickerPacksListAdapter.notifyDataSetChanged(); + } + } + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListAdapter.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListAdapter.java new file mode 100644 index 0000000..d8c45a1 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListAdapter.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.text.format.Formatter; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.geekofia.whatsappstickers.R; +import com.facebook.drawee.view.SimpleDraweeView; + +import java.util.List; + +public class StickerPackListAdapter extends RecyclerView.Adapter { + @NonNull + private List stickerPacks; + @NonNull + private final OnAddButtonClickedListener onAddButtonClickedListener; + private int maxNumberOfStickersInARow; + + StickerPackListAdapter(@NonNull List stickerPacks, @NonNull OnAddButtonClickedListener onAddButtonClickedListener) { + this.stickerPacks = stickerPacks; + this.onAddButtonClickedListener = onAddButtonClickedListener; + } + + @NonNull + @Override + public StickerPackListItemViewHolder onCreateViewHolder(@NonNull final ViewGroup viewGroup, final int i) { + final Context context = viewGroup.getContext(); + final LayoutInflater layoutInflater = LayoutInflater.from(context); + final View stickerPackRow = layoutInflater.inflate(R.layout.sticker_packs_list_item, viewGroup, false); + return new StickerPackListItemViewHolder(stickerPackRow); + } + + @Override + public void onBindViewHolder(@NonNull final StickerPackListItemViewHolder viewHolder, final int index) { + StickerPack pack = stickerPacks.get(index); + final Context context = viewHolder.publisherView.getContext(); + viewHolder.publisherView.setText(pack.publisher); + viewHolder.filesizeView.setText(Formatter.formatShortFileSize(context, pack.getTotalSize())); + + viewHolder.titleView.setText(pack.name); + viewHolder.container.setOnClickListener(view -> { + Intent intent = new Intent(view.getContext(), StickerPackDetailsActivity.class); + intent.putExtra(StickerPackDetailsActivity.EXTRA_SHOW_UP_BUTTON, true); + intent.putExtra(StickerPackDetailsActivity.EXTRA_STICKER_PACK_DATA, pack); + view.getContext().startActivity(intent); + }); + viewHolder.imageRowView.removeAllViews(); + //if this sticker pack contains less stickers than the max, then take the smaller size. + int actualNumberOfStickersToShow = Math.min(maxNumberOfStickersInARow, pack.getStickers().size()); + for (int i = 0; i < actualNumberOfStickersToShow; i++) { + final SimpleDraweeView rowImage = (SimpleDraweeView) LayoutInflater.from(context).inflate(R.layout.sticker_pack_list_item_image, viewHolder.imageRowView, false); + rowImage.setImageURI(StickerPackLoader.getStickerAssetUri(pack.identifier, pack.getStickers().get(i).imageFileName)); + final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) rowImage.getLayoutParams(); + final int marginBetweenImages = (viewHolder.imageRowView.getMeasuredWidth() - maxNumberOfStickersInARow * viewHolder.imageRowView.getContext().getResources().getDimensionPixelSize(R.dimen.sticker_pack_list_item_preview_image_size)) / (maxNumberOfStickersInARow - 1) - lp.leftMargin - lp.rightMargin; + if (i != actualNumberOfStickersToShow - 1 && marginBetweenImages > 0) { //do not set the margin for the last image + lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin + marginBetweenImages, lp.bottomMargin); + rowImage.setLayoutParams(lp); + } + viewHolder.imageRowView.addView(rowImage); + } + setAddButtonAppearance(viewHolder.addButton, pack); + } + + private void setAddButtonAppearance(ImageView addButton, StickerPack pack) { + if (pack.getIsWhitelisted()) { + addButton.setImageResource(R.drawable.sticker_3rdparty_added); + addButton.setClickable(false); + addButton.setOnClickListener(null); + setBackground(addButton, null); + } else { + addButton.setImageResource(R.drawable.sticker_3rdparty_add); + addButton.setOnClickListener(v -> onAddButtonClickedListener.onAddButtonClicked(pack)); + TypedValue outValue = new TypedValue(); + addButton.getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); + addButton.setBackgroundResource(outValue.resourceId); + } + } + + private void setBackground(View view, Drawable background) { + if (Build.VERSION.SDK_INT >= 16) { + view.setBackground(background); + } else { + view.setBackgroundDrawable(background); + } + } + @Override + public int getItemCount() { + return stickerPacks.size(); + } + + void setMaxNumberOfStickersInARow(int maxNumberOfStickersInARow) { + if (this.maxNumberOfStickersInARow != maxNumberOfStickersInARow) { + this.maxNumberOfStickersInARow = maxNumberOfStickersInARow; + notifyDataSetChanged(); + } + } + + public void setStickerPackList(List stickerPackList) { + this.stickerPacks = stickerPackList; + } + + public interface OnAddButtonClickedListener { + void onAddButtonClicked(StickerPack stickerPack); + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListItemViewHolder.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListItemViewHolder.java new file mode 100644 index 0000000..f1dc9e8 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackListItemViewHolder.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.geekofia.whatsappstickers.R; + +public class StickerPackListItemViewHolder extends RecyclerView.ViewHolder { + + View container; + TextView titleView; + TextView publisherView; + TextView filesizeView; + ImageView addButton; + LinearLayout imageRowView; + + StickerPackListItemViewHolder(final View itemView) { + super(itemView); + container = itemView; + titleView = itemView.findViewById(R.id.sticker_pack_title); + publisherView = itemView.findViewById(R.id.sticker_pack_publisher); + filesizeView = itemView.findViewById(R.id.sticker_pack_filesize); + addButton = itemView.findViewById(R.id.add_button_on_list); + imageRowView = itemView.findViewById(R.id.sticker_packs_list_item_image_list); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPackLoader.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackLoader.java new file mode 100644 index 0000000..2f3c951 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackLoader.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; + +import com.geekofia.whatsappstickers.BuildConfig; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static com.geekofia.whatsappstickers.StickerContentProvider.ANDROID_APP_DOWNLOAD_LINK_IN_QUERY; +import static com.geekofia.whatsappstickers.StickerContentProvider.IOS_APP_DOWNLOAD_LINK_IN_QUERY; +import static com.geekofia.whatsappstickers.StickerContentProvider.LICENSE_AGREENMENT_WEBSITE; +import static com.geekofia.whatsappstickers.StickerContentProvider.PRIVACY_POLICY_WEBSITE; +import static com.geekofia.whatsappstickers.StickerContentProvider.PUBLISHER_EMAIL; +import static com.geekofia.whatsappstickers.StickerContentProvider.PUBLISHER_WEBSITE; +import static com.geekofia.whatsappstickers.StickerContentProvider.STICKER_FILE_EMOJI_IN_QUERY; +import static com.geekofia.whatsappstickers.StickerContentProvider.STICKER_FILE_NAME_IN_QUERY; +import static com.geekofia.whatsappstickers.StickerContentProvider.STICKER_PACK_ICON_IN_QUERY; +import static com.geekofia.whatsappstickers.StickerContentProvider.STICKER_PACK_IDENTIFIER_IN_QUERY; +import static com.geekofia.whatsappstickers.StickerContentProvider.STICKER_PACK_NAME_IN_QUERY; +import static com.geekofia.whatsappstickers.StickerContentProvider.STICKER_PACK_PUBLISHER_IN_QUERY; + +public class StickerPackLoader { + + /** + * Get the list of sticker packs for the sticker content provider + */ + @NonNull + public static ArrayList fetchStickerPacks(Context context) throws IllegalStateException { + final Cursor cursor = context.getContentResolver().query(StickerContentProvider.AUTHORITY_URI, null, null, null, null); + if (cursor == null) { + throw new IllegalStateException("could not fetch from content provider, " + BuildConfig.CONTENT_PROVIDER_AUTHORITY); + } + HashSet identifierSet = new HashSet<>(); + final ArrayList stickerPackList = fetchFromContentProvider(cursor); + for (StickerPack stickerPack : stickerPackList) { + if (identifierSet.contains(stickerPack.identifier)) { + throw new IllegalStateException("sticker pack identifiers should be unique, there are more than one pack with identifier:" + stickerPack.identifier); + } else { + identifierSet.add(stickerPack.identifier); + } + } + if (stickerPackList.isEmpty()) { + throw new IllegalStateException("There should be at least one sticker pack in the app"); + } + for (StickerPack stickerPack : stickerPackList) { + final List stickers = getStickersForPack(context, stickerPack); + stickerPack.setStickers(stickers); + StickerPackValidator.verifyStickerPackValidity(context, stickerPack); + } + return stickerPackList; + } + + @NonNull + private static List getStickersForPack(Context context, StickerPack stickerPack) { + final List stickers = fetchFromContentProviderForStickers(stickerPack.identifier, context.getContentResolver()); + for (Sticker sticker : stickers) { + final byte[] bytes; + try { + bytes = fetchStickerAsset(stickerPack.identifier, sticker.imageFileName, context.getContentResolver()); + if (bytes.length <= 0) { + throw new IllegalStateException("Asset file is empty, pack: " + stickerPack.name + ", sticker: " + sticker.imageFileName); + } + sticker.setSize(bytes.length); + } catch (IOException | IllegalArgumentException e) { + throw new IllegalStateException("Asset file doesn't exist. pack: " + stickerPack.name + ", sticker: " + sticker.imageFileName, e); + } + } + return stickers; + } + + + @NonNull + private static ArrayList fetchFromContentProvider(Cursor cursor) { + ArrayList stickerPackList = new ArrayList<>(); + cursor.moveToFirst(); + do { + final String identifier = cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_IDENTIFIER_IN_QUERY)); + final String name = cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_NAME_IN_QUERY)); + final String publisher = cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_PUBLISHER_IN_QUERY)); + final String trayImage = cursor.getString(cursor.getColumnIndexOrThrow(STICKER_PACK_ICON_IN_QUERY)); + final String androidPlayStoreLink = cursor.getString(cursor.getColumnIndexOrThrow(ANDROID_APP_DOWNLOAD_LINK_IN_QUERY)); + final String iosAppLink = cursor.getString(cursor.getColumnIndexOrThrow(IOS_APP_DOWNLOAD_LINK_IN_QUERY)); + final String publisherEmail = cursor.getString(cursor.getColumnIndexOrThrow(PUBLISHER_EMAIL)); + final String publisherWebsite = cursor.getString(cursor.getColumnIndexOrThrow(PUBLISHER_WEBSITE)); + final String privacyPolicyWebsite = cursor.getString(cursor.getColumnIndexOrThrow(PRIVACY_POLICY_WEBSITE)); + final String licenseAgreementWebsite = cursor.getString(cursor.getColumnIndexOrThrow(LICENSE_AGREENMENT_WEBSITE)); + final StickerPack stickerPack = new StickerPack(identifier, name, publisher, trayImage, publisherEmail, publisherWebsite, privacyPolicyWebsite, licenseAgreementWebsite); + stickerPack.setAndroidPlayStoreLink(androidPlayStoreLink); + stickerPack.setIosAppStoreLink(iosAppLink); + stickerPackList.add(stickerPack); + } while (cursor.moveToNext()); + return stickerPackList; + } + + @NonNull + private static List fetchFromContentProviderForStickers(String identifier, ContentResolver contentResolver) { + Uri uri = getStickerListUri(identifier); + + final String[] projection = {STICKER_FILE_NAME_IN_QUERY, STICKER_FILE_EMOJI_IN_QUERY}; + final Cursor cursor = contentResolver.query(uri, projection, null, null, null); + List stickers = new ArrayList<>(); + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + do { + final String name = cursor.getString(cursor.getColumnIndexOrThrow(STICKER_FILE_NAME_IN_QUERY)); + final String emojisConcatenated = cursor.getString(cursor.getColumnIndexOrThrow(STICKER_FILE_EMOJI_IN_QUERY)); + stickers.add(new Sticker(name, Arrays.asList(emojisConcatenated.split(",")))); + } while (cursor.moveToNext()); + } + if (cursor != null) { + cursor.close(); + } + return stickers; + } + + public static byte[] fetchStickerAsset(@NonNull final String identifier, @NonNull final String name, ContentResolver contentResolver) throws IOException { + try (final InputStream inputStream = contentResolver.openInputStream(getStickerAssetUri(identifier, name)); + final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + if (inputStream == null) { + throw new IOException("cannot read sticker asset:" + identifier + "/" + name); + } + int read; + byte[] data = new byte[16384]; + + while ((read = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, read); + } + return buffer.toByteArray(); + } + } + + private static Uri getStickerListUri(String identifier) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.STICKERS).appendPath(identifier).build(); + } + + public static Uri getStickerAssetUri(String identifier, String stickerName) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.STICKERS_ASSET).appendPath(identifier).appendPath(stickerName).build(); + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPackValidator.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackValidator.java new file mode 100644 index 0000000..bfc7040 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPackValidator.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import android.util.Patterns; +import android.webkit.URLUtil; + +import com.facebook.animated.webp.WebPImage; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +public class StickerPackValidator { + private static final int STICKER_FILE_SIZE_LIMIT_KB = 100; + private static final int EMOJI_LIMIT = 3; + private static final int IMAGE_HEIGHT = 512; + private static final int IMAGE_WIDTH = 512; + private static final int STICKER_SIZE_MIN = 3; + private static final int STICKER_SIZE_MAX = 30; + private static final int CHAR_COUNT_MAX = 128; + private static final long ONE_KIBIBYTE = 8 * 1024; + private static final int TRAY_IMAGE_FILE_SIZE_MAX_KB = 50; + private static final int TRAY_IMAGE_DIMENSION_MIN = 24; + private static final int TRAY_IMAGE_DIMENSION_MAX = 512; + private static final String PLAY_STORE_DOMAIN = "play.google.com"; + private static final String APPLE_STORE_DOMAIN = "itunes.apple.com"; + + + /** + * Checks whether a sticker pack contains valid data + */ + static void verifyStickerPackValidity(@NonNull Context context, @NonNull StickerPack stickerPack) throws IllegalStateException { + if (TextUtils.isEmpty(stickerPack.identifier)) { + throw new IllegalStateException("sticker pack identifier is empty"); + } + if (stickerPack.identifier.length() > CHAR_COUNT_MAX) { + throw new IllegalStateException("sticker pack identifier cannot exceed " + CHAR_COUNT_MAX + " characters"); + } + checkStringValidity(stickerPack.identifier); + if (TextUtils.isEmpty(stickerPack.publisher)) { + throw new IllegalStateException("sticker pack publisher is empty, sticker pack identifier:" + stickerPack.identifier); + } + if (stickerPack.publisher.length() > CHAR_COUNT_MAX) { + throw new IllegalStateException("sticker pack publisher cannot exceed " + CHAR_COUNT_MAX + " characters, sticker pack identifier:" + stickerPack.identifier); + } + if (TextUtils.isEmpty(stickerPack.name)) { + throw new IllegalStateException("sticker pack name is empty, sticker pack identifier:" + stickerPack.identifier); + } + if (stickerPack.name.length() > CHAR_COUNT_MAX) { + throw new IllegalStateException("sticker pack name cannot exceed " + CHAR_COUNT_MAX + " characters, sticker pack identifier:" + stickerPack.identifier); + } + if (TextUtils.isEmpty(stickerPack.trayImageFile)) { + throw new IllegalStateException("sticker pack tray id is empty, sticker pack identifier:" + stickerPack.identifier); + } + if (!TextUtils.isEmpty(stickerPack.androidPlayStoreLink) && !isValidWebsiteUrl(stickerPack.androidPlayStoreLink)) { + throw new IllegalStateException("Make sure to include http or https in url links, android play store link is not a valid url: " + stickerPack.androidPlayStoreLink); + } + if (!TextUtils.isEmpty(stickerPack.androidPlayStoreLink) && !isURLInCorrectDomain(stickerPack.androidPlayStoreLink, PLAY_STORE_DOMAIN)) { + throw new IllegalStateException("android play store link should use play store domain: " + PLAY_STORE_DOMAIN); + } + if (!TextUtils.isEmpty(stickerPack.iosAppStoreLink) && !isValidWebsiteUrl(stickerPack.iosAppStoreLink)) { + throw new IllegalStateException("Make sure to include http or https in url links, ios app store link is not a valid url: " + stickerPack.iosAppStoreLink); + } + if (!TextUtils.isEmpty(stickerPack.iosAppStoreLink) && !isURLInCorrectDomain(stickerPack.iosAppStoreLink, APPLE_STORE_DOMAIN)) { + throw new IllegalStateException("iOS app store link should use app store domain: " + APPLE_STORE_DOMAIN); + } + if (!TextUtils.isEmpty(stickerPack.licenseAgreementWebsite) && !isValidWebsiteUrl(stickerPack.licenseAgreementWebsite)) { + throw new IllegalStateException("Make sure to include http or https in url links, license agreement link is not a valid url: " + stickerPack.licenseAgreementWebsite); + } + if (!TextUtils.isEmpty(stickerPack.privacyPolicyWebsite) && !isValidWebsiteUrl(stickerPack.privacyPolicyWebsite)) { + throw new IllegalStateException("Make sure to include http or https in url links, privacy policy link is not a valid url: " + stickerPack.privacyPolicyWebsite); + } + if (!TextUtils.isEmpty(stickerPack.publisherWebsite) && !isValidWebsiteUrl(stickerPack.publisherWebsite)) { + throw new IllegalStateException("Make sure to include http or https in url links, publisher website link is not a valid url: " + stickerPack.publisherWebsite); + } + if (!TextUtils.isEmpty(stickerPack.publisherEmail) && !Patterns.EMAIL_ADDRESS.matcher(stickerPack.publisherEmail).matches()) { + throw new IllegalStateException("publisher email does not seem valid, email is: " + stickerPack.publisherEmail); + } + try { + final byte[] bytes = StickerPackLoader.fetchStickerAsset(stickerPack.identifier, stickerPack.trayImageFile, context.getContentResolver()); + if (bytes.length > TRAY_IMAGE_FILE_SIZE_MAX_KB * ONE_KIBIBYTE) { + throw new IllegalStateException("tray image should be less than " + TRAY_IMAGE_FILE_SIZE_MAX_KB + " KB, tray image file: " + stickerPack.trayImageFile); + } + Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + if (bitmap.getHeight() > TRAY_IMAGE_DIMENSION_MAX || bitmap.getHeight() < TRAY_IMAGE_DIMENSION_MIN) { + throw new IllegalStateException("tray image height should between " + TRAY_IMAGE_DIMENSION_MIN + " and " + TRAY_IMAGE_DIMENSION_MAX + " pixels, current tray image height is " + bitmap.getHeight() + ", tray image file: " + stickerPack.trayImageFile); + } + if (bitmap.getWidth() > TRAY_IMAGE_DIMENSION_MAX || bitmap.getWidth() < TRAY_IMAGE_DIMENSION_MIN) { + throw new IllegalStateException("tray image width should be between " + TRAY_IMAGE_DIMENSION_MIN + " and " + TRAY_IMAGE_DIMENSION_MAX + " pixels, current tray image width is " + bitmap.getWidth() + ", tray image file: " + stickerPack.trayImageFile); + } + } catch (IOException e) { + throw new IllegalStateException("Cannot open tray image, " + stickerPack.trayImageFile, e); + } + final List stickers = stickerPack.getStickers(); + if (stickers.size() < STICKER_SIZE_MIN || stickers.size() > STICKER_SIZE_MAX) { + throw new IllegalStateException("sticker pack sticker count should be between 3 to 30 inclusive, it currently has " + stickers.size() + ", sticker pack identifier:" + stickerPack.identifier); + } + for (final Sticker sticker : stickers) { + validateSticker(context, stickerPack.identifier, sticker); + } + } + + private static void validateSticker(@NonNull Context context, @NonNull final String identifier, @NonNull final Sticker sticker) throws IllegalStateException { + if (sticker.emojis.size() > EMOJI_LIMIT) { + throw new IllegalStateException("emoji count exceed limit, sticker pack identifier:" + identifier + ", filename:" + sticker.imageFileName); + } + if (TextUtils.isEmpty(sticker.imageFileName)) { + throw new IllegalStateException("no file path for sticker, sticker pack identifier:" + identifier); + } + validateStickerFile(context, identifier, sticker.imageFileName); + } + + private static void validateStickerFile(@NonNull Context context, @NonNull String identifier, @NonNull final String fileName) throws IllegalStateException { + try { + final byte[] bytes = StickerPackLoader.fetchStickerAsset(identifier, fileName, context.getContentResolver()); + if (bytes.length > STICKER_FILE_SIZE_LIMIT_KB * ONE_KIBIBYTE) { + throw new IllegalStateException("sticker should be less than " + STICKER_FILE_SIZE_LIMIT_KB + "KB, sticker pack identifier:" + identifier + ", filename:" + fileName); + } + try { + final WebPImage webPImage = WebPImage.create(bytes); + if (webPImage.getHeight() != IMAGE_HEIGHT) { + throw new IllegalStateException("sticker height should be " + IMAGE_HEIGHT + ", sticker pack identifier:" + identifier + ", filename:" + fileName); + } + if (webPImage.getWidth() != IMAGE_WIDTH) { + throw new IllegalStateException("sticker width should be " + IMAGE_WIDTH + ", sticker pack identifier:" + identifier + ", filename:" + fileName); + } + if (webPImage.getFrameCount() > 1) { + throw new IllegalStateException("sticker shoud be a static image, no animated sticker support at the moment, sticker pack identifier:" + identifier + ", filename:" + fileName); + } + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Error parsing webp image, sticker pack identifier:" + identifier + ", filename:" + fileName, e); + } + } catch (IOException e) { + throw new IllegalStateException("cannot open sticker file: sticker pack identifier:" + identifier + ", filename:" + fileName, e); + } + } + + private static void checkStringValidity(@NonNull String string) { + String pattern = "[\\w-.,'\\s]+"; // [a-zA-Z0-9_-.' ] + if (!string.matches(pattern)) { + throw new IllegalStateException(string + " contains invalid characters, allowed characters are a to z, A to Z, _ , ' - . and space character"); + } + if (string.contains("..")) { + throw new IllegalStateException(string + " cannot contain .."); + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean isValidWebsiteUrl(String websiteUrl) throws IllegalStateException { + try { + new URL(websiteUrl); + } catch (MalformedURLException e) { + Log.e("StickerPackValidator", "url: " + websiteUrl + " is malformed"); + throw new IllegalStateException("url: " + websiteUrl + " is malformed", e); + } + return URLUtil.isHttpUrl(websiteUrl) || URLUtil.isHttpsUrl(websiteUrl); + + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean isURLInCorrectDomain(String urlString, String domain) throws IllegalStateException { + try { + URL url = new URL(urlString); + if (domain.equals(url.getHost())) { + return true; + } + } catch (MalformedURLException e) { + Log.e("StickerPackValidator", "url: " + urlString + " is malformed"); + throw new IllegalStateException("url: " + urlString + " is malformed"); + } + return false; + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPreviewAdapter.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPreviewAdapter.java new file mode 100644 index 0000000..b5af88f --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPreviewAdapter.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.geekofia.whatsappstickers.R; + +public class StickerPreviewAdapter extends RecyclerView.Adapter { + + @NonNull + private StickerPack stickerPack; + + private final int cellSize; + private int cellLimit; + private int cellPadding; + private final int errorResource; + + private final LayoutInflater layoutInflater; + + StickerPreviewAdapter( + @NonNull final LayoutInflater layoutInflater, + final int errorResource, + final int cellSize, + final int cellPadding, + @NonNull final StickerPack stickerPack) { + this.cellSize = cellSize; + this.cellPadding = cellPadding; + this.cellLimit = 0; + this.layoutInflater = layoutInflater; + this.errorResource = errorResource; + this.stickerPack = stickerPack; + } + + @NonNull + @Override + public StickerPreviewViewHolder onCreateViewHolder(@NonNull final ViewGroup viewGroup, final int i) { + View itemView = layoutInflater.inflate(R.layout.sticker_image, viewGroup, false); + StickerPreviewViewHolder vh = new StickerPreviewViewHolder(itemView); + + ViewGroup.LayoutParams layoutParams = vh.stickerPreviewView.getLayoutParams(); + layoutParams.height = cellSize; + layoutParams.width = cellSize; + vh.stickerPreviewView.setLayoutParams(layoutParams); + vh.stickerPreviewView.setPadding(cellPadding, cellPadding, cellPadding, cellPadding); + + return vh; + } + + @Override + public void onBindViewHolder(@NonNull final StickerPreviewViewHolder stickerPreviewViewHolder, final int i) { + stickerPreviewViewHolder.stickerPreviewView.setImageResource(errorResource); + stickerPreviewViewHolder.stickerPreviewView.setImageURI(StickerPackLoader.getStickerAssetUri(stickerPack.identifier, stickerPack.getStickers().get(i).imageFileName)); + } + + @Override + public int getItemCount() { + int numberOfPreviewImagesInPack; + numberOfPreviewImagesInPack = stickerPack.getStickers().size(); + if (cellLimit > 0) { + return Math.min(numberOfPreviewImagesInPack, cellLimit); + } + return numberOfPreviewImagesInPack; + } +} diff --git a/app/src/main/java/com/geekofia/whatsappstickers/StickerPreviewViewHolder.java b/app/src/main/java/com/geekofia/whatsappstickers/StickerPreviewViewHolder.java new file mode 100644 index 0000000..d9307f0 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/StickerPreviewViewHolder.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.geekofia.whatsappstickers.R; +import com.facebook.drawee.view.SimpleDraweeView; + +public class StickerPreviewViewHolder extends RecyclerView.ViewHolder { + + public SimpleDraweeView stickerPreviewView; + + StickerPreviewViewHolder(final View itemView) { + super(itemView); + stickerPreviewView = itemView.findViewById(R.id.sticker_preview); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geekofia/whatsappstickers/WhitelistCheck.java b/app/src/main/java/com/geekofia/whatsappstickers/WhitelistCheck.java new file mode 100644 index 0000000..3815892 --- /dev/null +++ b/app/src/main/java/com/geekofia/whatsappstickers/WhitelistCheck.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) WhatsApp Inc. and its affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.geekofia.whatsappstickers; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; + +import com.geekofia.whatsappstickers.BuildConfig; + +@SuppressWarnings("FieldCanBeLocal") +public class WhitelistCheck { + private static final String AUTHORITY_QUERY_PARAM = "authority"; + private static final String IDENTIFIER_QUERY_PARAM = "identifier"; + private static String STICKER_APP_AUTHORITY = BuildConfig.CONTENT_PROVIDER_AUTHORITY; + public static String CONSUMER_WHATSAPP_PACKAGE_NAME = "com.whatsapp"; + public static String SMB_WHATSAPP_PACKAGE_NAME = "com.whatsapp.w4b"; + private static String CONTENT_PROVIDER = ".provider.sticker_whitelist_check"; + private static String QUERY_PATH = "is_whitelisted"; + private static String QUERY_RESULT_COLUMN_NAME = "result"; + + static boolean isWhitelisted(@NonNull Context context, @NonNull String identifier) { + try { + boolean consumerResult = isWhitelistedFromProvider(context, identifier, CONSUMER_WHATSAPP_PACKAGE_NAME); + boolean smbResult = isWhitelistedFromProvider(context, identifier, SMB_WHATSAPP_PACKAGE_NAME); + return consumerResult && smbResult; + } catch (Exception e) { + return false; + } + } + + private static boolean isWhitelistedFromProvider(@NonNull Context context, @NonNull String identifier, String whatsappPackageName) { + final PackageManager packageManager = context.getPackageManager(); + if (isPackageInstalled(whatsappPackageName, packageManager)) { + final String whatsappProviderAuthority = whatsappPackageName + CONTENT_PROVIDER; + final ProviderInfo providerInfo = packageManager.resolveContentProvider(whatsappProviderAuthority, PackageManager.GET_META_DATA); + // provider is not there. + if (providerInfo == null) { + return false; + } + final Uri queryUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(whatsappProviderAuthority).appendPath(QUERY_PATH).appendQueryParameter(AUTHORITY_QUERY_PARAM, STICKER_APP_AUTHORITY).appendQueryParameter(IDENTIFIER_QUERY_PARAM, identifier).build(); + try (final Cursor cursor = context.getContentResolver().query(queryUri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + final int whiteListResult = cursor.getInt(cursor.getColumnIndexOrThrow(QUERY_RESULT_COLUMN_NAME)); + return whiteListResult == 1; + } + } + } else { + //if app is not installed, then don't need to take into its whitelist info into account. + return true; + } + return false; + } + + public static boolean isPackageInstalled(String packageName, PackageManager packageManager) { + try { + final ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); + //noinspection SimplifiableIfStatement + if (applicationInfo != null) { + return applicationInfo.enabled; + } else { + return false; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } +} diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_add.png new file mode 100644 index 0000000..13214f7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_added.png new file mode 100644 index 0000000..c30bdc2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_email.png new file mode 100644 index 0000000..373765a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_info.png new file mode 100644 index 0000000..2ee2db6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_privacy.png new file mode 100644 index 0000000..644821f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_wa.png new file mode 100644 index 0000000..0d856f4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-hdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-hdpi/sticker_3rdparty_web.png new file mode 100644 index 0000000..86ef3fa Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_add.png new file mode 100644 index 0000000..04e633e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_added.png new file mode 100644 index 0000000..0b64b96 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_email.png new file mode 100644 index 0000000..856e2c1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_info.png new file mode 100644 index 0000000..9eea757 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_privacy.png new file mode 100644 index 0000000..5c3ab69 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_wa.png new file mode 100644 index 0000000..d8f369b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-xhdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_web.png new file mode 100644 index 0000000..abe17d1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable-xxhdpi/btn_green_normal.9.png b/app/src/main/res/drawable-xxhdpi/btn_green_normal.9.png new file mode 100644 index 0000000..2132794 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/btn_green_normal.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/btn_green_pressed.9.png b/app/src/main/res/drawable-xxhdpi/btn_green_pressed.9.png new file mode 100644 index 0000000..00ff160 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/btn_green_pressed.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_add.png new file mode 100644 index 0000000..03c4381 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_added.png new file mode 100644 index 0000000..71c9532 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_email.png new file mode 100644 index 0000000..7a7e537 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_info.png new file mode 100644 index 0000000..ad780e6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_privacy.png new file mode 100644 index 0000000..e278a39 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_wa.png new file mode 100644 index 0000000..9f2902f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_web.png new file mode 100644 index 0000000..14e7dd2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable-xxhdpi/sticker_error.png b/app/src/main/res/drawable-xxhdpi/sticker_error.png new file mode 100644 index 0000000..a9a3e43 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/sticker_error.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_add.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_add.png new file mode 100644 index 0000000..c2f27cd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_add.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_added.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_added.png new file mode 100644 index 0000000..e5cb169 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_added.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_email.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_email.png new file mode 100644 index 0000000..d021790 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_email.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_info.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_info.png new file mode 100644 index 0000000..eebdb32 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_info.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_privacy.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_privacy.png new file mode 100644 index 0000000..004edcb Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_privacy.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_wa.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_wa.png new file mode 100644 index 0000000..c92fb83 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_wa.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_web.png b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_web.png new file mode 100644 index 0000000..955d021 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/sticker_3rdparty_web.png differ diff --git a/app/src/main/res/drawable/btn_green.xml b/app/src/main/res/drawable/btn_green.xml new file mode 100644 index 0000000..e59ed52 --- /dev/null +++ b/app/src/main/res/drawable/btn_green.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_entry.xml b/app/src/main/res/layout/activity_entry.xml new file mode 100644 index 0000000..91336dd --- /dev/null +++ b/app/src/main/res/layout/activity_entry.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sticker_pack_info.xml b/app/src/main/res/layout/activity_sticker_pack_info.xml new file mode 100644 index 0000000..f3562d9 --- /dev/null +++ b/app/src/main/res/layout/activity_sticker_pack_info.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_sticker_pack_list.xml b/app/src/main/res/layout/activity_sticker_pack_list.xml new file mode 100644 index 0000000..c2ec8c5 --- /dev/null +++ b/app/src/main/res/layout/activity_sticker_pack_list.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/app/src/main/res/layout/sticker_image.xml b/app/src/main/res/layout/sticker_image.xml new file mode 100644 index 0000000..34cfb0b --- /dev/null +++ b/app/src/main/res/layout/sticker_image.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/sticker_pack_details.xml b/app/src/main/res/layout/sticker_pack_details.xml new file mode 100644 index 0000000..32f9847 --- /dev/null +++ b/app/src/main/res/layout/sticker_pack_details.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/sticker_pack_list_item_image.xml b/app/src/main/res/layout/sticker_pack_list_item_image.xml new file mode 100644 index 0000000..6fc4822 --- /dev/null +++ b/app/src/main/res/layout/sticker_pack_list_item_image.xml @@ -0,0 +1,8 @@ + + diff --git a/app/src/main/res/layout/sticker_packs_list_item.xml b/app/src/main/res/layout/sticker_packs_list_item.xml new file mode 100644 index 0000000..b9da01d --- /dev/null +++ b/app/src/main/res/layout/sticker_packs_list_item.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/toolbar.xml b/app/src/main/res/menu/toolbar.xml new file mode 100644 index 0000000..ae5b319 --- /dev/null +++ b/app/src/main/res/menu/toolbar.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..0d9a470 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..0d9a470 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..ccb6ba2 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..ccb6ba2 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..a0e9b1f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..a0e9b1f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..3e04218 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3e04218 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..654068f Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..654068f Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..622ac8d --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,9 @@ + + + @android:color/transparent + #BABABA + #128C7E + #B3B3B3 + #EBEBEB + @color/colorAccent + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..bbd398d --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,11 @@ + + + 16dp + 16dp + 80dp + 8dp + 8dp + 50dp + 4dp + 4dp + diff --git a/app/src/main/res/values/refs.xml b/app/src/main/res/values/refs.xml new file mode 100644 index 0000000..07e3ca6 --- /dev/null +++ b/app/src/main/res/values/refs.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..a80e669 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,24 @@ + + WhatsApp Stickers + Sticker Pack Tray Image + More info about sticker pack + Add to WhatsApp + Add sticker pack to WhatsApp + WhatsApp Sticker Packs + + Sticker Packs + Sticker details + Info + View webpage + Send email + Privacy policy + Could not add this sticker pack. Please install the latest version of WhatsApp before adding sticker pack + Error: %s \nSee logcat for stack trace. + Error with the pack + Tray icon + Sticker pack added to WhatsApp + Send email with + Sticker pack not added. If you\'d like to add it, make sure you update to the latest version of WhatsApp. + Update + Google Play is not installed on the device. + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..7efff2d --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..df75d6a --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..efac220 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Oct 23 15:53:16 PDT 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/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 + +# 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\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +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"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # 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/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/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/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'