Merge Signal 4.41.0

This commit is contained in:
Niels Andriesse
2019-08-07 16:48:54 +10:00
584 changed files with 9504 additions and 1558 deletions

View File

@@ -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));

View File

@@ -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
{

View File

@@ -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()

View File

@@ -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.

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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()) {

View File

@@ -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++) {

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View 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);
}
}

View File

@@ -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) {

View File

@@ -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));

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}