AbstractConnectionManager.java

  1package eu.siacs.conversations.services;
  2
  3import static eu.siacs.conversations.entities.Transferable.VALID_CRYPTO_EXTENSIONS;
  4
  5import android.os.PowerManager;
  6import android.os.SystemClock;
  7import androidx.annotation.NonNull;
  8import androidx.annotation.Nullable;
  9import androidx.core.content.ContextCompat;
 10import eu.siacs.conversations.R;
 11import eu.siacs.conversations.entities.DownloadableFile;
 12import eu.siacs.conversations.utils.Compatibility;
 13import java.io.FileInputStream;
 14import java.io.IOException;
 15import java.io.InputStream;
 16import java.util.concurrent.atomic.AtomicLong;
 17import okhttp3.MediaType;
 18import okhttp3.RequestBody;
 19import okio.BufferedSink;
 20import okio.Okio;
 21import okio.Source;
 22import org.bouncycastle.crypto.engines.AESEngine;
 23import org.bouncycastle.crypto.io.CipherInputStream;
 24import org.bouncycastle.crypto.modes.AEADBlockCipher;
 25import org.bouncycastle.crypto.modes.GCMBlockCipher;
 26import org.bouncycastle.crypto.params.AEADParameters;
 27import org.bouncycastle.crypto.params.KeyParameter;
 28
 29public class AbstractConnectionManager {
 30
 31    private static final int UI_REFRESH_THRESHOLD = 250;
 32    private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0);
 33    protected XmppConnectionService mXmppConnectionService;
 34
 35    public AbstractConnectionManager(XmppConnectionService service) {
 36        this.mXmppConnectionService = service;
 37    }
 38
 39    public static InputStream upgrade(DownloadableFile file, InputStream is) {
 40        if (file.getKey() != null && file.getIv() != null) {
 41            AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
 42            cipher.init(
 43                    true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
 44            return new CipherInputStream(is, cipher);
 45        } else {
 46            return is;
 47        }
 48    }
 49
 50    // For progress tracking see:
 51    // https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java
 52
 53    public static RequestBody requestBody(
 54            final DownloadableFile file, final ProgressListener progressListener) {
 55        return new RequestBody() {
 56
 57            @Override
 58            public long contentLength() {
 59                return file.getSize() + (file.getKey() != null ? 16 : 0);
 60            }
 61
 62            @Nullable
 63            @Override
 64            public MediaType contentType() {
 65                return MediaType.parse(file.getMimeType());
 66            }
 67
 68            @Override
 69            public void writeTo(@NonNull final BufferedSink sink) throws IOException {
 70                long transmitted = 0;
 71                try (final Source source = Okio.source(upgrade(file, new FileInputStream(file)))) {
 72                    long read;
 73                    while ((read = source.read(sink.buffer(), 8196)) != -1) {
 74                        transmitted += read;
 75                        sink.flush();
 76                        progressListener.onProgress(transmitted);
 77                    }
 78                }
 79            }
 80        };
 81    }
 82
 83    public interface ProgressListener {
 84        void onProgress(long progress);
 85    }
 86
 87    public XmppConnectionService getXmppConnectionService() {
 88        return this.mXmppConnectionService;
 89    }
 90
 91    public long getAutoAcceptFileSize() {
 92        final long autoAcceptFileSize =
 93                this.mXmppConnectionService.getLongPreference(
 94                        "auto_accept_file_size", R.integer.auto_accept_filesize);
 95        return autoAcceptFileSize <= 0 ? -1 : autoAcceptFileSize;
 96    }
 97
 98    public boolean hasStoragePermission() {
 99        return Compatibility.hasStoragePermission(mXmppConnectionService);
100    }
101
102    public void updateConversationUi(boolean force) {
103        synchronized (LAST_UI_UPDATE_CALL) {
104            if (force
105                    || SystemClock.elapsedRealtime() - LAST_UI_UPDATE_CALL.get()
106                            >= UI_REFRESH_THRESHOLD) {
107                LAST_UI_UPDATE_CALL.set(SystemClock.elapsedRealtime());
108                mXmppConnectionService.updateConversationUi();
109            }
110        }
111    }
112
113    public PowerManager.WakeLock createWakeLock(final String name) {
114        final PowerManager powerManager =
115                ContextCompat.getSystemService(mXmppConnectionService, PowerManager.class);
116        return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
117    }
118
119    public static class Extension {
120        public final String main;
121        public final String secondary;
122
123        private Extension(String main, String secondary) {
124            this.main = main;
125            this.secondary = secondary;
126        }
127
128        public String getExtension() {
129            if (VALID_CRYPTO_EXTENSIONS.contains(main)) {
130                return secondary;
131            } else {
132                return main;
133            }
134        }
135
136        public static Extension of(String path) {
137            // TODO accept List<String> pathSegments
138            final int pos = path.lastIndexOf('/');
139            final String filename = path.substring(pos + 1).toLowerCase();
140            final String[] parts = filename.split("\\.");
141            final String main = parts.length >= 2 ? parts[parts.length - 1] : null;
142            final String secondary = parts.length >= 3 ? parts[parts.length - 2] : null;
143            return new Extension(main, secondary);
144        }
145    }
146}