mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 14:42:29 +00:00
Merge Signal 4.41.0
This commit is contained in:
@@ -9,12 +9,12 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.text.TextUtils;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
@@ -37,7 +37,11 @@ public class AttachmentUtil {
|
||||
Set<String> allowedTypes = getAllowedAutoDownloadTypes(context);
|
||||
String contentType = attachment.getContentType();
|
||||
|
||||
if (attachment.isVoiceNote() || (MediaUtil.isAudio(attachment) && TextUtils.isEmpty(attachment.getFileName())) || MediaUtil.isLongTextType(attachment.getContentType())) {
|
||||
if (attachment.isVoiceNote() ||
|
||||
(MediaUtil.isAudio(attachment) && TextUtils.isEmpty(attachment.getFileName())) ||
|
||||
MediaUtil.isLongTextType(attachment.getContentType()) ||
|
||||
attachment.isSticker())
|
||||
{
|
||||
return true;
|
||||
} else if (isNonDocumentType(contentType)) {
|
||||
return allowedTypes.contains(MediaUtil.getDiscreteMimeType(contentType));
|
||||
|
||||
@@ -64,7 +64,19 @@ public class BitmapUtil {
|
||||
final int maxImageSize)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
return createScaledBytes(context, model, maxImageWidth, maxImageHeight, maxImageSize, 1, 0);
|
||||
return createScaledBytes(context, model, maxImageWidth, maxImageHeight, maxImageSize, CompressFormat.JPEG);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static <T> ScaleResult createScaledBytes(Context context,
|
||||
T model,
|
||||
int maxImageWidth,
|
||||
int maxImageHeight,
|
||||
int maxImageSize,
|
||||
@NonNull CompressFormat format)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
return createScaledBytes(context, model, maxImageWidth, maxImageHeight, maxImageSize, format, 1, 0);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@@ -73,6 +85,7 @@ public class BitmapUtil {
|
||||
final int maxImageWidth,
|
||||
final int maxImageHeight,
|
||||
final int maxImageSize,
|
||||
@NonNull CompressFormat format,
|
||||
final int sizeAttempt,
|
||||
int totalAttempts)
|
||||
throws BitmapDecodingException
|
||||
@@ -102,7 +115,7 @@ public class BitmapUtil {
|
||||
do {
|
||||
totalAttempts++;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
scaledBitmap.compress(CompressFormat.JPEG, quality, baos);
|
||||
scaledBitmap.compress(format, quality, baos);
|
||||
bytes = baos.toByteArray();
|
||||
|
||||
Log.d(TAG, "iteration with quality " + quality + " size " + bytes.length + " bytes.");
|
||||
@@ -122,7 +135,7 @@ public class BitmapUtil {
|
||||
scaledBitmap = null;
|
||||
|
||||
Log.i(TAG, "Halving dimensions and retrying.");
|
||||
return createScaledBytes(context, model, maxImageWidth / 2, maxImageHeight / 2, maxImageSize, sizeAttempt + 1, totalAttempts);
|
||||
return createScaledBytes(context, model, maxImageWidth / 2, maxImageHeight / 2, maxImageSize, format, sizeAttempt + 1, totalAttempts);
|
||||
} else {
|
||||
throw new BitmapDecodingException("Unable to scale image below " + bytes.length + " bytes.");
|
||||
}
|
||||
@@ -184,6 +197,17 @@ public class BitmapUtil {
|
||||
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
|
||||
}
|
||||
|
||||
public static @NonNull CompressFormat getCompressFormatForContentType(@Nullable String contentType) {
|
||||
if (contentType == null) return CompressFormat.JPEG;
|
||||
|
||||
switch (contentType) {
|
||||
case MediaUtil.IMAGE_JPEG: return CompressFormat.JPEG;
|
||||
case MediaUtil.IMAGE_PNG: return CompressFormat.PNG;
|
||||
case MediaUtil.IMAGE_WEBP: return CompressFormat.WEBP;
|
||||
default: return CompressFormat.JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
private static BitmapFactory.Options getImageDimensions(InputStream inputStream)
|
||||
throws BitmapDecodingException
|
||||
{
|
||||
|
||||
@@ -25,6 +25,14 @@ import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
public class CommunicationActions {
|
||||
|
||||
public static void startVoiceCall(@NonNull Activity activity, @NonNull Recipient recipient) {
|
||||
if (TelephonyUtil.isAnyPstnLineBusy(activity)) {
|
||||
Toast.makeText(activity,
|
||||
R.string.CommunicationActions_a_cellular_call_is_already_in_progress,
|
||||
Toast.LENGTH_SHORT
|
||||
).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.os.Handler;
|
||||
|
||||
/**
|
||||
* A class that will throttle the number of runnables executed to be at most once every specified
|
||||
* interval.
|
||||
* interval. However, it could be longer if events are published consistently.
|
||||
*
|
||||
* Useful for performing actions in response to rapid user input, such as inputting text, where you
|
||||
* don't necessarily want to perform an action after <em>every</em> input.
|
||||
|
||||
@@ -283,7 +283,7 @@ public class DirectoryHelper {
|
||||
@NonNull RecipientDatabase recipientDatabase,
|
||||
@NonNull Set<String> eligibleContactNumbers)
|
||||
{
|
||||
return SignalExecutors.IO.submit(() -> {
|
||||
return SignalExecutors.UNBOUNDED.submit(() -> {
|
||||
List<ContactTokenDetails> activeTokens = accountManager.getContacts(eligibleContactNumbers);
|
||||
|
||||
if (activeTokens != null) {
|
||||
@@ -329,7 +329,7 @@ public class DirectoryHelper {
|
||||
@NonNull RecipientDatabase recipientDatabase,
|
||||
@NonNull Recipient recipient)
|
||||
{
|
||||
return SignalExecutors.IO.submit(() -> {
|
||||
return SignalExecutors.UNBOUNDED.submit(() -> {
|
||||
boolean activeUser = recipient.resolve().getRegistered() == RegisteredState.REGISTERED;
|
||||
boolean systemContact = recipient.isSystemContact();
|
||||
String number = recipient.getAddress().serialize();
|
||||
@@ -368,7 +368,7 @@ public class DirectoryHelper {
|
||||
KeyStore iasKeyStore = getIasKeyStore(context);
|
||||
|
||||
for (Set<String> batch : batches) {
|
||||
Future<Set<String>> future = SignalExecutors.IO.submit(() -> {
|
||||
Future<Set<String>> future = SignalExecutors.UNBOUNDED.submit(() -> {
|
||||
return new HashSet<>(accountManager.getRegisteredUsers(iasKeyStore, batch, BuildConfig.MRENCLAVE));
|
||||
});
|
||||
futures.add(future);
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.TargetApi;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.View;
|
||||
@@ -32,7 +33,7 @@ public class LongClickCopySpan extends URLSpan {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint ds) {
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.bgColor = highlightColor;
|
||||
ds.setUnderlineText(!isHighlighted);
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.text.TextUtils;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import android.util.Pair;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
@@ -17,6 +16,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
@@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||
import org.thoughtcrime.securesms.mms.MmsSlide;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.StickerSlide;
|
||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
|
||||
@@ -40,6 +41,7 @@ public class MediaUtil {
|
||||
|
||||
public static final String IMAGE_PNG = "image/png";
|
||||
public static final String IMAGE_JPEG = "image/jpeg";
|
||||
public static final String IMAGE_WEBP = "image/webp";
|
||||
public static final String IMAGE_GIF = "image/gif";
|
||||
public static final String AUDIO_AAC = "audio/aac";
|
||||
public static final String AUDIO_UNSPECIFIED = "audio/*";
|
||||
@@ -50,7 +52,9 @@ public class MediaUtil {
|
||||
|
||||
public static Slide getSlideForAttachment(Context context, Attachment attachment) {
|
||||
Slide slide = null;
|
||||
if (isGif(attachment.getContentType())) {
|
||||
if (attachment.isSticker()) {
|
||||
slide = new StickerSlide(context, attachment);
|
||||
} else if (isGif(attachment.getContentType())) {
|
||||
slide = new GifSlide(context, attachment);
|
||||
} else if (isImageType(attachment.getContentType())) {
|
||||
slide = new ImageSlide(context, attachment);
|
||||
|
||||
45
src/org/thoughtcrime/securesms/util/ObservingLiveData.java
Normal file
45
src/org/thoughtcrime/securesms/util/ObservingLiveData.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.database.ContentObserver;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.ObservableContent;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* Implementation of {@link android.arch.lifecycle.LiveData} that will handle closing the contained
|
||||
* {@link Closeable} when the value changes.
|
||||
*/
|
||||
public class ObservingLiveData<E extends ObservableContent> extends MutableLiveData<E> {
|
||||
|
||||
private ContentObserver observer;
|
||||
|
||||
@Override
|
||||
public void setValue(E value) {
|
||||
E previous = getValue();
|
||||
|
||||
if (previous != null) {
|
||||
previous.unregisterContentObserver(observer);
|
||||
Util.close(previous);
|
||||
}
|
||||
|
||||
value.registerContentObserver(observer);
|
||||
|
||||
super.setValue(value);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
E value = getValue();
|
||||
|
||||
if (value != null) {
|
||||
value.unregisterContentObserver(observer);
|
||||
Util.close(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerContentObserver(@NonNull ContentObserver observer) {
|
||||
this.observer = observer;
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,10 @@ import android.content.Context;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.ArrayRes;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.DimenRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.TypedValue;
|
||||
|
||||
@@ -61,4 +60,10 @@ public class ResUtil {
|
||||
typedArray.recycle();
|
||||
return resourceIds;
|
||||
}
|
||||
|
||||
public static float getFloat(@NonNull Context context, @DimenRes int resId) {
|
||||
TypedValue value = new TypedValue();
|
||||
context.getResources().getValue(resId, value, true);
|
||||
return value.getFloat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.os.Vibrator;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.view.WindowManager;
|
||||
@@ -57,6 +56,6 @@ public class ServiceUtil {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
|
||||
public static @Nullable SubscriptionManager getSubscriptionManager(@NonNull Context context) {
|
||||
return ContextCompat.getSystemService(context, SubscriptionManager.class);
|
||||
return (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.*;
|
||||
@@ -210,7 +212,7 @@ public class SoftHashMap<K, V> implements Map<K, V> {
|
||||
return values != null && values.contains(value);
|
||||
}
|
||||
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
public void putAll(@NonNull Map<? extends K, ? extends V> m) {
|
||||
if (m == null || m.isEmpty()) {
|
||||
processQueue();
|
||||
return;
|
||||
@@ -220,12 +222,12 @@ public class SoftHashMap<K, V> implements Map<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<K> keySet() {
|
||||
public @NonNull Set<K> keySet() {
|
||||
processQueue();
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
public Collection<V> values() {
|
||||
public @NonNull Collection<V> values() {
|
||||
processQueue();
|
||||
Collection<K> keys = map.keySet();
|
||||
if (keys.isEmpty()) {
|
||||
@@ -245,7 +247,7 @@ public class SoftHashMap<K, V> implements Map<K, V> {
|
||||
/**
|
||||
* Creates a new entry, but wraps the value in a SoftValue instance to enable auto garbage collection.
|
||||
*/
|
||||
public V put(K key, V value) {
|
||||
public V put(@NonNull K key, @NonNull V value) {
|
||||
processQueue(); // throw out garbage collected values first
|
||||
SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue);
|
||||
SoftValue<V, K> previous = map.put(key, sv);
|
||||
@@ -275,7 +277,7 @@ public class SoftHashMap<K, V> implements Map<K, V> {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public Set<Map.Entry<K, V>> entrySet() {
|
||||
public @NonNull Set<Map.Entry<K, V>> entrySet() {
|
||||
processQueue(); // throw out garbage collected values first
|
||||
Collection<K> keys = map.keySet();
|
||||
if (keys.isEmpty()) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.util;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build.VERSION;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -42,8 +43,8 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
RecyclerView.State state)
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent,
|
||||
@NonNull RecyclerView.State state)
|
||||
{
|
||||
int position = parent.getChildAdapterPosition(view);
|
||||
int headerHeight = 0;
|
||||
@@ -106,7 +107,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
|
||||
final int count = parent.getChildCount();
|
||||
|
||||
for (int layoutPos = 0; layoutPos < count; layoutPos++) {
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.util;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
@@ -39,4 +41,8 @@ public class TelephonyUtil {
|
||||
final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS).getExtraInfo();
|
||||
}
|
||||
|
||||
public static boolean isAnyPstnLineBusy(@NonNull Context context) {
|
||||
return getManager(context).getCallState() != TelephonyManager.CALL_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +180,10 @@ public class TextSecurePreferences {
|
||||
|
||||
private static final String GIF_GRID_LAYOUT = "pref_gif_grid_layout";
|
||||
|
||||
private static final String SEEN_STICKER_INTRO_TOOLTIP = "pref_seen_sticker_intro_tooltip";
|
||||
|
||||
private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode";
|
||||
|
||||
public static boolean isScreenLockEnabled(@NonNull Context context) {
|
||||
return getBooleanPreference(context, SCREEN_LOCK, false);
|
||||
}
|
||||
@@ -509,13 +513,13 @@ public class TextSecurePreferences {
|
||||
|
||||
public static void setFcmToken(Context context, String registrationId) {
|
||||
setStringPreference(context, GCM_REGISTRATION_ID_PREF, registrationId);
|
||||
setIntegerPrefrence(context, GCM_REGISTRATION_ID_VERSION_PREF, Util.getCurrentApkReleaseVersion(context));
|
||||
setIntegerPrefrence(context, GCM_REGISTRATION_ID_VERSION_PREF, Util.getCanonicalVersionCode());
|
||||
}
|
||||
|
||||
public static String getFcmToken(Context context) {
|
||||
int storedRegistrationIdVersion = getIntegerPreference(context, GCM_REGISTRATION_ID_VERSION_PREF, 0);
|
||||
|
||||
if (storedRegistrationIdVersion != Util.getCurrentApkReleaseVersion(context)) {
|
||||
if (storedRegistrationIdVersion != Util.getCanonicalVersionCode()) {
|
||||
return null;
|
||||
} else {
|
||||
return getStringPreference(context, GCM_REGISTRATION_ID_PREF, null);
|
||||
@@ -1078,6 +1082,23 @@ public class TextSecurePreferences {
|
||||
setBooleanPreference(context, NEEDS_MESSAGE_PULL, needsMessagePull);
|
||||
}
|
||||
|
||||
public static boolean hasSeenStickerIntroTooltip(Context context) {
|
||||
return getBooleanPreference(context, SEEN_STICKER_INTRO_TOOLTIP, false);
|
||||
}
|
||||
|
||||
public static void setHasSeenStickerIntroTooltip(Context context, boolean seenStickerTooltip) {
|
||||
setBooleanPreference(context, SEEN_STICKER_INTRO_TOOLTIP, seenStickerTooltip);
|
||||
}
|
||||
|
||||
public static void setMediaKeyboardMode(Context context, MediaKeyboardMode mode) {
|
||||
setStringPreference(context, MEDIA_KEYBOARD_MODE, mode.name());
|
||||
}
|
||||
|
||||
public static MediaKeyboardMode getMediaKeyboardMode(Context context) {
|
||||
String name = getStringPreference(context, MEDIA_KEYBOARD_MODE, MediaKeyboardMode.EMOJI.name());
|
||||
return MediaKeyboardMode.valueOf(name);
|
||||
}
|
||||
|
||||
public static void setBooleanPreference(Context context, String key, boolean value) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
|
||||
}
|
||||
@@ -1127,6 +1148,11 @@ public class TextSecurePreferences {
|
||||
}
|
||||
}
|
||||
|
||||
// NEVER rename these -- they're persisted by name
|
||||
public enum MediaKeyboardMode {
|
||||
EMOJI, STICKER
|
||||
}
|
||||
|
||||
// region Loki
|
||||
public static long getBackgroundPollTime(Context context) {
|
||||
return getLongPreference(context, "background_poll_time", 0L);
|
||||
|
||||
@@ -37,9 +37,12 @@ public class ThemeUtil {
|
||||
TypedValue outValue = new TypedValue();
|
||||
|
||||
if (context.getTheme().resolveAttribute(attribute, outValue, true)) {
|
||||
return outValue.coerceToString().toString();
|
||||
} else {
|
||||
return defaultValue;
|
||||
CharSequence charSequence = outValue.coerceToString();
|
||||
if (charSequence != null) {
|
||||
return charSequence.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
45
src/org/thoughtcrime/securesms/util/Throttler.java
Normal file
45
src/org/thoughtcrime/securesms/util/Throttler.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
/**
|
||||
* A class that will throttle the number of runnables executed to be at most once every specified
|
||||
* interval.
|
||||
*
|
||||
* Useful for performing actions in response to rapid user input where you want to take action on
|
||||
* the initial input but prevent follow-up spam.
|
||||
*
|
||||
* This is different from {@link Debouncer} in that it will run the first runnable immediately
|
||||
* instead of waiting for input to die down.
|
||||
*
|
||||
* See http://rxmarbles.com/#throttle
|
||||
*/
|
||||
public class Throttler {
|
||||
|
||||
private static final int WHAT = 8675309;
|
||||
|
||||
private final Handler handler;
|
||||
private final long threshold;
|
||||
|
||||
/**
|
||||
* @param threshold Only one runnable will be executed via {@link #publish(Runnable)} every
|
||||
* {@code threshold} milliseconds.
|
||||
*/
|
||||
public Throttler(long threshold) {
|
||||
this.handler = new Handler();
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
public void publish(Runnable runnable) {
|
||||
if (handler.hasMessages(WHAT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
runnable.run();
|
||||
handler.sendMessageDelayed(handler.obtainMessage(WHAT), threshold);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Handler;
|
||||
@@ -39,7 +38,6 @@ import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.StyleSpan;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import com.google.android.mms.pdu_alt.CharacterSets;
|
||||
import com.google.android.mms.pdu_alt.EncodedStringValue;
|
||||
@@ -50,6 +48,7 @@ import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import network.loki.messenger.BuildConfig;
|
||||
import org.thoughtcrime.securesms.components.ComposeText;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
@@ -60,7 +59,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
@@ -370,7 +368,24 @@ public class Util {
|
||||
return context.getPackageName().equals(Telephony.Sms.getDefaultSmsPackage(context));
|
||||
}
|
||||
|
||||
public static int getCurrentApkReleaseVersion(Context context) {
|
||||
/**
|
||||
* The app version.
|
||||
* <p>
|
||||
* This code should be used in all places that compare app versions rather than
|
||||
* {@link #getManifestApkVersion(Context)} or {@link BuildConfig#VERSION_CODE}.
|
||||
*/
|
||||
public static int getCanonicalVersionCode() {
|
||||
return BuildConfig.CANONICAL_VERSION_CODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BuildConfig#VERSION_CODE} may not be the actual version due to ABI split code adding a
|
||||
* postfix after BuildConfig is generated.
|
||||
* <p>
|
||||
* However, in most cases you want to use {@link BuildConfig#CANONICAL_VERSION_CODE} via
|
||||
* {@link #getCanonicalVersionCode()}
|
||||
*/
|
||||
public static int getManifestApkVersion(Context context) {
|
||||
try {
|
||||
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
|
||||
@@ -31,7 +31,7 @@ public class VerifySpan extends ClickableSpan {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
public void onClick(@NonNull View widget) {
|
||||
Intent intent = new Intent(context, VerifyIdentityActivity.class);
|
||||
intent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, address);
|
||||
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey));
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class VersionTracker {
|
||||
|
||||
|
||||
public static int getLastSeenVersion(Context context) {
|
||||
public static int getLastSeenVersion(@NonNull Context context) {
|
||||
return TextSecurePreferences.getLastVersionCode(context);
|
||||
}
|
||||
|
||||
public static void updateLastSeenVersion(Context context) {
|
||||
public static void updateLastSeenVersion(@NonNull Context context) {
|
||||
try {
|
||||
int currentVersionCode = Util.getCurrentApkReleaseVersion(context);
|
||||
int currentVersionCode = Util.getCanonicalVersionCode();
|
||||
TextSecurePreferences.setLastVersionCode(context, currentVersionCode);
|
||||
} catch (IOException ioe) {
|
||||
throw new AssertionError(ioe);
|
||||
|
||||
@@ -240,4 +240,16 @@ public class ViewUtil {
|
||||
public static void setPaddingBottom(@NonNull View view, int padding) {
|
||||
view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), padding);
|
||||
}
|
||||
|
||||
public static boolean isPointInsideView(@NonNull View view, float x, float y) {
|
||||
int[] location = new int[2];
|
||||
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
int viewX = location[0];
|
||||
int viewY = location[1];
|
||||
|
||||
return x > viewX && x < viewX + view.getWidth() &&
|
||||
y > viewY && y < viewY + view.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public class WakeLockUtil {
|
||||
|
||||
/**
|
||||
* Run a runnable with a wake lock. Ensures that the lock is safely acquired and released.
|
||||
*
|
||||
* @param tag will be prefixed with "signal:" if it does not already start with it.
|
||||
*/
|
||||
public static void runWithLock(@NonNull Context context, int lockType, long timeout, @NonNull String tag, @NonNull Runnable task) {
|
||||
WakeLock wakeLock = null;
|
||||
@@ -26,7 +28,11 @@ public class WakeLockUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tag will be prefixed with "signal:" if it does not already start with it.
|
||||
*/
|
||||
public static WakeLock acquire(@NonNull Context context, int lockType, long timeout, @NonNull String tag) {
|
||||
tag = prefixTag(tag);
|
||||
try {
|
||||
PowerManager powerManager = ServiceUtil.getPowerManager(context);
|
||||
WakeLock wakeLock = powerManager.newWakeLock(lockType, tag);
|
||||
@@ -41,7 +47,11 @@ public class WakeLockUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tag will be prefixed with "signal:" if it does not already start with it.
|
||||
*/
|
||||
public static void release(@NonNull WakeLock wakeLock, @NonNull String tag) {
|
||||
tag = prefixTag(tag);
|
||||
try {
|
||||
if (wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
@@ -53,4 +63,8 @@ public class WakeLockUtil {
|
||||
Log.w(TAG, "Failed to release wakelock with tag: " + tag, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String prefixTag(@NonNull String tag) {
|
||||
return tag.startsWith("signal:") ? tag : "signal:" + tag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,29 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class SignalExecutors {
|
||||
|
||||
public static final ExecutorService IO = Executors.newCachedThreadPool(new ThreadFactory() {
|
||||
private final AtomicInteger counter = new AtomicInteger();
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable r) {
|
||||
return new Thread(r, "signal-io-" + counter.getAndIncrement());
|
||||
}
|
||||
});
|
||||
|
||||
public static final ExecutorService UNBOUNDED = Executors.newCachedThreadPool(new NumberedThreadFactory("signal-unbounded"));
|
||||
public static final ExecutorService BOUNDED = Executors.newFixedThreadPool(Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)), new NumberedThreadFactory("signal-bounded"));
|
||||
public static final ExecutorService SERIAL = Executors.newSingleThreadExecutor(new NumberedThreadFactory("signal-serial"));
|
||||
|
||||
public static ExecutorService newCachedSingleThreadExecutor(final String name) {
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 15, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, name));
|
||||
executor.allowCoreThreadTimeOut(true);
|
||||
return executor;
|
||||
}
|
||||
|
||||
private static class NumberedThreadFactory implements ThreadFactory {
|
||||
|
||||
private final String baseName;
|
||||
private final AtomicInteger counter;
|
||||
|
||||
NumberedThreadFactory(@NonNull String baseName) {
|
||||
this.baseName = baseName;
|
||||
this.counter = new AtomicInteger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable r) {
|
||||
return new Thread(r, baseName + "-" + counter.getAndIncrement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.util.spans;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.MetricAffectingSpan;
|
||||
|
||||
@@ -13,7 +14,7 @@ public class CenterAlignedRelativeSizeSpan extends MetricAffectingSpan {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasureState(TextPaint p) {
|
||||
public void updateMeasureState(@NonNull TextPaint p) {
|
||||
updateDrawState(p);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user