skill.md 24.7 KB

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) or Warply.getInitializer(context, callback)
  • Uses WarplyInitializer for setup, which calls initInternal() 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 RequestQueue for 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 SharedPreferences on 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/JSONArrayNOT 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 (NOT AppCompatActivity) — except where noted
  • Use View.OnClickListener interface implementation
  • Navigation via Intent with Parcelable extras
  • Loading states with RelativeLayout overlays (mPbLoading)
  • Edge-to-edge display: WarpUtils.applyEdgeToEdge(activity, topView, bottomView)

8.2 Banner Carousel (HomeActivity)

  • ViewPager2 with BannerAdapter
  • Pagination dots (custom LinearLayout with ImageView dots)
  • 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
  • CouponsetAdapter with OnCouponsetClickListener
  • 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.otf
  • ping_lcg_regular.otf
  • ping_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: mPrefixedName for UI fields (e.g., mPbLoading, mBannerViewPager)
  • Constants: UPPER_SNAKE_CASE (e.g., MAX_ITEMS_PER_SECTION, HEADER_SIGNATURE)
  • JSON keys: lower_snake_case as private static final String constants
  • 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:

  1. Do NOT use Gson for model serialization — all models use org.json.JSONObject/JSONArray. The converter-gson dependency is only for Retrofit response parsing.
  2. Do NOT modify the embedded Volley fork (io/volley/) — it contains customized request handling specific to the SDK's microapp architecture.
  3. Do NOT store tokens in plain text — always use CryptoUtils.encrypt() when writing to the client or auth DB tables, and CryptoUtils.decrypt() when reading.
  4. Do NOT change Activities to AppCompatActivity unless explicitly required — the SDK uses android.app.Activity base class.
  5. Do NOT introduce Kotlin into the SDK module — it is a pure Java codebase.
  6. Do NOT hard-code server URLs, API keys, or UUIDs — these come from warply.properties via WarplyProperty.
  7. Do NOT remove or modify the BSD license headers from source files.
  8. Do NOT expose sensitive data (tokens, keys, credentials) in logs — all logging is gated behind WarpConstants.DEBUG.

✅ DO:

  1. Follow the Parcelable + Serializable pattern for all new data models.
  2. Use optNullableString() helper for nullable String JSON fields in models.
  3. Use CallbackReceiver<T> pattern for all new async operations.
  4. Use Guava ListenableFuture with Futures.allAsList() for parallel API calls in WarplyManager.
  5. Post results to main thread via new Handler(Looper.getMainLooper()).post(...) in Futures callbacks.
  6. Implement token refresh retry (max 3 attempts) for authenticated endpoints.
  7. Use WarpUtils.renderCustomFont() for applying custom fonts to TextViews.
  8. Use WarpUtils.applyEdgeToEdge() for edge-to-edge display in new Activities.
  9. Cache API results in WarplyManagerHelper static fields for UI consumption.
  10. Update WarpConstants.SDK_VERSION and build.gradle PUBLISH_VERSION when making version changes.

12. Common Tasks Quick Reference

Adding a new API endpoint:

  1. Add Retrofit method to ApiService.java
  2. Add static method in WarplyManager.java with ListenableFuture pattern
  3. Create/update model in io/models/
  4. Add cache field in WarplyManagerHelper.java if needed
  5. Call from Activity/Fragment with CallbackReceiver<T>

Adding a new Activity:

  1. Create in ly.warp.sdk.activities
  2. Extend android.app.Activity
  3. Register in warply_android_sdk/src/main/AndroidManifest.xml with android:exported="false" and android:theme="@style/SDKAppTheme"
  4. Use WarpUtils.applyEdgeToEdge() in onCreate()
  5. Follow mPrefixed naming for view fields

Adding a new data model:

  1. Create in ly.warp.sdk.io.models
  2. Implement Parcelable, Serializable
  3. Define JSON key constants as private static final String
  4. Add optNullableString() helper
  5. Implement constructors: default, JSONObject, String, Parcel
  6. Implement toJSONObject(), toString(), toHumanReadableString()
  7. Implement writeToParcel() and CREATOR
  8. Add getters/setters

Debugging:

  • Set Debug=true in warply.properties to enable WarpUtils.log() output
  • Enable OkHttp logging interceptor in ApiClient.getClient() (uncomment the interceptor lines)
  • Check WARP_DEBUG tag 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 (in WarpConstants.java)
  • Publish version: PUBLISH_VERSION (in warply_android_sdk/build.gradle)
  • Group ID: ly.warp
  • Artifact ID: warply-android-sdk
  • Both must be updated in sync when releasing new versions.