Whattsapp does support new Stickerpacks to be imported from our Apps.
One needs to create a Content-Provider to do so (it is one of the Steps mandatory).
This one is Provided with the Exampleapp
As you can see it is using a Value from Android Studios BuildConfig
BuildConfig.CONTENT_PROVIDER_AUTHORITY
I found out that the content of this value is the Value of the Packagename +".StickerContentProvider"
If i understand correctly as there is no BuildConfig in b4a i would need to find a way to set this Values dynamically.
How could i use such a ContentProvider in my java-wrapper-library?
In my case it is included in the jar created by SLC. com.whatsappex.StickerContentProvider
But i´m not able to setup the Manifest correctly to reference this StickerContentProvider
I tried
For reference: This is what the told in the Description:
----------------------------------------------------------------
Advanced development
For advanced developers looking to make richer sticker apps, follow the instructions below.
Overview
Sticker apps communicate with WhatsApp as follows:
The ContentProvider in the sample app is StickerContentProvider. The ContentProvider provides 4 APIs:
- Is it possible at all to add a ContentProvider with a b4a library?
- Does anyone have an example on how it would work?
- How does the Manifest looks like? I always have problems seting up the Manifest correctly ;-(
One needs to create a Content-Provider to do so (it is one of the Steps mandatory).
This one is Provided with the Exampleapp
B4X:
/*
* 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.example.samplestickerapp;
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 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<StickerPack> 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<StickerPack> 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<StickerPack> 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<String> 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");
}
}
As you can see it is using a Value from Android Studios BuildConfig
BuildConfig.CONTENT_PROVIDER_AUTHORITY
I found out that the content of this value is the Value of the Packagename +".StickerContentProvider"
If i understand correctly as there is no BuildConfig in b4a i would need to find a way to set this Values dynamically.
How could i use such a ContentProvider in my java-wrapper-library?
In my case it is included in the jar created by SLC. com.whatsappex.StickerContentProvider
But i´m not able to setup the Manifest correctly to reference this StickerContentProvider
I tried
B4X:
AddApplicationText(
<provider
android:name=".StickerContentProvider"
android:authorities="${applicationId}.StickerContentProvider"
android:enabled="true"
android:exported="true"
android:readPermission="com.whatsapp.sticker.READ" />
)
java.lang.RuntimeException: Unable to get provider de.donmanfred.whatsappsticker.StickerContentProvider: java.lang.ClassNotFoundException: Didn't find class "de.donmanfred.whatsappsticker.StickerContentProvider" on path: DexPathList[[zip file "/data/app/de.donmanfred.whatsappsticker-lTmAWBTR4UyqLJ1NilNo6g==/base.apk"],nativeLibraryDirectories=[/data/app/de.donmanfred.whatsappsticker-lTmAWBTR4UyqLJ1NilNo6g==/lib/arm64, /system/lib64, /system/vendor/lib64]]
at android.app.ActivityThread.installProvider(ActivityThread.java:6577)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:6129)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6039)
at android.app.ActivityThread.-wrap1(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1764)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6940)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Caused by: java.lang.ClassNotFoundException: Didn't find class "de.donmanfred.whatsappsticker.StickerContentProvider" on path: DexPathList[[zip file "/data/app/de.donmanfred.whatsappsticker-lTmAWBTR4UyqLJ1NilNo6g==/base.apk"],nativeLibraryDirectories=[/data/app/de.donmanfred.whatsappsticker-lTmAWBTR4UyqLJ1NilNo6g==/lib/arm64, /system/lib64, /system/vendor/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at android.app.ActivityThread.installProvider(ActivityThread.java:6562)
... 10 more
For reference: This is what the told in the Description:
----------------------------------------------------------------
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.
The ContentProvider in the sample app is StickerContentProvider. The ContentProvider provides 4 APIs:
- <authority>/metadata, this returns information about all the sticker packs in your app. Replace <authority> with the actual authority string. In the sample app, it is com.example.samplestickerapp.stickercontentprovider
- <authority>/metadata/<pack_identifier>, this returns information about a single pack. Replace <pack_identifier> with the actual identifier of the pack. In the sample app, it is 1.
- <authority>/stickers/<pack_identifier>, this returns information about the stickers in a pack. The returned information includes the sticker file name and emoji associated with the sticker.
- <authority>/stickers_asset/<pack_identifier>/<sticker_file_name>, this returns the binary information of the sticker: AssetFileDescriptor, which points to the asset file for the sticker. Replace <sticker_file_name> with the actual sticker file name that should be fetched.
B4X:
<provider
android:name=".StickerContentProvider"
android:authorities="${contentProviderAuthority}"
android:enabled="true"
android:exported="true"
android:readPermission="com.whatsapp.sticker.READ" />
- Is it possible at all to add a ContentProvider with a b4a library?
- Does anyone have an example on how it would work?
- How does the Manifest looks like? I always have problems seting up the Manifest correctly ;-(