1package com.cheogram.android;
2
3import android.app.Notification;
4import android.app.NotificationManager;
5import android.app.Service;
6import android.content.Context;
7import android.content.SharedPreferences;
8import android.content.Intent;
9import android.database.Cursor;
10import android.media.MediaScannerConnection;
11import android.net.Uri;
12import android.os.Environment;
13import android.os.IBinder;
14import android.provider.DocumentsContract;
15import android.preference.PreferenceManager;
16import android.provider.MediaStore;
17import android.util.Log;
18
19import androidx.core.app.NotificationCompat;
20
21import com.google.common.io.ByteStreams;
22
23import io.ipfs.cid.Cid;
24
25import java.io.File;
26import java.io.FileOutputStream;
27import java.io.OutputStream;
28import java.io.OutputStreamWriter;
29import java.util.HashSet;
30import java.util.concurrent.atomic.AtomicBoolean;
31
32import org.json.JSONArray;
33import org.json.JSONObject;
34
35import okhttp3.OkHttpClient;
36import okhttp3.Request;
37import okhttp3.Response;
38
39import eu.siacs.conversations.Config;
40import eu.siacs.conversations.R;
41import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
42import eu.siacs.conversations.entities.Account;
43import eu.siacs.conversations.entities.Conversation;
44import eu.siacs.conversations.entities.Message;
45import eu.siacs.conversations.http.HttpConnectionManager;
46import eu.siacs.conversations.persistance.DatabaseBackend;
47import eu.siacs.conversations.persistance.FileBackend;
48import eu.siacs.conversations.utils.Compatibility;
49import eu.siacs.conversations.utils.FileUtils;
50import eu.siacs.conversations.utils.MimeUtils;
51
52public class DownloadDefaultStickers extends Service {
53
54 private static final int NOTIFICATION_ID = 20;
55 private static final AtomicBoolean RUNNING = new AtomicBoolean(false);
56 private DatabaseBackend mDatabaseBackend;
57 private NotificationManager notificationManager;
58 private File mStickerDir;
59 private OkHttpClient http = null;
60 private HashSet<Uri> pendingPacks = new HashSet<Uri>();
61
62 @Override
63 public void onCreate() {
64 mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
65 notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
66 mStickerDir = stickerDir();
67 }
68
69 @Override
70 public int onStartCommand(Intent intent, int flags, int startId) {
71 if (http == null) {
72 http = HttpConnectionManager.newBuilder(intent.getBooleanExtra("tor", getResources().getBoolean(R.bool.use_tor))).build();
73 }
74 synchronized(pendingPacks) {
75 pendingPacks.add(intent.getData() == null ? Uri.parse("https://stickers.cheogram.com/index.json") : intent.getData());
76 }
77 if (RUNNING.compareAndSet(false, true)) {
78 new Thread(() -> {
79 try {
80 download();
81 } catch (final Exception e) {
82 Log.d(Config.LOGTAG, "unable to download stickers", e);
83 }
84 stopForeground(true);
85 RUNNING.set(false);
86 stopSelf();
87 }).start();
88 return START_STICKY;
89 } else {
90 Log.d(Config.LOGTAG, "DownloadDefaultStickers. ignoring start command because already running");
91 }
92 return START_NOT_STICKY;
93 }
94
95 private void oneSticker(JSONObject sticker) throws Exception {
96 Response r = http.newCall(new Request.Builder().url(sticker.getString("url")).build()).execute();
97 File file = null;
98 try {
99 file = new File(mStickerDir.getAbsolutePath() + "/" + sticker.getString("pack") + "/" + sticker.getString("name") + "." + MimeUtils.guessExtensionFromMimeType(r.headers().get("content-type")));
100 file.getParentFile().mkdirs();
101 OutputStream os = new FileOutputStream(file);
102 ByteStreams.copy(r.body().byteStream(), os);
103 os.close();
104 } catch (final Exception e) {
105 file = null;
106 e.printStackTrace();
107 }
108
109 JSONArray cids = sticker.getJSONArray("cids");
110 for (int i = 0; i < cids.length(); i++) {
111 Cid cid = Cid.decode(cids.getString(i));
112 mDatabaseBackend.saveCid(cid, file, sticker.getString("url"));
113 }
114
115 MediaScannerConnection.scanFile(
116 getBaseContext(),
117 new String[] { file.getAbsolutePath() },
118 null,
119 new MediaScannerConnection.MediaScannerConnectionClient() {
120 @Override
121 public void onMediaScannerConnected() {}
122
123 @Override
124 public void onScanCompleted(String path, Uri uri) {}
125 }
126 );
127
128 try {
129 File copyright = new File(mStickerDir.getAbsolutePath() + "/" + sticker.getString("pack") + "/copyright.txt");
130 OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(copyright, true), "utf-8");
131 w.write(sticker.getString("pack"));
132 w.write('/');
133 w.write(sticker.getString("name"));
134 w.write(": ");
135 w.write(sticker.getString("copyright"));
136 w.write('\n');
137 w.close();
138 } catch (final Exception e) { }
139 }
140
141 private void download() throws Exception {
142 Uri jsonUri;
143 synchronized(pendingPacks) {
144 if (pendingPacks.iterator().hasNext()) {
145 jsonUri = pendingPacks.iterator().next();
146 } else {
147 return;
148 }
149 }
150
151 NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
152 mBuilder.setContentTitle("Downloading Stickers")
153 .setSmallIcon(R.drawable.ic_archive_white_24dp)
154 .setProgress(1, 0, false);
155 startForeground(NOTIFICATION_ID, mBuilder.build());
156
157 Response r = http.newCall(new Request.Builder().url(jsonUri.toString()).build()).execute();
158 JSONArray stickers = new JSONArray(r.body().string());
159
160 final Progress progress = new Progress(mBuilder, 1, 0);
161 for (int i = 0; i < stickers.length(); i++) {
162 try {
163 oneSticker(stickers.getJSONObject(i));
164 } catch (final Exception e) {
165 e.printStackTrace();
166 }
167
168 final int percentage = i * 100 / stickers.length();
169 notificationManager.notify(NOTIFICATION_ID, progress.build(percentage));
170 }
171
172 synchronized(pendingPacks) {
173 pendingPacks.remove(jsonUri);
174 }
175 download();
176 }
177
178 private File stickerDir() {
179 SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
180 final String dir = p.getString("sticker_directory", "Stickers");
181 if (dir.startsWith("content://")) {
182 Uri uri = Uri.parse(dir);
183 uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
184 return new File(FileUtils.getPath(getBaseContext(), uri));
185 } else {
186 return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + dir);
187 }
188 }
189
190 @Override
191 public IBinder onBind(Intent intent) {
192 return null;
193 }
194
195 private static class Progress {
196 private final NotificationCompat.Builder builder;
197 private final int max;
198 private final int count;
199
200 private Progress(NotificationCompat.Builder builder, int max, int count) {
201 this.builder = builder;
202 this.max = max;
203 this.count = count;
204 }
205
206 private Notification build(int percentage) {
207 builder.setProgress(max * 100, count * 100 + percentage, false);
208 return builder.build();
209 }
210 }
211}