Warply Android SDK — AI Agent Skill File
Purpose: This file provides comprehensive context about the Warply Android SDK codebase for AI coding agents (Claude Code, Gemini CLI, Codex, OpenCode, Cline, etc.). It describes architecture, patterns, conventions, and critical rules to follow when working with this project.
1. Project Overview
- What: Warply Android SDK — a loyalty, marketing, and engagement SDK for Android apps. It provides campaigns, coupons, couponsets, push notifications, beacons, location tracking, analytics, and user management.
- Type: Android Library (AAR) published to Maven via Gradle.
-
Package namespace:
ly.warp.sdk - License: BSD-2-Clause (Warply Ltd.) — all source files must retain the license header.
-
Repository: Git-based (
warply_android_sdk_maven_plugin)
2. Project Structure
warply_android_sdk_maven_plugin/ # Root project
├── build.gradle # Root build config (AGP 8.7.3, plugins)
├── settings.gradle # Includes: app, warply_android_sdk
├── gradle.properties # Gradle/Maven properties
├── scripts/
│ ├── publish-module.gradle # Module publishing script
│ └── publish-root.gradle # Root publishing script
│
├── app/ # Demo/host application
│ ├── build.gradle
│ ├── src/main/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/warply.properties # SDK configuration file
│ │ └── java/warp/ly/android_sdk/
│ │ ├── WarplyAndroidSDKApplication.java
│ │ └── activities/
│ │ ├── BaseActivity.java
│ │ └── SplashActivity.java
│
├── warply_android_sdk/ # SDK Library Module (the main codebase)
│ ├── build.gradle # Library build config, dependencies, publishing
│ ├── proguard-rules.pro
│ └── src/main/
│ ├── AndroidManifest.xml # SDK manifest (permissions, services, receivers)
│ ├── java/ly/warp/sdk/
│ │ ├── Warply.java # ★ SDK Singleton entry point
│ │ ├── activities/ # UI Activities
│ │ ├── fragments/ # UI Fragments
│ │ ├── db/ # SQLite database layer
│ │ ├── dexter/ # Runtime permissions library (embedded)
│ │ ├── io/
│ │ │ ├── adapters/ # RecyclerView/ViewPager adapters
│ │ │ ├── callbacks/ # Async callback interfaces
│ │ │ ├── models/ # Data models (Campaign, Coupon, etc.)
│ │ │ ├── request/ # Custom Volley request types
│ │ │ └── volley/ # Custom Volley fork + Retrofit layer
│ │ ├── receivers/ # Broadcast receivers
│ │ ├── services/ # Background services (push, location, etc.)
│ │ ├── utils/ # Utility classes
│ │ │ ├── constants/ # WarpConstants, GCMConstants
│ │ │ └── managers/ # Business logic managers
│ │ └── views/ # Custom views and decorations
│ └── res/ # Resources (layouts, drawables, fonts, etc.)
3. Build Configuration
| Setting | Value |
|---|---|
| Android Gradle Plugin | 8.7.3 |
| compileSdkVersion | 35 |
| buildToolsVersion | 35.0.0 |
| minSdkVersion | 31 |
| targetSdkVersion | 35 |
| Namespace | ly.warp.sdk |
| Java version | Java 8+ (no Kotlin) |
| Publishing | Maven (single variant: release with sources) |
Key Dependencies
| Category | Library | Version/Notes |
|---|---|---|
| UI | androidx.appcompat:appcompat |
1.7.1 |
| UI | com.google.android.material:material |
1.13.0 |
| Security | androidx.security:security-crypto |
1.1.0 |
| Networking | com.squareup.retrofit2:retrofit |
3.0.0 |
| Networking | com.squareup.retrofit2:converter-gson |
3.0.0 |
| Networking | com.squareup.okhttp3:logging-interceptor |
5.1.0 |
| Networking | Custom Volley fork (embedded in source) | — |
| Reactive | io.reactivex.rxjava3:rxjava |
3.1.8 |
| Reactive | io.reactivex.rxjava3:rxandroid |
3.0.2 |
| Async | com.google.guava:guava |
33.0.0-android |
| Events | org.greenrobot:eventbus |
3.3.1 |
| Images | com.github.bumptech.glide:glide |
4.16.0 |
| Firebase | com.google.firebase:firebase-bom |
34.2.0 |
| Firebase | com.google.firebase:firebase-messaging |
(from BOM) |
| GMS | com.google.android.gms:play-services-base |
18.7.2 |
| GMS | com.google.android.gms:play-services-location |
21.3.0 |
| Huawei | com.huawei.agconnect:agconnect-core |
1.9.3.301 |
| Huawei | com.huawei.hms:push |
6.10.0.300 |
| Beacons | org.altbeacon:android-beacon-library |
2.19.3 |
| DB | androidx.sqlite:sqlite |
2.5.2 |
| Work | androidx.work:work-runtime |
2.10.3 |
| Lifecycle | androidx.lifecycle:lifecycle-extensions |
2.2.0 |
SDK Configuration
The SDK is configured at runtime via a warply.properties file placed in the host app's assets/ directory. Key properties include:
-
Uuid— Application UUID (identifies the app to the Warply backend) -
BaseURL— Backend server base URL -
Language— Default language -
Debug— Enable/disable debug logging -
MerchantId— Merchant identifier -
LoginType— Authentication type -
PushColor,PushIcon,PushSound— Push notification customization -
ProgressColor,ProgressDrawable— UI loading customization
Configuration is read via WarplyProperty.java which loads properties from the assets file.
4. Architecture & Core Patterns
4.1 SDK Entry Point — Warply.java (Singleton Enum)
public enum Warply {
INSTANCE;
// ...
}
-
Initialization:
Warply.getInitializer(context)orWarply.getInitializer(context, callback) - Uses
WarplyInitializerfor setup, which callsinitInternal()to configure the request queue, debug mode, server preferences, etc. -
Registration:
Warply.registerWarply()— registers the device with the Warply backend -
Context:
Warply.getWarplyContext()— returns the application context -
Request Queue: Internal Volley
RequestQueuefor legacy API calls -
Microapp Pattern: Data is posted/retrieved per "microapp" (e.g.,
device_info,application_data,inapp_analytics,offers, etc.)
Key Pattern — Public static → private internal delegation:
public static void someMethod() {
INSTANCE.isInitializedOrThrow();
INSTANCE.someMethodInternal();
}
private void someMethodInternal() { /* actual logic */ }
4.2 API Manager — WarplyManager.java
The primary class for all backend API calls. Uses static methods exclusively.
Key API Methods:
| Method | Returns | Description |
|--------|---------|-------------|
| login(email, receiver) | JSONObject | Authenticate user |
| logout(receiver) | JSONObject | Logout and clear tokens |
| getUser(receiver) | User | Fetch user profile |
| getCampaigns(receiver) | ArrayList<BannerItem> | Fetch campaigns + articles (parallel) |
| getCouponsets(receiver) | LinkedHashMap<String, ArrayList<Couponset>> | Fetch couponsets + merchants (parallel, categorized) |
| getUserCouponsWithCouponsets(receiver) | CouponList | Fetch user's redeemed coupons |
| getSingleCampaign(sessionUuid) | void | Trigger campaign session |
Async Pattern — Guava ListenableFuture for parallel requests:
ListenableFuture<ArrayList<Campaign>> futureCampaigns = getCampaignsRetro(service);
ListenableFuture<ArrayList<Content>> futureArticles = getArticlesRetro(service, categories);
ListenableFuture<List<Object>> allResultsFuture = Futures.allAsList(futureCampaigns, futureArticles);
ListenableFuture<ArrayList<BannerItem>> mergedResult = Futures.transformAsync(allResultsFuture, results -> {
// merge logic
}, executorService);
Futures.addCallback(mergedResult, new FutureCallback<>() {
public void onSuccess(result) { new Handler(Looper.getMainLooper()).post(() -> receiver.onSuccess(result)); }
public void onFailure(throwable) { new Handler(Looper.getMainLooper()).post(() -> receiver.onFailure(2)); }
}, executorService);
Token Refresh Pattern:
- On HTTP 401 → call
refreshToken()→ retry original request - Max 3 retries (
MAX_RETRIES = 3) - On refresh failure with 401 → clear auth tokens and fail
4.3 In-Memory Cache — WarplyManagerHelper.java
Static fields caching the latest fetched data:
-
mCouponRedeemedList— user's active coupons -
mCampaignListAll— all campaigns -
mBannerListAll— banner items (campaigns + articles merged) -
mCouponsetCategorizedMap— couponsets organized by category (LinkedHashMap<String, ArrayList<Couponset>>)
Important: These are set by WarplyManager after API calls and read by UI components.
4.4 Callback Interface
public interface CallbackReceiver<T> {
void onSuccess(T result);
void onFailure(int errorCode);
}
All async operations use this pattern. Error codes are defined in WarpConstants:
-
1= Success -
2= Generic error -
401= Unauthorized (triggers token refresh) -
-1= No internet -
-4= Not registered
5. Networking Layer
5.1 Retrofit (Modern API calls)
ApiClient.java — Singleton Retrofit instance:
- Base URL from
WarplyProperty.getBaseUrl() - OkHttp client with 30s connect/write/read timeouts
- GsonConverterFactory for serialization
ApiService.java — Retrofit interface defining all endpoints:
| Endpoint | Path | Auth |
|----------|------|------|
| Login | POST /partners/dei/app_login | No Bearer |
| Logout | POST /oauth/{appUuid}/logout | Bearer token |
| Logout (JWT) | POST /user/v5/{appUuid}/logout | Bearer token |
| Get User | POST /oauth/{appUuid}/context | Bearer token |
| Get Coupons | POST /oauth/{appUuid}/context | Bearer token |
| Get Campaigns | POST /api/mobile/v2/{appUuid}/context/ | Signature only |
| Get Articles | POST /api/mobile/v2/{appUuid}/context/ | Signature only |
| Get Campaigns (Personalized) | POST /oauth/{appUuid}/context | Bearer token |
| Get Couponsets | POST /api/mobile/v2/{appUuid}/context/ | Bearer token |
| Get Merchants | POST /api/mobile/v2/{appUuid}/context/ | Signature only |
Required Headers on all requests:
Content-Type: application/json
loyalty-date: <timestamp yyyy-MM-dd hh:mm:ss>
loyalty-bundle-id: android:<package_name>
unique-device-id: <device_unique_id>
channel: mobile
loyalty-web-id: <web_id>
loyalty-signature: SHA256(api_key + timestamp)
Authorization: Bearer <access_token> (when authenticated)
Signature Generation (WarpUtils.produceSignature):
SHA-256(apiKey + timestamp) → hex string
5.2 Custom Volley Fork (Legacy)
The project contains a full embedded copy of Volley under io/volley/ with custom modifications. This is used for:
- Legacy microapp data posting (batched requests from DB queue)
- Context retrieval
- Device registration
⚠️ Do NOT replace or upgrade this Volley fork — it contains custom request types (WarplyJsonObjectRequest, WarplyJsonArrayRequest) and batching logic specific to the SDK.
6. Data Layer
6.1 SQLite Database — WarplyDBHelper.java
Singleton pattern: WarplyDBHelper.getInstance(context)
| Table | Purpose | Key Columns |
|---|---|---|
requests |
Queued microapp data requests | microapp, entity, force_post, date_added |
push_requests |
Queued push notification events | microapp, entity, force_post, date_added |
push_ack_requests |
Queued push acknowledgments | microapp, entity, force_post, date_added |
client |
OAuth client credentials | client_id (encrypted), client_secret (encrypted) |
auth |
OAuth tokens | access_token (encrypted), refresh_token (encrypted) |
tags |
User tags | tag, last_add_date |
⚠️ CRITICAL: Auth tokens and client credentials are stored with field-level encryption using CryptoUtils.encrypt()/CryptoUtils.decrypt(). Always use these methods when reading/writing to the client and auth tables.
6.2 Encrypted SharedPreferences — WarpUtils.java
Preferences are stored using EncryptedSharedPreferences with:
-
MasterKey(AES256_GCM scheme) - Key encryption: AES256_SIV
- Value encryption: AES256_GCM
- Fallback to plain
SharedPreferenceson error
Key stored values: web_id, api_key, device_token, device_info, app_data, locale, dark_mode, JWT enabled flag, webview params.
7. Data Models
All models are in ly.warp.sdk.io.models and follow these conventions:
7.1 Model Pattern
public class ModelName implements Parcelable, Serializable {
private static final long serialVersionUID = ...;
// JSON key constants
private static final String FIELD_NAME = "json_key";
// Member variables
private String fieldName;
// Null-safe JSON helper
private static String optNullableString(JSONObject json, String key) {
return json.isNull(key) ? null : json.optString(key);
}
// Constructors: default, JSONObject, String (JSON), Parcel
public ModelName() { /* defaults */ }
public ModelName(JSONObject json) { /* parse from JSON */ }
public ModelName(String json) throws JSONException { this(new JSONObject(json)); }
public ModelName(Parcel source) { /* read from parcel */ }
// Serialization
public JSONObject toJSONObject() { /* to JSON */ }
public String toString() { return toJSONObject().toString(); }
public String toHumanReadableString() { return toJSONObject().toString(2); }
// Parcelable
public void writeToParcel(Parcel dest, int flags) { /* write */ }
public static final Creator<ModelName> CREATOR = ...;
// Getters and Setters
}
7.2 Key Models
| Model | Key Fields | Notes |
|---|---|---|
| Campaign | sessionUUID, title, subtitle, logoUrl, indexUrl, offerCategory, sorting, type, extraFields, actions, startDate, endDate | Represents loyalty campaigns |
| Couponset | uuid, name, description, imgPreview, merchantUuid, offerCategory, promoted, points, discount, availability, extraFields | Represents coupon offers |
| Coupon | barcode, coupon, couponsetUuid, merchantUuid, status, expiration, discount, couponsetDetails, merchantDetails, redeemDetails | User's acquired coupons |
| Merchant | uuid, name, address, logo, imgPreview, latitude, longitude, category, extraFields, merchantMetadata | Merchant/store info |
| User | (parsed from JSON result) | User profile data |
| BannerItem | Contains either Campaign or Content | Unified carousel item |
| Content | (articles/content items) | CMS content with imgPreview, extraFields |
| CouponList | Extends ArrayList<Coupon> | Typed list of coupons |
⚠️ JSON Handling: Models use org.json.JSONObject/JSONArray — NOT Gson. Do not change this pattern. The optNullableString() helper must be used for nullable String fields.
8. UI Layer
8.1 Activities
| Activity | Purpose |
|---|---|
HomeActivity |
Main SDK screen — banner carousel + categorized couponset sections |
ProfileActivity |
User profile screen |
SingleCouponActivity |
Single coupon detail view |
SingleCouponsetActivity |
Single couponset/offer detail view |
WarpViewActivity |
WebView for campaign content (loads campaign URLs) |
BaseFragmentActivity |
Fragment host activity |
WarpBaseActivity |
Base activity class |
ApplicationSessionActivity |
Session management |
UI Patterns:
- Activities extend
android.app.Activity(NOTAppCompatActivity) — except where noted - Use
View.OnClickListenerinterface implementation - Navigation via
IntentwithParcelableextras - Loading states with
RelativeLayoutoverlays (mPbLoading) - Edge-to-edge display:
WarpUtils.applyEdgeToEdge(activity, topView, bottomView)
8.2 Banner Carousel (HomeActivity)
-
ViewPager2withBannerAdapter - Pagination dots (custom
LinearLayoutwithImageViewdots) - Data source:
WarplyManagerHelper.getBannerList()(merged campaigns + articles) - Image preloading with Glide (
DiskCacheStrategy.DATA) - Click handlers: campaigns →
WarpViewActivity, articles →SingleCouponsetActivity(via UUID lookup)
8.3 Couponset Sections (HomeActivity)
- Dynamically inflated sections from
item_couponset_section.xml - Each section: title + "See all" + horizontal
RecyclerView -
CouponsetAdapterwithOnCouponsetClickListener - Max 5 items per section (
MAX_ITEMS_PER_SECTION = 5) - Data source:
LinkedHashMap<String, ArrayList<Couponset>>— "Top offers" first, then by category
8.4 Campaign URL Construction
// WarplyManagerHelper.constructCampaignUrl(campaign)
// Sets webview params as JSON in SharedPreferences:
{
"web_id": "...",
"app_uuid": "...",
"api_key": "...",
"session_uuid": "...",
"access_token": "...",
"refresh_token": "...",
"client_id": "...",
"client_secret": "...",
"lan": "...",
"dark": "true/false"
}
8.5 Fonts
Custom fonts in res/font/:
ping_lcg_bold.otfping_lcg_regular.otfping_lcg_light.otf
Applied via: WarpUtils.renderCustomFont(context, R.font.ping_lcg_bold, textView1, textView2, ...)
9. Services & Background Work
| Service | Purpose |
|---|---|
FCMBaseMessagingService |
Firebase Cloud Messaging (currently commented out in manifest) |
HMSBaseMessagingService |
Huawei Push (currently commented out in manifest) |
UpdateUserLocationService |
Location updates (JobService) |
EventRefreshDeviceTokenService |
Device token refresh (JobService) |
WarplyBeaconsRangingService |
iBeacon ranging |
PushEventsWorkerService |
Push event processing (WorkManager) |
PushEventsClickedWorkerService |
Push click tracking (WorkManager) |
| Receiver | Purpose |
|---|---|
ConnectivityChangedReceiver |
Network state changes |
LocationChangedReceiver |
Location updates |
10. Code Style & Conventions
10.1 Section Comments
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
10.2 License Header
Every source file must start with the BSD-2-Clause license block:
/*
* Copyright 2010-2013 Warply Ltd. All rights reserved.
* ...BSD-2-Clause text...
*/
10.3 Naming Conventions
-
Packages:
ly.warp.sdk.* -
Member variables:
mPrefixedNamefor UI fields (e.g.,mPbLoading,mBannerViewPager) -
Constants:
UPPER_SNAKE_CASE(e.g.,MAX_ITEMS_PER_SECTION,HEADER_SIGNATURE) -
JSON keys:
lower_snake_caseasprivate static final Stringconstants -
Callbacks: Anonymous inner classes or final field references (e.g.,
mCampaignsCallback)
10.4 Logging
WarpUtils.log("message"); // Debug log (only when WarpConstants.DEBUG is true)
WarpUtils.verbose("message"); // Verbose log
WarpUtils.warn("message", exception); // Warning log
10.5 General Rules
- Pure Java — no Kotlin in the SDK module
-
No data binding — traditional
findViewById()approach - No dependency injection — manual singleton/static patterns
-
Callbacks over coroutines — all async uses
CallbackReceiver<T> -
Author attribution:
Created by Panagiotis Triantafyllou on <date>.
11. Critical Rules for AI Agents
⚠️ DO NOT:
-
Do NOT use Gson for model serialization — all models use
org.json.JSONObject/JSONArray. Theconverter-gsondependency is only for Retrofit response parsing. -
Do NOT modify the embedded Volley fork (
io/volley/) — it contains customized request handling specific to the SDK's microapp architecture. -
Do NOT store tokens in plain text — always use
CryptoUtils.encrypt()when writing to theclientorauthDB tables, andCryptoUtils.decrypt()when reading. -
Do NOT change Activities to AppCompatActivity unless explicitly required — the SDK uses
android.app.Activitybase class. - Do NOT introduce Kotlin into the SDK module — it is a pure Java codebase.
-
Do NOT hard-code server URLs, API keys, or UUIDs — these come from
warply.propertiesviaWarplyProperty. - Do NOT remove or modify the BSD license headers from source files.
-
Do NOT expose sensitive data (tokens, keys, credentials) in logs — all logging is gated behind
WarpConstants.DEBUG.
✅ DO:
- Follow the Parcelable + Serializable pattern for all new data models.
-
Use
optNullableString()helper for nullable String JSON fields in models. -
Use
CallbackReceiver<T>pattern for all new async operations. -
Use Guava
ListenableFuturewithFutures.allAsList()for parallel API calls inWarplyManager. -
Post results to main thread via
new Handler(Looper.getMainLooper()).post(...)in Futures callbacks. - Implement token refresh retry (max 3 attempts) for authenticated endpoints.
-
Use
WarpUtils.renderCustomFont()for applying custom fonts to TextViews. -
Use
WarpUtils.applyEdgeToEdge()for edge-to-edge display in new Activities. -
Cache API results in
WarplyManagerHelperstatic fields for UI consumption. -
Update
WarpConstants.SDK_VERSIONandbuild.gradlePUBLISH_VERSIONwhen making version changes.
12. Common Tasks Quick Reference
Adding a new API endpoint:
- Add Retrofit method to
ApiService.java - Add static method in
WarplyManager.javawithListenableFuturepattern - Create/update model in
io/models/ - Add cache field in
WarplyManagerHelper.javaif needed - Call from Activity/Fragment with
CallbackReceiver<T>
Adding a new Activity:
- Create in
ly.warp.sdk.activities - Extend
android.app.Activity - Register in
warply_android_sdk/src/main/AndroidManifest.xmlwithandroid:exported="false"andandroid:theme="@style/SDKAppTheme" - Use
WarpUtils.applyEdgeToEdge()inonCreate() - Follow
mPrefixednaming for view fields
Adding a new data model:
- Create in
ly.warp.sdk.io.models - Implement
Parcelable, Serializable - Define JSON key constants as
private static final String - Add
optNullableString()helper - Implement constructors: default,
JSONObject,String,Parcel - Implement
toJSONObject(),toString(),toHumanReadableString() - Implement
writeToParcel()andCREATOR - Add getters/setters
Debugging:
- Set
Debug=trueinwarply.propertiesto enableWarpUtils.log()output - Enable OkHttp logging interceptor in
ApiClient.getClient()(uncomment the interceptor lines) - Check
WARP_DEBUGtag in Logcat
13. Manifest Permissions
The SDK requests these permissions:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Runtime permissions are handled via the embedded Dexter library (ly.warp.sdk.dexter).
14. Version Management
-
SDK Version constant:
WarpConstants.SDK_VERSION(inWarpConstants.java) -
Publish version:
PUBLISH_VERSION(inwarply_android_sdk/build.gradle) -
Group ID:
ly.warp -
Artifact ID:
warply-android-sdk - Both must be updated in sync when releasing new versions.