AbstractConnectionManager.java

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