Option to download default stickers and save their cid and url in database

Stephen Paul Weber created

Change summary

src/cheogram/AndroidManifest.xml                                         |   1 
src/cheogram/java/com/cheogram/android/DownloadDefaultStickers.java      | 164 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java    |  13 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |   6 
src/main/java/eu/siacs/conversations/ui/SettingsActivity.java            |  23 
src/main/res/xml/preferences.xml                                         |   3 
6 files changed, 209 insertions(+), 1 deletion(-)

Detailed changes

src/cheogram/AndroidManifest.xml 🔗

@@ -7,6 +7,7 @@
     <application tools:ignore="GoogleAppIndexingWarning">
         <!-- INSERT -->
 
+        <service android:name="com.cheogram.android.DownloadDefaultStickers" />
         <service android:name="com.cheogram.android.ConnectionService"
             android:label="Cheogram"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"

src/cheogram/java/com/cheogram/android/DownloadDefaultStickers.java 🔗

@@ -0,0 +1,164 @@
+package com.cheogram.android;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.IBinder;
+import android.provider.DocumentsContract;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.core.app.NotificationCompat;
+
+import com.google.common.io.ByteStreams;
+
+import io.ipfs.cid.Cid;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.persistance.DatabaseBackend;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.utils.Compatibility;
+import eu.siacs.conversations.utils.FileUtils;
+import eu.siacs.conversations.utils.MimeUtils;
+
+public class DownloadDefaultStickers extends Service {
+
+	private static final int NOTIFICATION_ID = 20;
+	private static final AtomicBoolean RUNNING = new AtomicBoolean(false);
+	private DatabaseBackend mDatabaseBackend;
+	private NotificationManager notificationManager;
+	private File mStickerDir;
+	private OkHttpClient http = new OkHttpClient();
+
+	@Override
+	public void onCreate() {
+		mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
+		notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+		mStickerDir = stickerDir();
+	}
+
+	@Override
+	public int onStartCommand(Intent intent, int flags, int startId) {
+		if (RUNNING.compareAndSet(false, true)) {
+			new Thread(() -> {
+				try {
+					download();
+				} catch (final Exception e) {
+					Log.d(Config.LOGTAG, "unable to download stickers", e);
+				}
+				stopForeground(true);
+				RUNNING.set(false);
+				stopSelf();
+			}).start();
+			return START_STICKY;
+		} else {
+			Log.d(Config.LOGTAG, "DownloadDefaultStickers. ignoring start command because already running");
+		}
+		return START_NOT_STICKY;
+	}
+
+	private void oneSticker(JSONObject sticker) throws Exception {
+		Response r = http.newCall(new Request.Builder().url(sticker.getString("url")).build()).execute();
+		File file = new File(mStickerDir.getAbsolutePath() + "/" + sticker.getString("pack") + "/" + sticker.getString("name") + "." + MimeUtils.guessExtensionFromMimeType(r.headers().get("content-type")));
+		file.getParentFile().mkdirs();
+		OutputStream os = new FileOutputStream(file);
+		ByteStreams.copy(r.body().byteStream(), os);
+		os.close();
+
+		JSONArray cids = sticker.getJSONArray("cids");
+		for (int i = 0; i < cids.length(); i++) {
+			Cid cid = Cid.decode(cids.getString(i));
+			mDatabaseBackend.saveCid(cid, file, sticker.getString("url"));
+		}
+
+		try {
+			File copyright = new File(mStickerDir.getAbsolutePath() + "/" + sticker.getString("pack") + "/copyright.txt");
+			OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(copyright, true), "utf-8");
+			w.write(sticker.getString("pack"));
+			w.write('/');
+			w.write(sticker.getString("name"));
+			w.write(": ");
+			w.write(sticker.getString("copyright"));
+			w.write('\n');
+			w.close();
+		} catch (final Exception e) { }
+	}
+
+	private void download() throws Exception {
+		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
+		mBuilder.setContentTitle("Downloading Default Stickers")
+				.setSmallIcon(R.drawable.ic_archive_white_24dp)
+				.setProgress(1, 0, false);
+		startForeground(NOTIFICATION_ID, mBuilder.build());
+
+		Response r = http.newCall(new Request.Builder().url("https://stickers.cheogram.com/index.json").build()).execute();
+		JSONArray stickers = new JSONArray(r.body().string());
+
+		final Progress progress = new Progress(mBuilder, 1, 0);
+		for (int i = 0; i < stickers.length(); i++) {
+			oneSticker(stickers.getJSONObject(i));
+
+			final int percentage = i * 100 / stickers.length();
+			notificationManager.notify(NOTIFICATION_ID, progress.build(percentage));
+		}
+	}
+
+	private File stickerDir() {
+		SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
+		final String dir = p.getString("sticker_directory", "Stickers");
+		if (dir.startsWith("content://")) {
+			Uri uri = Uri.parse(dir);
+			uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
+			return new File(FileUtils.getPath(getBaseContext(), uri));
+		} else {
+			return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + dir);
+		}
+	}
+
+	@Override
+	public IBinder onBind(Intent intent) {
+		return null;
+	}
+
+	private static class Progress {
+		private final NotificationCompat.Builder builder;
+		private final int max;
+		private final int count;
+
+		private Progress(NotificationCompat.Builder builder, int max, int count) {
+			this.builder = builder;
+			this.max = max;
+			this.count = count;
+		}
+
+		private Notification build(int percentage) {
+			builder.setProgress(max * 100, count * 100 + percentage, false);
+			return builder.build();
+		}
+	}
+}

