WhatsApp / stickers

This repository contains the iOS and Android sample apps and API for creating third party sticker packs for WhatsApp.
Other
2.73k stars 1.7k forks source link

[Android]Stickers are disappearing some times when creating packs in runtime #398

Closed vrsrohit closed 5 years ago

vrsrohit commented 5 years ago

Hello I am trying to load stickers from server , I am saving them in internal storage and changed the content provider to suit my requirement but the problem here is when I tried to add 5 sticker packs only 2/3 are reflecting correctly in WhatsApp and they are loading very slowly . (sometimes all packs will show in WhatsApp but fail to display stickers if in case it displays stickers after re opening once or twice they are getting cleared from whatsapp )

Here I have pasted my ContentProvider. Please Help !!

PS :: I have switched of battery optimisation for the app and tested in 4 different type of mobiles.

package com.dsrtech.whatsappstickers.Whatsapp;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;

import com.dsrtech.whatsappstickers.BuildConfig;
import com.dsrtech.whatsappstickers.utils.NewImageSaver;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
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 = "https://play.google.com/store/apps/details?id=com.dsrtech.cutpasteeditor&hl=en";
    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_SCHEME = "content";
    static final String METADATA = "metadata";
    static final String STICKERS = "stickers";
    static final String STICKERS_ASSET = "stickers_asset";
    private static final String TAG = StickerContentProvider.class.getSimpleName();
    /**
     * 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);
    private static final int METADATA_CODE = 1;
    private static final int METADATA_CODE_FOR_SINGLE_PACK = 2;
    private static final int STICKERS_CODE = 3;
    private static final int STICKERS_ASSET_CODE = 4;
    private static final int STICKER_PACK_TRAY_ICON_CODE = 5;
    public static Uri AUTHORITY_URI = new Uri.Builder().scheme(StickerContentProvider.CONTENT_SCHEME).authority(BuildConfig.CONTENT_PROVIDER_AUTHORITY).appendPath(StickerContentProvider.METADATA).build();
    private List<StickerPack> stickerPackList = new ArrayList<>();

    @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()) {
            Log.e(TAG, "onCreate: " + stickerPack.identifier);
            MATCHER.addURI(authority, STICKERS_ASSET + "/" + stickerPack.identifier + "/" + stickerPack.trayImageFile, STICKER_PACK_TRAY_ICON_CODE);
            if (stickerPack.getStickers() != null) {
                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);
        Log.d(TAG, "query: " + code + 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) {
        MATCHER.match(uri);
        final int matchCode = MATCHER.match(uri);
        final List<String> pathSegments = uri.getPathSegments();
        Log.d(TAG, "openFile: " + matchCode + uri + "\n" + uri.getAuthority()
                + "\n" + pathSegments.get(pathSegments.size() - 3) + "/"
                + "\n" + pathSegments.get(pathSegments.size() - 2) + "/"
                + "\n" + pathSegments.get(pathSegments.size() - 1));

        return getImageAsset(uri);
    }

    private AssetFileDescriptor getImageAsset(Uri uri) throws IllegalArgumentException {
        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);
        }
        for (StickerPack stickerPack : getStickerPackList()) {
            if (identifier.equals(stickerPack.identifier)) {
                if (fileName.equals(stickerPack.trayImageFile)) {
                    return fetchFile(uri, fileName, identifier);
                } else {
                    for (Sticker sticker : stickerPack.getStickers()) {
                        if (fileName.equals(sticker.imageFileName)) {
                            return fetchFile(uri, fileName, identifier);
                        }
                    }
                }
            }
        }
        return null;
    }

    private AssetFileDescriptor fetchFile(@NonNull Uri uri, @NonNull String fileName, @NonNull String identifier) {
        try {
            File file;
            file = new NewImageSaver(getContext()).loadFile("whatsapp_stickers" + identifier, fileName);
            if (!file.exists()) {
                Log.d("zxz fetFile", "StickerPack dir not found");
            }
            Log.d("zxz fetchFile", "StickerPack " + file.getPath());
            return new AssetFileDescriptor(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY), 0L, -1L);
        } catch (IOException e) {
            Log.e(Objects.requireNonNull(getContext()).getPackageName(), "IOException when getting asset file, uri:" + uri, e);
            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);
        }
    }

    public List<StickerPack> getStickerPackList() {
        stickerPackList.clear();
        for (int i = 1 ; i<=new NewImageSaver(getContext()).loadMainFolderSize();i++ ){
            StickerPack mStickerPack = new StickerPack(String.valueOf(i), "teststickerpack"+i, "PixelForce", "tray_Cuppy.png", "", "", "", "");
            ArrayList<Sticker> mStickers = new ArrayList<>();
            List<String> mEmojis = new ArrayList<>();
            String str = getEmojiByUnicode(0x1F60A);
            mEmojis.add(str);
            mEmojis.add(str);
            for (int j = 0; j < new NewImageSaver(getContext()).loadFileNames("whatsapp_stickers"+i).size(); j++) {
                Log.i("zxzxzsizezxzxzx", "" + j);
                String file = new NewImageSaver(getContext()).loadFileNames("whatsapp_stickers"+i).get(j);
                if (!file.equals("tray_Cuppy.png")) {
                    Log.i("zxzxz name", "" + file);
                    mStickers.add(new Sticker(file, mEmojis));
                }
            }
            mStickerPack.setStickers(mStickers);

            stickerPackList.add(mStickerPack);
        }

        return stickerPackList;

    }

    public String getEmojiByUnicode(int unicode) {
        return new String(Character.toChars(unicode));
    }

    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 (Objects.requireNonNull(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);
        }
        Log.d(TAG, "getStickerPackInfo: " + stickerPackList.size());
        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 (Objects.requireNonNull(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;
    }

    @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");
    }

}
simonzhexu commented 5 years ago

what does NewImageSaver class look like?

vrsrohit commented 5 years ago

its just a class to handle saving and loading of files I have pasted here please check it

package com.dsrtech.whatsappstickers.utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class NewImageSaver {

    private Context mContext;

    public NewImageSaver(Context context) {
        this.mContext = context;
    }

    public void save(Bitmap bitmapImage, String directoryName, String fileName) {

        File mainDirectory = new File(mContext.getFilesDir(), "PIXEL_WHATSAPP_STICKERS");
        // creates the directory if not present yet
        mainDirectory.mkdir();
        File subDirectory = new File(mainDirectory, directoryName);
        subDirectory.mkdir();
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(new File(subDirectory, fileName));
            if (fileName.equals("tray_Cuppy.png")) {
                bitmapImage.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
            } else {
                bitmapImage.compress(Bitmap.CompressFormat.WEBP, 50, fileOutputStream);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private File[] loadFilesOfDirectory(String directoryName) {
        File directory = new File(mContext.getFilesDir(), directoryName);
        if (!directory.exists() && !directory.mkdirs()) {
            Log.e("ImageSaver", "Error creating directory " + directory);
        }

        return directory.listFiles();
    }

    public ArrayList<String> loadPathsOfDirectory(String directoryName) {
        if (new File(mContext.getFilesDir(), "PIXEL_WHATSAPP_STICKERS"+File.separator+directoryName).listFiles().length > 0) {
            ArrayList<String> paths = new ArrayList<>();
            for (File file : new File(mContext.getFilesDir(), "PIXEL_WHATSAPP_STICKERS"+File.separator+directoryName).listFiles()) {
                paths.add(file.getPath());
            }
            return paths;
        }
        return null;
    }

    public File loadFile(String directoryName, String bitmapName) {
        try {
            File[] files = (loadFilesOfDirectory("PIXEL_WHATSAPP_STICKERS"+File.separator+directoryName));
            for (File file : files) {
                if (((file.getName()).equals(bitmapName))) {
                    return file;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public ArrayList<String> loadFileNames(String directoryName) {
        ArrayList<String> filenames = new ArrayList<>();
        try {
            File[] files = (loadFilesOfDirectory("PIXEL_WHATSAPP_STICKERS"+File.separator+directoryName));
            for (File file : files) {
                filenames.add(file.getName());
            }
            return filenames;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public int loadMainFolderSize(){
        return loadFilesOfDirectory("PIXEL_WHATSAPP_STICKERS").length;
    }

    public ArrayList<String> loadPaths(String directoryName) {
        ArrayList<String> filenames = new ArrayList<>();
        try {
            File[] files = (loadFilesOfDirectory("PIXEL_WHATSAPP_STICKERS"+File.separator+directoryName));
            for (File file : files) {
                if (!file.getName().endsWith(".png")){
                    filenames.add(file.getPath());
                }

            }
            return filenames;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
vrsrohit commented 5 years ago

@simonzhexu @MarvinJWendt Please Help guys its very urgent for me

MarvinJWendt commented 5 years ago

Hey, This Issue seems to be inactive. To keep things organized I will close this for now. If this Issue is still relevant, don't hesitate to drop a comment and I will reopen it if necessary.

Sincerely, Marvin Wendt