src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java 🔗

@@ -283,6 +283,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
                 db.execSQL("PRAGMA cheogram.user_version = 6");
             }
 
+            if(cheogramVersion < 7) {
+                db.execSQL(
+                    "ALTER TABLE cheogram.cids " +
+                    "ADD COLUMN url TEXT"
+                );
+                db.execSQL("PRAGMA cheogram.user_version = 7");
+            }
+
             db.setTransactionSuccessful();
         } finally {
             db.endTransaction();
@@ -776,10 +784,15 @@ public class DatabaseBackend extends SQLiteOpenHelper {
     }
 
     public void saveCid(Cid cid, File file) {
+        saveCid(cid, file, null);
+    }
+
+    public void saveCid(Cid cid, File file, String url) {
         SQLiteDatabase db = this.getWritableDatabase();
         ContentValues cv = new ContentValues();
         cv.put("cid", cid.toString());
         cv.put("path", file.getAbsolutePath());
+        cv.put("url", url);
         db.insertWithOnConflict("cheogram.cids", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
     }
 

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -562,10 +562,14 @@ public class XmppConnectionService extends Service {
     }
 
     public void saveCid(Cid cid, File file) throws BlockedMediaException {
+        saveCid(cid, file, null);
+    }
+
+    public void saveCid(Cid cid, File file, String url) throws BlockedMediaException {
         if (this.databaseBackend.isBlockedMedia(cid)) {
             throw new BlockedMediaException();
         }
-        this.databaseBackend.saveCid(cid, file);
+        this.databaseBackend.saveCid(cid, file, url);
     }
 
     public void blockMedia(File f) {

src/main/java/eu/siacs/conversations/ui/SettingsActivity.java 🔗

@@ -25,6 +25,8 @@ import androidx.annotation.NonNull;
 import androidx.appcompat.app.AlertDialog;
 import androidx.core.content.ContextCompat;
 
+import com.cheogram.android.DownloadDefaultStickers;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -69,6 +71,7 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference
     public static final String PREVENT_SCREENSHOTS = "prevent_screenshots";
 
     public static final int REQUEST_CREATE_BACKUP = 0xbf8701;
+    public static final int REQUEST_DOWNLOAD_STICKERS = 0xbf8702;
 
     private SettingsFragment mSettingsFragment;
 
@@ -390,6 +393,17 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference
             }
         }
 
+        final Preference downloadDefaultStickers = mSettingsFragment.findPreference("download_default_stickers");
+        if (downloadDefaultStickers != null) {
+            downloadDefaultStickers.setOnPreferenceClickListener(
+                    preference -> {
+                        if (hasStoragePermission(REQUEST_DOWNLOAD_STICKERS)) {
+                            downloadStickers();
+                        }
+                        return true;
+                    });
+        }
+
         final Preference clearBlockedMedia = mSettingsFragment.findPreference("clear_blocked_media");
         if (clearBlockedMedia != null) {
             clearBlockedMedia.setOnPreferenceClickListener((p) -> {
@@ -587,6 +601,9 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference
                 if (requestCode == REQUEST_CREATE_BACKUP) {
                     createBackup();
                 }
+                if (requestCode == REQUEST_DOWNLOAD_STICKERS) {
+                    downloadStickers();
+                }
             } else {
                 Toast.makeText(
                                 this,
@@ -620,6 +637,12 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference
         builder.create().show();
     }
 
+    private void downloadStickers() {
+        Intent intent = new Intent(this, DownloadDefaultStickers.class);
+        ContextCompat.startForegroundService(this, intent);
+        displayToast("Sticker download started");
+    }
+
     private void displayToast(final String msg) {
         runOnUiThread(() -> Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show());
     }

src/main/res/xml/preferences.xml 🔗

@@ -370,6 +370,9 @@
                 <Preference
                     android:title="Change Stickers Location"
                     android:key="sticker_directory" />
+                <Preference
+                    android:title="Update Default Stickers"
+                    android:key="download_default_stickers" />
                 <Preference
                     android:title="Clear Blocked Media"
                     android:key="clear_blocked_media" />