Merge branch 'dev' into strings-squashed

This commit is contained in:
Al Lansley 2024-08-19 08:41:47 +10:00
commit 558684a56d
55 changed files with 244 additions and 1171 deletions

View File

@ -290,10 +290,6 @@
android:name="org.thoughtcrime.securesms.scribbles.StickerSelectActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Theme.Session.ForceDark" />
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat" />
<activity
android:name="org.thoughtcrime.securesms.ShortcutLauncherActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"

View File

@ -38,7 +38,9 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.loader.app.LoaderManager;
@ -435,11 +437,13 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
mode.getMenuInflater().inflate(R.menu.media_overview_context, menu);
mode.setTitle("1");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getActivity().getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
FragmentActivity activity = getActivity();
if (activity == null) return false;
Window window = activity.getWindow();
originalStatusBarColor = window.getStatusBarColor();
window.setStatusBarColor(ContextCompat.getColor(activity, R.color.action_mode_status_bar));
return true;
}
@ -469,11 +473,12 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
getListAdapter().clearSelection();
((MediaOverviewActivity) getActivity()).onExitMultiSelect();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(originalStatusBarColor);
}
MediaOverviewActivity activity = ((MediaOverviewActivity) getActivity());
if(activity == null) return;
activity.onExitMultiSelect();
activity.getWindow().setStatusBarColor(originalStatusBarColor);
}
}
}

View File

@ -55,6 +55,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.squareup.phrase.Phrase;
import java.io.IOException;
import java.util.Locale;
@ -76,8 +78,6 @@ import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.AttachmentUtil;

View File

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
@ -31,7 +33,6 @@ import androidx.fragment.app.Fragment
import com.squareup.phrase.Phrase
import network.loki.messenger.R
import org.session.libsession.utilities.StringSubstitutionConstants.URL_KEY
import org.thoughtcrime.securesms.conversation.v2.Util.writeTextToClipboard
import org.thoughtcrime.securesms.util.toPx
@DslMarker
@ -171,6 +172,12 @@ class SessionDialogBuilder(val context: Context) {
fun Context.showSessionDialog(build: SessionDialogBuilder.() -> Unit): AlertDialog =
SessionDialogBuilder(this).apply { build() }.show()
public fun Context.copyURLToClipboard(url: String) {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(url, url)
clipboard.setPrimaryClip(clip)
}
// Method to show a dialog used to open or copy a URL
fun Context.showOpenUrlDialog(url: String, showCloseButton: Boolean = true): AlertDialog {
return SessionDialogBuilder(this).apply {
@ -281,7 +288,7 @@ fun Context.showOpenUrlDialog(url: String, showCloseButton: Boolean = true): Ale
// Note: The text and contentDescription are set on the `copyUrlButton` by the function.
contentView.addView(scrollView)
dangerButton(R.string.open, R.string.AccessibilityId_urlOpenBrowser) { openUrl(url) }
copyUrlButton { writeTextToClipboard(context, url) }
copyUrlButton { context.copyURLToClipboard(url) }
}.show()
}

View File

@ -1,22 +1,19 @@
package org.thoughtcrime.securesms.audio;
import android.annotation.TargetApi;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Build;
import org.session.libsignal.utilities.Log;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class AudioCodec {
private static final String TAG = AudioCodec.class.getSimpleName();

View File

@ -43,7 +43,7 @@ public class AudioSlidePlayer implements SensorEventListener {
private final @NonNull Handler progressEventHandler;
private final @NonNull AudioManager audioManager;
private final @NonNull SensorManager sensorManager;
private final @NonNull Sensor proximitySensor;
private final Sensor proximitySensor;
private final @Nullable WakeLock wakeLock;
private @NonNull WeakReference<Listener> listener;
@ -129,7 +129,9 @@ public class AudioSlidePlayer implements SensorEventListener {
mediaPlayer.seekTo((long) (mediaPlayer.getDuration() * progress));
}
sensorManager.registerListener(AudioSlidePlayer.this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
if(proximitySensor != null) {
sensorManager.registerListener(AudioSlidePlayer.this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
}
setPlaying(AudioSlidePlayer.this);
}

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.text.InputType;
import android.text.Spannable;
@ -16,15 +15,14 @@ import android.view.inputmethod.InputConnection;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.os.BuildCompat;
import androidx.core.view.inputmethod.EditorInfoCompat;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
import org.session.libsignal.utilities.Log;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
public class ComposeText extends EmojiEditText {
@ -136,7 +134,6 @@ public class ComposeText extends EmojiEditText {
editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
if (Build.VERSION.SDK_INT < 21) return inputConnection;
if (mediaListener == null) return inputConnection;
if (inputConnection == null) return null;
@ -154,7 +151,6 @@ public class ComposeText extends EmojiEditText {
}
}
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR2)
private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener {
private static final String TAG = CommitContentListener.class.getSimpleName();

View File

@ -1,9 +1,7 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
@ -27,7 +25,6 @@ public class ConversationItemAlertView extends LinearLayout {
initialize(attrs);
}
@TargetApi(VERSION_CODES.HONEYCOMB)
public ConversationItemAlertView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(attrs);

View File

@ -1,9 +1,6 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@ -11,6 +8,8 @@ import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.widget.LinearLayout;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
public class HidingLinearLayout extends LinearLayout {
public HidingLinearLayout(Context context) {
@ -21,7 +20,6 @@ public class HidingLinearLayout extends LinearLayout {
super(context, attrs);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public HidingLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

View File

@ -1,13 +1,12 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
public class InputPanel extends LinearLayout {
public InputPanel(Context context) {
@ -18,7 +17,6 @@ public class InputPanel extends LinearLayout {
super(context, attrs);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public InputPanel(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

View File

@ -16,26 +16,25 @@
*/
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.preference.PreferenceManager;
import androidx.appcompat.widget.LinearLayoutCompat;
import android.util.AttributeSet;
import org.session.libsignal.utilities.Log;
import android.view.Surface;
import android.view.View;
import network.loki.messenger.R;
import androidx.appcompat.widget.LinearLayoutCompat;
import org.session.libsession.utilities.ServiceUtil;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.Log;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import network.loki.messenger.R;
/**
* LinearLayout that, when a view container, will report back when it thinks a soft keyboard
* has been opened and what its height would be.
@ -95,7 +94,7 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
}
private void updateKeyboardState() {
if (viewInset == 0 && Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) viewInset = getViewInset();
if (viewInset == 0) viewInset = getViewInset();
getWindowVisibleDisplayFrame(rect);
@ -118,7 +117,6 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
}
}
@TargetApi(VERSION_CODES.LOLLIPOP)
private int getViewInset() {
try {
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");

View File

@ -3,24 +3,24 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;
import android.view.Window;
import android.widget.FrameLayout;
import network.loki.messenger.R;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.RequestManager;
import org.session.libsession.utilities.Stub;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.video.VideoPlayer;
import org.session.libsession.utilities.Stub;
import java.io.IOException;
import network.loki.messenger.R;
public class MediaView extends FrameLayout {
private ZoomingImageView imageView;
@ -41,12 +41,6 @@ public class MediaView extends FrameLayout {
initialize();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public MediaView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
private void initialize() {
inflate(getContext(), R.layout.media_view, this);

View File

@ -1,19 +1,11 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@ -21,16 +13,24 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.signature.MediaStoreSignature;
import network.loki.messenger.R;
import org.session.libsession.utilities.ViewUtil;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader;
import com.bumptech.glide.Glide;
import org.session.libsession.utilities.ViewUtil;
import network.loki.messenger.R;
public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.LoaderCallbacks<Cursor> {
@ -130,14 +130,12 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
}
@TargetApi(16)
@SuppressWarnings("SuspiciousNameCombination")
private String getWidthColumn(int orientation) {
if (orientation == 0 || orientation == 180) return MediaStore.Images.ImageColumns.WIDTH;
else return MediaStore.Images.ImageColumns.HEIGHT;
}
@TargetApi(16)
@SuppressWarnings("SuspiciousNameCombination")
private String getHeightColumn(int orientation) {
if (orientation == 0 || orientation == 180) return MediaStore.Images.ImageColumns.HEIGHT;

View File

@ -93,15 +93,11 @@ public class SearchToolbar extends LinearLayout {
searchItem.expandActionView();
if (Build.VERSION.SDK_INT >= 21) {
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, 0, getWidth());
animator.setDuration(400);
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, 0, getWidth());
animator.setDuration(400);
setVisibility(View.VISIBLE);
animator.start();
} else {
setVisibility(View.VISIBLE);
}
setVisibility(View.VISIBLE);
animator.start();
}
}
@ -115,19 +111,15 @@ public class SearchToolbar extends LinearLayout {
if (listener != null) listener.onSearchClosed();
if (Build.VERSION.SDK_INT >= 21) {
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, getWidth(), 0);
animator.setDuration(400);
animator.addListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(View.INVISIBLE);
}
});
animator.start();
} else {
setVisibility(View.INVISIBLE);
}
Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, getWidth(), 0);
animator.setDuration(400);
animator.addListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(View.INVISIBLE);
}
});
animator.start();
}
}

View File

@ -1,10 +1,7 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.widget.FrameLayout;
@ -24,7 +21,7 @@ public class SquareFrameLayout extends FrameLayout {
this(context, attrs, 0);
}
@TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused")
@SuppressWarnings("unused")
public SquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

View File

@ -15,7 +15,6 @@
package org.thoughtcrime.securesms.components.camera;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
@ -28,26 +27,26 @@ import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Build.VERSION;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import org.session.libsignal.utilities.Log;
import android.view.OrientationEventListener;
import android.view.ViewGroup;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.session.libsignal.utilities.guava.Optional;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.util.BitmapUtil;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import network.loki.messenger.R;
@SuppressWarnings("deprecation")
public class CameraView extends ViewGroup {
private static final String TAG = CameraView.class.getSimpleName();
@ -91,7 +90,6 @@ public class CameraView extends ViewGroup {
addView(surface);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onResume() {
if (state != State.PAUSED) return;
state = State.RESUMED;
@ -255,33 +253,15 @@ public class CameraView extends ViewGroup {
return Camera.getNumberOfCameras() > 1;
}
public boolean isRearCamera() {
return cameraId == CameraInfo.CAMERA_FACING_BACK;
}
public void flipCamera() {
if (Camera.getNumberOfCameras() > 1) {
cameraId = cameraId == CameraInfo.CAMERA_FACING_BACK
? CameraInfo.CAMERA_FACING_FRONT
: CameraInfo.CAMERA_FACING_BACK;
onPause();
onResume();
TextSecurePreferences.setDirectCaptureCameraId(getContext(), cameraId);
}
}
@TargetApi(14)
private void onCameraReady(final @NonNull Camera camera) {
final Parameters parameters = camera.getParameters();
if (VERSION.SDK_INT >= 14) {
parameters.setRecordingHint(true);
final List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
parameters.setRecordingHint(true);
final List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
displayOrientation = CameraUtils.getCameraDisplayOrientation(getActivity(), getCameraInfo());
@ -465,7 +445,7 @@ public class CameraView extends ViewGroup {
}
final float newWidth = visibleRect.width() * scale;
final float newHeight = visibleRect.height() * scale;
final float centerX = (VERSION.SDK_INT < 14 || isTroublemaker()) ? previewWidth - newWidth / 2 : previewWidth / 2;
final float centerX = (isTroublemaker()) ? previewWidth - newWidth / 2 : previewWidth / 2;
final float centerY = previewHeight / 2;
visibleRect.set((int) (centerX - newWidth / 2),

View File

@ -16,39 +16,19 @@
*/
package org.thoughtcrime.securesms.conversation.v2
import android.annotation.TargetApi
import android.app.ActivityManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.text.Spannable
import android.text.SpannableString
import android.text.TextUtils
import android.text.style.StyleSpan
import android.view.View
import com.annimon.stream.Stream
import com.google.android.mms.pdu_alt.CharacterSets
import com.google.android.mms.pdu_alt.EncodedStringValue
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.UnsupportedEncodingException
import java.util.Collections
import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.min
import network.loki.messenger.R
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.components.ComposeText
object Util {
private val TAG: String = Log.tag(Util::class.java)
private val BUILD_LIFESPAN = TimeUnit.DAYS.toMillis(90)
fun <T> asList(vararg elements: T): List<T> {
val result = mutableListOf<T>() // LinkedList()
Collections.addAll(result, *elements)
@ -104,19 +84,6 @@ object Util {
return sb.toString()
}
fun rightPad(value: String, length: Int): String {
if (value.length >= length) {
return value
}
val out = StringBuilder(value)
while (out.length < length) {
out.append(" ")
}
return out.toString()
}
fun isEmpty(value: Array<EncodedStringValue?>?): Boolean {
return value == null || value.size == 0
}
@ -130,65 +97,7 @@ object Util {
}
fun isEmpty(charSequence: CharSequence?): Boolean {
return charSequence == null || charSequence.length == 0
}
fun hasItems(collection: Collection<*>?): Boolean {
return collection != null && !collection.isEmpty()
}
fun <K, V> getOrDefault(map: Map<K, V>, key: K, defaultValue: V): V? {
return if (map.containsKey(key)) map[key] else defaultValue
}
fun getFirstNonEmpty(vararg values: String?): String {
for (value in values) {
if (!value.isNullOrEmpty()) { return value }
}
return ""
}
fun emptyIfNull(value: String?): String {
return value ?: ""
}
fun emptyIfNull(value: CharSequence?): CharSequence {
return value ?: ""
}
fun getBoldedString(value: String?): CharSequence {
val spanned = SpannableString(value)
spanned.setSpan(
StyleSpan(Typeface.BOLD), 0,
spanned.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return spanned
}
fun toIsoString(bytes: ByteArray?): String {
try {
return String(bytes!!, charset(CharacterSets.MIMENAME_ISO_8859_1))
} catch (e: UnsupportedEncodingException) {
throw AssertionError("ISO_8859_1 must be supported!")
}
}
fun toIsoBytes(isoString: String): ByteArray {
try {
return isoString.toByteArray(charset(CharacterSets.MIMENAME_ISO_8859_1))
} catch (e: UnsupportedEncodingException) {
throw AssertionError("ISO_8859_1 must be supported!")
}
}
fun toUtf8Bytes(utf8String: String): ByteArray {
try {
return utf8String.toByteArray(charset(CharacterSets.MIMENAME_UTF_8))
} catch (e: UnsupportedEncodingException) {
throw AssertionError("UTF_8 must be supported!")
}
return charSequence.isNullOrEmpty()
}
fun wait(lock: Any, timeout: Long) {
@ -225,20 +134,6 @@ object Util {
return parts
}
fun combine(vararg elements: ByteArray?): ByteArray {
try {
val baos = ByteArrayOutputStream()
for (element in elements) {
baos.write(element)
}
return baos.toByteArray()
} catch (e: IOException) {
throw AssertionError(e)
}
}
fun trim(input: ByteArray?, length: Int): ByteArray {
val result = ByteArray(length)
System.arraycopy(input, 0, result, 0, result.size)
@ -251,26 +146,6 @@ object Util {
else Uri.parse(uri)
}
@TargetApi(VERSION_CODES.KITKAT)
fun isLowMemory(context: Context): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return (VERSION.SDK_INT >= VERSION_CODES.KITKAT && activityManager.isLowRamDevice) ||
activityManager.largeMemoryClass <= 64
}
fun clamp(value: Int, min: Int, max: Int): Int {
return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toInt()
}
fun clamp(value: Long, min: Long, max: Long): Long {
return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toLong()
}
fun clamp(value: Float, min: Float, max: Float): Float {
return min(max(value.toDouble(), min.toDouble()), max.toDouble()).toFloat()
}
/**
* Returns half of the difference between the given length, and the length when scaled by the
* given scale.
@ -280,74 +155,6 @@ object Util {
return (length - scaledLength) / 2
}
fun readTextFromClipboard(context: Context): String? {
run {
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
return if (clipboardManager.hasPrimaryClip() && clipboardManager.primaryClip!!.itemCount > 0) {
clipboardManager.primaryClip!!.getItemAt(0).text.toString()
} else {
null
}
}
}
fun writeTextToClipboard(context: Context, text: String) {
writeTextToClipboard(context, context.getString(R.string.app_name), text)
}
fun writeTextToClipboard(context: Context, label: String, text: String) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(label, text)
clipboard.setPrimaryClip(clip)
}
fun toIntExact(value: Long): Int {
if (value.toInt().toLong() != value) {
throw ArithmeticException("integer overflow")
}
return value.toInt()
}
fun isEquals(first: Long?, second: Long): Boolean {
return first != null && first == second
}
@SafeVarargs
fun <T> concatenatedList(vararg items: Collection<T>): List<T> {
val concat: MutableList<T> = ArrayList(
Stream.of(*items).reduce(0) { sum: Int, list: Collection<T> -> sum + list.size })
for (list in items) {
concat.addAll(list)
}
return concat
}
fun isLong(value: String): Boolean {
try {
value.toLong()
return true
} catch (e: NumberFormatException) {
return false
}
}
fun parseInt(integer: String, defaultValue: Int): Int {
return try {
integer.toInt()
} catch (e: NumberFormatException) {
defaultValue
}
}
// Method to determine if we're currently in a left-to-right or right-to-left language like Arabic
fun usingRightToLeftLanguage(context: Context): Boolean {
val config = context.resources.configuration
return config.layoutDirection == View.LAYOUT_DIRECTION_RTL
}
// Method to determine if we're currently in a left-to-right or right-to-left language like Arabic
fun usingLeftToRightLanguage(context: Context): Boolean {
val config = context.resources.configuration

View File

@ -148,11 +148,8 @@ class InputBarButton : RelativeLayout {
private fun onDown(event: MotionEvent) {
expand()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
} else {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
val newLongPressCallback = Runnable { onLongPress?.invoke() }
this.longPressCallback = newLongPressCallback

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar
import android.content.Context
import android.content.res.Resources
import android.net.Uri
import android.os.Build
import android.util.AttributeSet
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
@ -57,7 +56,7 @@ class InputBarEditText : AppCompatEditText {
InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, opts ->
val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
// read and display inputContentInfo asynchronously
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) {
if (lacksPermission) {
try {
inputContentInfo.requestPermission()
} catch (e: Exception) {

View File

@ -59,25 +59,17 @@ public class AttachmentSecretProvider {
{
AttachmentSecret attachmentSecret = AttachmentSecret.fromString(unencryptedSecret);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return attachmentSecret;
} else {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
TextSecurePreferences.setAttachmentUnencryptedSecret(context, null);
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
TextSecurePreferences.setAttachmentUnencryptedSecret(context, null);
return attachmentSecret;
}
return attachmentSecret;
}
private AttachmentSecret getEncryptedAttachmentSecret(@NonNull String serializedEncryptedSecret) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
} else {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
return AttachmentSecret.fromString(new String(KeyStoreHelper.unseal(encryptedSecret)));
}
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
return AttachmentSecret.fromString(new String(KeyStoreHelper.unseal(encryptedSecret)));
}
private AttachmentSecret createAndStoreAttachmentSecret(@NonNull Context context) {
@ -91,12 +83,8 @@ public class AttachmentSecretProvider {
}
private void storeAttachmentSecret(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
} else {
TextSecurePreferences.setAttachmentUnencryptedSecret(context, attachmentSecret.serialize());
}
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(attachmentSecret.serialize().getBytes());
TextSecurePreferences.setAttachmentEncryptedSecret(context, encryptedSecret.serialize());
}
}

View File

@ -42,9 +42,8 @@ class BiometricSecretProvider {
builder.setUnlockedDeviceRequired(true)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(true)
}
builder.setInvalidatedByBiometricEnrollment(true)
keyGenerator.initialize(builder.build())
keyGenerator.generateKeyPair()
}

View File

@ -36,28 +36,20 @@ public class DatabaseSecretProvider {
try {
DatabaseSecret databaseSecret = new DatabaseSecret(unencryptedSecret);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return databaseSecret;
} else {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
TextSecurePreferences.setDatabaseUnencryptedSecret(context, null);
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
TextSecurePreferences.setDatabaseUnencryptedSecret(context, null);
return databaseSecret;
}
return databaseSecret;
} catch (IOException e) {
throw new AssertionError(e);
}
}
private DatabaseSecret getEncryptedDatabaseSecret(@NonNull String serializedEncryptedSecret) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
} else {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
return new DatabaseSecret(KeyStoreHelper.unseal(encryptedSecret));
}
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
return new DatabaseSecret(KeyStoreHelper.unseal(encryptedSecret));
}
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
@ -66,12 +58,8 @@ public class DatabaseSecretProvider {
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
} else {
TextSecurePreferences.setDatabaseUnencryptedSecret(context, databaseSecret.asString());
}
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
return databaseSecret;
}

View File

@ -129,27 +129,19 @@ public class IdentityKeyUtil {
}
private static String getUnencryptedSecret(String key, String unencryptedSecret, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return unencryptedSecret;
} else {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(unencryptedSecret.getBytes());
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(unencryptedSecret.getBytes());
// save the encrypted suffix secret "key_encrypted"
save(context,key+ENCRYPTED_SUFFIX,encryptedSecret.serialize());
// delete the regular secret "key"
delete(context,key);
// save the encrypted suffix secret "key_encrypted"
save(context,key+ENCRYPTED_SUFFIX,encryptedSecret.serialize());
// delete the regular secret "key"
delete(context,key);
return unencryptedSecret;
}
return unencryptedSecret;
}
private static String getEncryptedSecret(String encryptedSecret) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
} else {
KeyStoreHelper.SealedData sealedData = KeyStoreHelper.SealedData.fromString(encryptedSecret);
return new String(KeyStoreHelper.unseal(sealedData));
}
KeyStoreHelper.SealedData sealedData = KeyStoreHelper.SealedData.fromString(encryptedSecret);
return new String(KeyStoreHelper.unseal(sealedData));
}
@ -157,17 +149,14 @@ public class IdentityKeyUtil {
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
Editor preferencesEditor = preferences.edit();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
boolean isEncryptedSuffix = key.endsWith(ENCRYPTED_SUFFIX);
if (isEncryptedSuffix) {
preferencesEditor.putString(key, value);
} else {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(value.getBytes());
preferencesEditor.putString(key+ENCRYPTED_SUFFIX, encryptedSecret.serialize());
}
} else {
boolean isEncryptedSuffix = key.endsWith(ENCRYPTED_SUFFIX);
if (isEncryptedSuffix) {
preferencesEditor.putString(key, value);
} else {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(value.getBytes());
preferencesEditor.putString(key+ENCRYPTED_SUFFIX, encryptedSecret.serialize());
}
if (!preferencesEditor.commit()) throw new AssertionError("failed to save identity key/value to shared preferences");
}

View File

@ -966,11 +966,6 @@ public class AttachmentDatabase extends Database {
@SuppressLint("NewApi")
private ThumbnailData generateVideoThumbnail(AttachmentId attachmentId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Log.w(TAG, "Video thumbnails not supported...");
return null;
}
DataInfo dataInfo = getAttachmentDataFileInfo(attachmentId, DATA);
if (dataInfo == null) {

View File

@ -256,11 +256,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
String channelId = context.getString(R.string.failures);
if (NotificationChannels.supported()) {
NotificationChannel channel = new NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH);
channel.enableVibration(true);
notificationManager.createNotificationChannel(channel);
}
NotificationChannel channel = new NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH);
channel.enableVibration(true);
notificationManager.createNotificationChannel(channel);
CharSequence errorTxt = Phrase.from(context, R.string.databaseErrorGeneric)
.put(APP_NAME_KEY, R.string.app_name)
@ -274,10 +272,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
.setContentText(errorTxt)
.setAutoCancel(true);
if (!NotificationChannels.supported()) {
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
}
notificationManager.notify(5874, builder.build());
// Throw the error (app will crash but there is nothing else we can do unfortunately)

View File

@ -15,6 +15,7 @@ import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.squareup.phrase.Phrase
import kotlinx.coroutines.Dispatchers
@ -40,7 +41,6 @@ import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
import org.thoughtcrime.securesms.util.getAccentColor
import org.thoughtcrime.securesms.util.getColorWithID
class PathActivity : PassphraseRequiredActionBarActivity() {
private lateinit var binding: ActivityPathBinding
@ -292,7 +292,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
private fun expand() {
dotView.animateSizeChange(R.dimen.path_row_dot_size, R.dimen.path_row_expanded_dot_size)
@ColorRes val startColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
val startColor = context.resources.getColorWithID(startColorID, context.theme)
val startColor = ContextCompat.getColor(context, startColorID)
val endColor = context.getAccentColor()
GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor)
}
@ -301,7 +301,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
dotView.animateSizeChange(R.dimen.path_row_expanded_dot_size, R.dimen.path_row_dot_size)
@ColorRes val endColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
val startColor = context.getAccentColor()
val endColor = context.resources.getColorWithID(endColorID, context.theme)
val endColor = ContextCompat.getColor(context, endColorID)
GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor)
}

View File

@ -9,6 +9,7 @@ import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.lifecycle.coroutineScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kotlinx.coroutines.Dispatchers
@ -17,7 +18,6 @@ import kotlinx.coroutines.withContext
import network.loki.messenger.R
import org.session.libsession.snode.OnionRequestAPI
import org.thoughtcrime.securesms.conversation.v2.ViewUtil
import org.thoughtcrime.securesms.util.getColorWithID
import org.thoughtcrime.securesms.util.toPx
class PathStatusView : View {
@ -104,7 +104,7 @@ class PathStatusView : View {
sessionShadowColor = hasPathsColor
} else {
setBackgroundResource(R.drawable.paths_building_dot)
val pathsBuildingColor = resources.getColorWithID(R.color.paths_building, context.theme)
val pathsBuildingColor = ContextCompat.getColor(context, R.color.paths_building)
mainColor = pathsBuildingColor
sessionShadowColor = pathsBuildingColor
}

View File

@ -8,11 +8,6 @@ public interface Constraint {
boolean isMet();
@NonNull String getFactoryKey();
@RequiresApi(26)
void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder);
interface Factory<T extends Constraint> {
T create();
}

View File

@ -28,17 +28,6 @@ public class NetworkConstraint implements Constraint {
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
@Override
public @NonNull String getFactoryKey() {
return KEY;
}
@RequiresApi(26)
@Override
public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) {
jobInfoBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
public static final class Factory implements Constraint.Factory<NetworkConstraint> {
private final Application application;

View File

@ -32,24 +32,16 @@ class LogSecretProvider {
}
private static byte[] parseEncryptedSecret(String secret) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(secret);
return KeyStoreHelper.unseal(encryptedSecret);
} else {
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
}
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(secret);
return KeyStoreHelper.unseal(encryptedSecret);
}
private static byte[] createAndStoreSecret(@NonNull Context context) {
byte[] secret = new byte[32];
SECURE_RANDOM.nextBytes(secret);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(secret);
TextSecurePreferences.setLogEncryptedSecret(context, encryptedSecret.serialize());
} else {
TextSecurePreferences.setLogUnencryptedSecret(context, Base64.encodeBytes(secret));
}
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(secret);
TextSecurePreferences.setLogEncryptedSecret(context, encryptedSecret.serialize());
return secret;
}

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.mediasend;
import android.annotation.TargetApi;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
@ -200,14 +199,12 @@ class MediaRepository {
}).toList();
}
@TargetApi(16)
@SuppressWarnings("SuspiciousNameCombination")
private String getWidthColumn(int orientation) {
if (orientation == 0 || orientation == 180) return Images.Media.WIDTH;
else return Images.Media.HEIGHT;
}
@TargetApi(16)
@SuppressWarnings("SuspiciousNameCombination")
private String getHeightColumn(int orientation) {
if (orientation == 0 || orientation == 180) return Images.Media.HEIGHT;

View File

@ -50,8 +50,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui
}
public void setAlarms(@Nullable Uri ringtone, VibrateState vibrate) {
Uri defaultRingtone = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : TextSecurePreferences.getNotificationRingtone(context);
boolean defaultVibrate = NotificationChannels.supported() ? NotificationChannels.getMessageVibrate(context) : TextSecurePreferences.isNotificationVibrateEnabled(context);
Uri defaultRingtone = NotificationChannels.getMessageRingtone(context);
boolean defaultVibrate = NotificationChannels.getMessageVibrate(context);
if (ringtone == null && !TextUtils.isEmpty(defaultRingtone.toString())) setSound(defaultRingtone);
else if (ringtone != null && !ringtone.toString().isEmpty()) setSound(ringtone);

View File

@ -38,10 +38,6 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu
setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, HomeActivity.class), PendingIntent.FLAG_IMMUTABLE));
setCategory(NotificationCompat.CATEGORY_MESSAGE);
setGroupSummary(true);
if (!NotificationChannels.supported()) {
setPriority(TextSecurePreferences.getNotificationPriority(context));
}
}
public void setMessageCount(int messageCount, int threadCount) {

View File

@ -1,15 +1,12 @@
package org.thoughtcrime.securesms.notifications;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
@ -24,7 +21,6 @@ import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.ServiceUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsession.utilities.recipients.Recipient.VibrateState;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
@ -63,10 +59,6 @@ public class NotificationChannels {
* ignored for API < 26.
*/
public static synchronized void create(@NonNull Context context) {
if (!supported()) {
return;
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
int oldVersion = TextSecurePreferences.getNotificationChannelVersion(context);
@ -82,32 +74,6 @@ public class NotificationChannels {
});
}
/**
* Recreates all notification channels for contacts with custom notifications enabled. Should be
* safe to call repeatedly. Needs to be executed on a background thread.
*/
@WorkerThread
public static synchronized void restoreContactNotificationChannels(@NonNull Context context) {
if (!NotificationChannels.supported()) {
return;
}
RecipientDatabase db = DatabaseComponent.get(context).recipientDatabase();
try (RecipientDatabase.RecipientReader reader = db.getRecipientsWithNotificationChannels()) {
Recipient recipient;
while ((recipient = reader.getNext()) != null) {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
if (!channelExists(notificationManager.getNotificationChannel(recipient.getNotificationChannel()))) {
String id = createChannelFor(context, recipient);
db.setNotificationChannel(recipient, id);
}
}
}
ensureCustomChannelConsistency(context);
}
/**
* @return The channel ID for the default messages channel.
*/
@ -115,13 +81,6 @@ public class NotificationChannels {
return getMessagesChannelId(TextSecurePreferences.getNotificationMessagesChannelVersion(context));
}
/**
* @return Whether or not notification channels are supported.
*/
public static boolean supported() {
return Build.VERSION.SDK_INT >= 26;
}
/**
* @return A name suitable to be displayed as the notification channel title.
*/
@ -137,119 +96,18 @@ public class NotificationChannels {
}
}
/**
* Creates a channel for the specified recipient.
* @return The channel ID for the newly-created channel.
*/
public static synchronized String createChannelFor(@NonNull Context context, @NonNull Recipient recipient) {
VibrateState vibrateState = recipient.getMessageVibrate();
boolean vibrationEnabled = vibrateState == VibrateState.DEFAULT ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == VibrateState.ENABLED;
Uri messageRingtone = recipient.getMessageRingtone() != null ? recipient.getMessageRingtone() : getMessageRingtone(context);
String displayName = getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.getAddress());
return createChannelFor(context, recipient.getAddress(), displayName, messageRingtone, vibrationEnabled);
}
/**
* More verbose version of {@link #createChannelFor(Context, Recipient)}.
*/
public static synchronized @Nullable String createChannelFor(@NonNull Context context,
@NonNull Address address,
@NonNull String displayName,
@Nullable Uri messageSound,
boolean vibrationEnabled)
{
if (!supported()) {
return null;
}
String channelId = generateChannelIdFor(address);
NotificationChannel channel = new NotificationChannel(channelId, displayName, NotificationManager.IMPORTANCE_HIGH);
setLedPreference(channel, TextSecurePreferences.getNotificationLedColor(context));
channel.setGroup(CATEGORY_MESSAGES);
channel.enableVibration(vibrationEnabled);
if (messageSound != null) {
channel.setSound(messageSound, new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
.build());
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
notificationManager.createNotificationChannel(channel);
return channelId;
}
/**
* Deletes the channel generated for the provided recipient. Safe to call even if there was never
* a channel made for that recipient.
*/
public static synchronized void deleteChannelFor(@NonNull Context context, @NonNull Recipient recipient) {
if (!supported()) {
return;
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
String channel = recipient.getNotificationChannel();
if (channel != null) {
Log.i(TAG, "Deleting channel");
notificationManager.deleteNotificationChannel(channel);
}
}
/**
* Navigates the user to the system settings for the desired notification channel.
*/
public static void openChannelSettings(@NonNull Context context, @NonNull String channelId) {
if (!supported()) {
return;
}
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
context.startActivity(intent);
}
/**
* Updates the LED color for message notifications and all contact-specific message notification
* channels. Performs database operations and should therefore be invoked on a background thread.
*/
@WorkerThread
public static synchronized void updateMessagesLedColor(@NonNull Context context, @NonNull Integer color) {
if (!supported()) {
return;
}
Log.i(TAG, "Updating LED color.");
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
updateMessageChannel(context, channel -> setLedPreference(channel, color));
updateAllRecipientChannelLedColors(context, notificationManager, color);
ensureCustomChannelConsistency(context);
}
/**
* @return The message ringtone set for the default message channel.
*/
public static synchronized @NonNull Uri getMessageRingtone(@NonNull Context context) {
if (!supported()) {
return Uri.EMPTY;
}
Uri sound = ServiceUtil.getNotificationManager(context).getNotificationChannel(getMessagesChannel(context)).getSound();
return sound == null ? Uri.EMPTY : sound;
}
public static synchronized @Nullable Uri getMessageRingtone(@NonNull Context context, @NonNull Recipient recipient) {
if (!supported() || recipient.getNotificationChannel() == null) {
return null;
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
NotificationChannel channel = notificationManager.getNotificationChannel(recipient.getNotificationChannel());
@ -265,9 +123,6 @@ public class NotificationChannels {
* Update the message ringtone for the default message channel.
*/
public static synchronized void updateMessageRingtone(@NonNull Context context, @Nullable Uri uri) {
if (!supported()) {
return;
}
Log.i(TAG, "Updating default message ringtone with URI: " + String.valueOf(uri));
updateMessageChannel(context, channel -> {
@ -275,121 +130,23 @@ public class NotificationChannels {
});
}
/**
* Updates the message ringtone for a specific recipient. If that recipient has no channel, this
* does nothing.
*
* This has to update the database, and therefore should be run on a background thread.
*/
@WorkerThread
public static synchronized void updateMessageRingtone(@NonNull Context context, @NonNull Recipient recipient, @Nullable Uri uri) {
if (!supported() || recipient.getNotificationChannel() == null) {
return;
}
Log.i(TAG, "Updating recipient message ringtone with URI: " + String.valueOf(uri));
String newChannelId = generateChannelIdFor(recipient.getAddress());
boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context),
recipient.getNotificationChannel(),
generateChannelIdFor(recipient.getAddress()),
channel -> channel.setSound(uri == null ? Settings.System.DEFAULT_NOTIFICATION_URI : uri, getRingtoneAudioAttributes()));
DatabaseComponent.get(context).recipientDatabase().setNotificationChannel(recipient, success ? newChannelId : null);
ensureCustomChannelConsistency(context);
}
/**
* @return The vibrate settings for the default message channel.
*/
public static synchronized boolean getMessageVibrate(@NonNull Context context) {
if (!supported()) {
return false;
}
return ServiceUtil.getNotificationManager(context).getNotificationChannel(getMessagesChannel(context)).shouldVibrate();
}
/**
* @return The vibrate setting for a specific recipient. If that recipient has no channel, this
* will return the setting for the default message channel.
*/
public static synchronized boolean getMessageVibrate(@NonNull Context context, @NonNull Recipient recipient) {
if (!supported()) {
return getMessageVibrate(context);
}
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
NotificationChannel channel = notificationManager.getNotificationChannel(recipient.getNotificationChannel());
if (!channelExists(channel)) {
Log.w(TAG, "Recipient didn't have a channel. Returning message default.");
return getMessageVibrate(context);
}
return channel.shouldVibrate();
}
/**
* Sets the vibrate property for the default message channel.
*/
public static synchronized void updateMessageVibrate(@NonNull Context context, boolean enabled) {
if (!supported()) {
return;
}
Log.i(TAG, "Updating default vibrate with value: " + enabled);
updateMessageChannel(context, channel -> channel.enableVibration(enabled));
}
/**
* Updates the message ringtone for a specific recipient. If that recipient has no channel, this
* does nothing.
*
* This has to update the database and should therefore be run on a background thread.
*/
@WorkerThread
public static synchronized void updateMessageVibrate(@NonNull Context context, @NonNull Recipient recipient, VibrateState vibrateState) {
if (!supported() || recipient.getNotificationChannel() == null) {
return ;
}
Log.i(TAG, "Updating recipient vibrate with value: " + vibrateState);
boolean enabled = vibrateState == VibrateState.DEFAULT ? getMessageVibrate(context) : vibrateState == VibrateState.ENABLED;
String newChannelId = generateChannelIdFor(recipient.getAddress());
boolean success = updateExistingChannel(ServiceUtil.getNotificationManager(context),
recipient.getNotificationChannel(),
newChannelId,
channel -> channel.enableVibration(enabled));
DatabaseComponent.get(context).recipientDatabase().setNotificationChannel(recipient, success ? newChannelId : null);
ensureCustomChannelConsistency(context);
}
/**
* Updates the name of an existing channel to match the recipient's current name. Will have no
* effect if the recipient doesn't have an existing valid channel.
*/
public static synchronized void updateContactChannelName(@NonNull Context context, @NonNull Recipient recipient) {
if (!supported() || recipient.getNotificationChannel() == null) {
return;
}
Log.i(TAG, "Updating contact channel name");
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
if (notificationManager.getNotificationChannel(recipient.getNotificationChannel()) == null) {
Log.w(TAG, "Tried to update the name of a channel, but that channel doesn't exist.");
return;
}
NotificationChannel channel = new NotificationChannel(recipient.getNotificationChannel(),
getChannelDisplayNameFor(context, recipient.getName(), recipient.getProfileName(), recipient.getAddress()),
NotificationManager.IMPORTANCE_HIGH);
channel.setGroup(CATEGORY_MESSAGES);
notificationManager.createNotificationChannel(channel);
}
@TargetApi(26)
@WorkerThread
public static synchronized void ensureCustomChannelConsistency(@NonNull Context context) {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
@ -421,7 +178,6 @@ public class NotificationChannels {
}
}
@TargetApi(26)
private static void onCreate(@NonNull Context context, @NonNull NotificationManager notificationManager) {
NotificationChannelGroup messagesGroup = new NotificationChannelGroup(CATEGORY_MESSAGES, context.getResources().getString(R.string.messages));
notificationManager.createNotificationChannelGroup(messagesGroup);
@ -452,7 +208,6 @@ public class NotificationChannels {
}
}
@TargetApi(26)
private static void onUpgrade(@NonNull NotificationManager notificationManager, int oldVersion, int newVersion) {
Log.i(TAG, "Upgrading channels from " + oldVersion + " to " + newVersion);
@ -467,7 +222,6 @@ public class NotificationChannels {
}
}
@TargetApi(26)
private static void setLedPreference(@NonNull NotificationChannel channel, @NonNull Integer ledColor) {
if ("none".equals(ledColor)) {
channel.enableLights(false);
@ -482,7 +236,6 @@ public class NotificationChannels {
return CONTACT_PREFIX + address.serialize() + "_" + System.currentTimeMillis();
}
@TargetApi(26)
private static @NonNull NotificationChannel copyChannel(@NonNull NotificationChannel original, @NonNull String id) {
NotificationChannel copy = new NotificationChannel(id, original.getName(), original.getImportance());
@ -503,27 +256,7 @@ public class NotificationChannels {
return MESSAGES_PREFIX + version;
}
@WorkerThread
@TargetApi(26)
private static void updateAllRecipientChannelLedColors(@NonNull Context context, @NonNull NotificationManager notificationManager, @NonNull Integer color) {
RecipientDatabase database = DatabaseComponent.get(context).recipientDatabase();
try (RecipientDatabase.RecipientReader recipients = database.getRecipientsWithNotificationChannels()) {
Recipient recipient;
while ((recipient = recipients.getNext()) != null) {
assert recipient.getNotificationChannel() != null;
String newChannelId = generateChannelIdFor(recipient.getAddress());
boolean success = updateExistingChannel(notificationManager, recipient.getNotificationChannel(), newChannelId, channel -> setLedPreference(channel, color));
database.setNotificationChannel(recipient, success ? newChannelId : null);
}
}
ensureCustomChannelConsistency(context);
}
@TargetApi(26)
private static void updateMessageChannel(@NonNull Context context, @NonNull ChannelUpdater updater) {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
int existingVersion = TextSecurePreferences.getNotificationMessagesChannelVersion(context);
@ -537,7 +270,6 @@ public class NotificationChannels {
}
}
@TargetApi(26)
private static boolean updateExistingChannel(@NonNull NotificationManager notificationManager,
@NonNull String channelId,
@NonNull String newChannelId,
@ -557,20 +289,17 @@ public class NotificationChannels {
return true;
}
@TargetApi(21)
private static AudioAttributes getRingtoneAudioAttributes() {
return new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
.build();
}
@TargetApi(26)
private static boolean channelExists(@Nullable NotificationChannel channel) {
return channel != null && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId());
}
private interface ChannelUpdater {
@TargetApi(26)
void update(@NonNull NotificationChannel channel);
}
}

View File

@ -51,8 +51,7 @@ public class NotificationState {
Recipient recipient = notifications.getFirst().getRecipient();
if (recipient != null) {
return NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context, recipient)
: recipient.resolve().getMessageRingtone();
return NotificationChannels.getMessageRingtone(context, recipient);
}
}

View File

@ -13,7 +13,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.text.SpannableStringBuilder;
import androidx.annotation.NonNull;
@ -24,11 +23,10 @@ import androidx.core.app.NotificationCompat.Action;
import androidx.core.app.RemoteInput;
import androidx.core.content.ContextCompat;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.session.libsession.avatars.ContactColors;
import org.session.libsession.avatars.ContactPhoto;
import org.session.libsession.avatars.ResourceContactPhoto;
import org.session.libsession.messaging.contacts.Contact;
import org.session.libsession.utilities.NotificationPrivacyPreference;
import org.session.libsession.utilities.TextSecurePreferences;
@ -38,7 +36,6 @@ import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.database.SessionContactDatabase;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator;
@ -69,10 +66,6 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
setSmallIcon(R.drawable.ic_notification);
setColor(ContextCompat.getColor(context, R.color.accent_green));
setCategory(NotificationCompat.CATEGORY_MESSAGE);
if (!NotificationChannels.supported()) {
setPriority(TextSecurePreferences.getNotificationPriority(context));
}
}
public void setThread(@NonNull Recipient recipient) {
@ -179,13 +172,11 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
Action replyAction = new Action(R.drawable.ic_reply_white_36dp, actionName, quickReplyIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp,
actionName,
wearableReplyIntent)
.addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
.build();
}
replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp,
actionName,
wearableReplyIntent)
.addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
.build();
Action wearableReplyAction = new Action.Builder(R.drawable.ic_reply,
actionName,

View File

@ -210,15 +210,8 @@ public class Permissions {
.toArray(new String[0]);
}
public static boolean hasAny(@NonNull Context context, String... permissions) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
Stream.of(permissions).anyMatch(permission -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
}
public static boolean hasAll(@NonNull Context context, String... permissions) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
Stream.of(permissions).allMatch(permission -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
return Stream.of(permissions).allMatch(permission -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
}

View File

@ -56,14 +56,14 @@ class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() {
true
}
if (NotificationChannels.supported()) {
prefs.setNotificationRingtone(
NotificationChannels.getMessageRingtone(requireContext()).toString()
)
prefs.setNotificationVibrateEnabled(
NotificationChannels.getMessageVibrate(requireContext())
)
}
prefs.setNotificationRingtone(
NotificationChannels.getMessageRingtone(requireContext()).toString()
)
prefs.setNotificationVibrateEnabled(
NotificationChannels.getMessageVibrate(requireContext())
)
findPreference<Preference>(TextSecurePreferences.RINGTONE_PREF)!!.onPreferenceChangeListener = RingtoneSummaryListener()
findPreference<Preference>(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)!!.onPreferenceChangeListener = NotificationPrivacyListener()
findPreference<Preference>(TextSecurePreferences.VIBRATE_PREF)!!.onPreferenceChangeListener =
@ -100,18 +100,18 @@ class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() {
true
}
initializeListSummary(findPreference<Preference>(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF) as ListPreference?)
if (NotificationChannels.supported()) {
findPreference<Preference>(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(
Settings.EXTRA_CHANNEL_ID, NotificationChannels.getMessagesChannel(requireContext())
)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
startActivity(intent)
true
}
}
findPreference<Preference>(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(
Settings.EXTRA_CHANNEL_ID, NotificationChannels.getMessagesChannel(requireContext())
)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
startActivity(intent)
true
}
initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF))
initializeMessageVibrateSummary(findPreference<Preference>(TextSecurePreferences.VIBRATE_PREF) as SwitchPreferenceCompat?)
}

View File

@ -78,14 +78,9 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() {
title(R.string.sessionNotifications)
text(R.string.callsNotificationsRequired)
button(R.string.sessionNotifications) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
} else {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID))
}
.apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
.putExtra(Settings.EXTRA_APP_PACKAGE, BuildConfig.APPLICATION_ID)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.takeIf { IntentUtils.isResolvable(requireContext(), it) }.let {
startActivity(it)
}

View File

@ -7,7 +7,6 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
@ -215,7 +214,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.settings_general, menu)
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (BuildConfig.DEBUG) {
menu.findItem(R.id.action_qr_code)?.contentDescription = resources.getString(R.string.AccessibilityId_qrView)
}
return true

View File

@ -1,74 +1,70 @@
package org.thoughtcrime.securesms.preferences.widgets;
import android.content.Context;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceViewHolder;
import network.loki.messenger.R;
public class SignalListPreference extends ListPreference {
private TextView rightSummaryTV;
private CharSequence summary;
private OnPreferenceClickListener clickListener;
private CharSequence summarySpecifiedInLayoutXML;
private TextView rightSummary;
private CharSequence summary;
private OnPreferenceClickListener clickListener;
public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
public SignalListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public SignalListPreference(Context context) {
super(context);
initialize();
}
private void initialize() {
setWidgetLayoutResource(R.layout.preference_right_summary_widget);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
this.rightSummary = (TextView)view.findViewById(R.id.right_summary);
setSummary(this.summary);
}
@Override
public void setSummary(CharSequence summary) {
super.setSummary(null);
this.summary = summary;
if (this.rightSummary != null) {
this.rightSummary.setText(summary);
}
}
public SignalListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
public SignalListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public SignalListPreference(Context context) {
super(context);
initialize();
}
private void initialize() {
summarySpecifiedInLayoutXML = this.getSummary();
if (summarySpecifiedInLayoutXML == null) { summarySpecifiedInLayoutXML = ""; }
setWidgetLayoutResource(R.layout.preference_right_summary_widget);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
this.rightSummaryTV = (TextView)view.findViewById(R.id.right_summary);
setSummary(this.summary);
}
@Override
public void setSummary(CharSequence incomingSummary) {
// Set the left "subtitle" summary such as "The information shown in notifications." etc.
super.setSummary(summarySpecifiedInLayoutXML);
// Then set the right summary to be the incoming drop-down selected option
this.summary = incomingSummary;
if (this.rightSummaryTV != null) {
this.rightSummaryTV.setText(incomingSummary);
}
}
@Override
public void setOnPreferenceClickListener (OnPreferenceClickListener
onPreferenceClickListener){
this.clickListener = onPreferenceClickListener;
}
@Override
protected void onClick () {
if (clickListener == null || !clickListener.onPreferenceClick(this)) {
super.onClick();
}
@Override
public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
this.clickListener = onPreferenceClickListener;
}
@Override
protected void onClick() {
if (clickListener == null || !clickListener.onPreferenceClick(this)) {
super.onClick();
}
}
}

View File

@ -23,7 +23,6 @@
package org.thoughtcrime.securesms.scribbles.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@ -34,7 +33,6 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@ -98,12 +96,6 @@ public class VerticalSlideColorPicker extends View {
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public VerticalSlideColorPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setWillNotDraw(false);

View File

@ -14,10 +14,7 @@ public class FileProviderUtil {
private static final String AUTHORITY = "network.loki.securesms.fileprovider";
public static Uri getUriFor(@NonNull Context context, @NonNull File file) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
return FileProvider.getUriForFile(context, AUTHORITY, file);
else
return Uri.fromFile(file);
return FileProvider.getUriForFile(context, AUTHORITY, file);
}
public static boolean delete(@NonNull Context context, @NonNull Uri uri) {

View File

@ -1,20 +1,9 @@
package org.thoughtcrime.securesms.util
import android.content.res.Resources
import android.os.Build
import androidx.annotation.ColorRes
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.max
import kotlin.math.roundToInt
fun Resources.getColorWithID(@ColorRes id: Int, theme: Resources.Theme?): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getColor(id, theme)
} else {
@Suppress("DEPRECATION") getColor(id)
}
}
fun toPx(dp: Int, resources: Resources): Int {
return toPx(dp.toFloat(), resources).roundToInt()
}

View File

@ -11,7 +11,6 @@ import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import network.loki.messenger.R
import kotlin.math.roundToInt
@ -22,18 +21,6 @@ interface GlowView {
object GlowViewUtilities {
fun animateColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
val startColor = context.resources.getColorWithID(startColorID, context.theme)
val endColor = context.resources.getColorWithID(endColorID, context.theme)
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
animation.duration = 250
animation.addUpdateListener { animator ->
val color = animator.animatedValue as Int
view.mainColor = color
}
animation.start()
}
fun animateColorChange(view: GlowView, @ColorInt startColor: Int, @ColorInt endColor: Int) {
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
animation.duration = 250
@ -44,18 +31,6 @@ object GlowViewUtilities {
animation.start()
}
fun animateShadowColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
val startColor = context.resources.getColorWithID(startColorID, context.theme)
val endColor = context.resources.getColorWithID(endColorID, context.theme)
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
animation.duration = 250
animation.addUpdateListener { animator ->
val color = animator.animatedValue as Int
view.sessionShadowColor = color
}
animation.start()
}
fun animateShadowColorChange(
view: GlowView,
@ColorInt startColor: Int,

View File

@ -1,64 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.Context;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import android.text.TextPaint;
import android.text.style.URLSpan;
import network.loki.messenger.R;
public class LongClickCopySpan extends URLSpan {
private static final String PREFIX_MAILTO = "mailto:";
private static final String PREFIX_TEL = "tel:";
private boolean isHighlighted;
@ColorInt
private int highlightColor;
public LongClickCopySpan(String url) {
super(url);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.bgColor = highlightColor;
ds.setUnderlineText(!isHighlighted);
}
void setHighlighted(boolean highlighted, @ColorInt int highlightColor) {
this.isHighlighted = highlighted;
this.highlightColor = highlightColor;
}
private void copyUrl(Context context, String url) {
int sdk = android.os.Build.VERSION.SDK_INT;
if (sdk < android.os.Build.VERSION_CODES.HONEYCOMB) {
@SuppressWarnings("deprecation") android.text.ClipboardManager clipboard =
(android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(url);
} else {
copyUriSdk11(context, url);
}
}
@TargetApi(android.os.Build.VERSION_CODES.HONEYCOMB)
private void copyUriSdk11(Context context, String url) {
android.content.ClipboardManager clipboard =
(android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(context.getString(R.string.app_name), url);
clipboard.setPrimaryClip(clip);
}
private String prepareUrl(String url) {
if (url.startsWith(PREFIX_MAILTO)) {
return url.substring(PREFIX_MAILTO.length());
} else if (url.startsWith(PREFIX_TEL)) {
return url.substring(PREFIX_TEL.length());
}
return url;
}
}

View File

@ -131,7 +131,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
int layoutPos)
{
int headerHeight = getHeaderHeightForLayout(header);
int top = getChildY(parent, child) - headerHeight;
int top = (int)child.getY() - headerHeight;
if (sticky && layoutPos == 0) {
final int count = parent.getChildCount();
final long currentId = adapter.getHeaderId(adapterPos);
@ -142,7 +142,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
long nextId = adapter.getHeaderId(adapterPosHere);
if (nextId != currentId) {
final View next = parent.getChildAt(translatedChildPosition(parent, i));
final int offset = getChildY(parent, next) - (headerHeight + getHeader(parent, adapter, adapterPosHere).itemView.getHeight());
final int offset = (int)next.getY() - (headerHeight + getHeader(parent, adapter, adapterPosHere).itemView.getHeight());
if (offset < 0) {
return offset;
} else {
@ -162,16 +162,6 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
return isReverseLayout(parent) ? parent.getChildCount() - 1 - position : position;
}
private int getChildY(RecyclerView parent, View child) {
if (VERSION.SDK_INT < 11) {
Rect rect = new Rect();
parent.getChildVisibleRect(child, rect, null);
return rect.top;
} else {
return (int)ViewCompat.getY(child);
}
}
protected int getHeaderHeightForLayout(View header) {
return renderInline ? 0 : header.getHeight();
}

View File

@ -1,22 +1,20 @@
package org.thoughtcrime.securesms.video;
import android.annotation.TargetApi;
import android.media.MediaDataSource;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.session.libsession.utilities.Util;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.session.libsession.utilities.Util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@TargetApi(Build.VERSION_CODES.M)
public class EncryptedMediaDataSource extends MediaDataSource {
private final AttachmentSecret attachmentSecret;

View File

@ -8,10 +8,8 @@ import android.media.AudioDeviceInfo;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import org.session.libsession.utilities.ServiceUtil;
import org.session.libsignal.utilities.Log;
@ -116,14 +114,9 @@ public abstract class AudioManagerCompat {
abstract public void abandonCallAudioFocus();
public static AudioManagerCompat create(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 26) {
return new Api26AudioManagerCompat(context);
} else {
return new Api21AudioManagerCompat(context);
}
return new Api26AudioManagerCompat(context);
}
@RequiresApi(26)
private static class Api26AudioManagerCompat extends AudioManagerCompat {
private static AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
@ -180,44 +173,4 @@ public abstract class AudioManagerCompat {
audioFocusRequest = null;
}
}
@RequiresApi(21)
private static class Api21AudioManagerCompat extends AudioManagerCompat {
private static AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
.build();
private Api21AudioManagerCompat(@NonNull Context context) {
super(context);
}
@Override
public SoundPool createSoundPool() {
return new SoundPool.Builder()
.setAudioAttributes(AUDIO_ATTRIBUTES)
.setMaxStreams(1)
.build();
}
@Override
public void requestCallAudioFocus() {
int result = audioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_VOICE_CALL, AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.w(TAG, "Audio focus not granted. Result code: " + result);
}
}
@Override
public void abandonCallAudioFocus() {
int result = audioManager.abandonAudioFocus(onAudioFocusChangeListener);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.w(TAG, "Audio focus abandon failed. Result code: " + result);
}
}
}
}

View File

@ -69,7 +69,7 @@ allprojects {
}
project.ext {
androidMinimumSdkVersion = 23
androidMinimumSdkVersion = 26
androidTargetSdkVersion = 34
androidCompileSdkVersion = 34
}

View File

@ -73,8 +73,8 @@ object SnodeAPI {
private const val maxRetryCount = 6
private const val minimumSnodePoolCount = 12
private const val minimumSwarmSnodeCount = 3
// Use port 4433 if the API level can handle the network security configuration and enforce pinned certificates
private val seedNodePort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4443
// Use port 4433 to enforce pinned certificates
private val seedNodePort = 4443
private const val useTestnet = false

View File

@ -5,17 +5,12 @@ import android.media.MediaCodec
import android.media.MediaDataSource
import android.media.MediaExtractor
import android.media.MediaFormat
import android.os.Build
import androidx.annotation.RequiresApi
import java.io.FileDescriptor
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.ShortBuffer
import kotlin.jvm.Throws
import kotlin.math.ceil
import kotlin.math.roundToInt
import kotlin.math.sqrt
@ -44,7 +39,6 @@ class DecodedAudio {
}
@JvmStatic
@RequiresApi(api = Build.VERSION_CODES.M)
@Throws(IOException::class)
fun create(dataSource: MediaDataSource): DecodedAudio {
val mediaExtractor = MediaExtractor().apply { setDataSource(dataSource) }
@ -69,15 +63,7 @@ class DecodedAudio {
val samples: ShortBuffer
get() {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
) {
// Hack for Nougat where asReadOnlyBuffer fails to respect byte ordering.
// See https://code.google.com/p/android/issues/detail?id=223824
decodedSamples
} else {
decodedSamples.asReadOnlyBuffer()
}
return decodedSamples.asReadOnlyBuffer()
}
/**
@ -128,15 +114,13 @@ class DecodedAudio {
codec.start()
// Check if the track is in PCM 16 bit encoding.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
val pcmEncoding = codec.outputFormat.getInteger(MediaFormat.KEY_PCM_ENCODING)
if (pcmEncoding != AudioFormat.ENCODING_PCM_16BIT) {
throw IOException("Unsupported PCM encoding code: $pcmEncoding")
}
} catch (e: NullPointerException) {
// If KEY_PCM_ENCODING is not specified, means it's ENCODING_PCM_16BIT.
try {
val pcmEncoding = codec.outputFormat.getInteger(MediaFormat.KEY_PCM_ENCODING)
if (pcmEncoding != AudioFormat.ENCODING_PCM_16BIT) {
throw IOException("Unsupported PCM encoding code: $pcmEncoding")
}
} catch (e: NullPointerException) {
// If KEY_PCM_ENCODING is not specified, means it's ENCODING_PCM_16BIT.
}
var decodedSamplesSize: Int = 0 // size of the output buffer containing decoded samples.

View File

@ -6,13 +6,8 @@ import android.app.NotificationManager;
import android.content.Context;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.PowerManager;
import android.os.Vibrator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
@ -54,8 +49,4 @@ public class ServiceUtil {
return (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
public static @Nullable SubscriptionManager getSubscriptionManager(@NonNull Context context) {
return (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
}
}

View File

@ -19,21 +19,16 @@ package org.session.libsession.utilities;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.LinearLayout.LayoutParams;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import org.session.libsignal.utilities.ListenableFuture;
@ -42,58 +37,7 @@ import org.session.libsignal.utilities.SettableFuture;
public class ViewUtil {
@SuppressWarnings("deprecation")
public static void setBackground(final @NonNull View v, final @Nullable Drawable drawable) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
v.setBackground(drawable);
} else {
v.setBackgroundDrawable(drawable);
}
}
public static void setY(final @NonNull View v, final int y) {
if (VERSION.SDK_INT >= 11) {
ViewCompat.setY(v, y);
} else {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
params.topMargin = y;
v.setLayoutParams(params);
}
}
public static float getY(final @NonNull View v) {
if (VERSION.SDK_INT >= 11) {
return ViewCompat.getY(v);
} else {
return ((ViewGroup.MarginLayoutParams)v.getLayoutParams()).topMargin;
}
}
public static void setX(final @NonNull View v, final int x) {
if (VERSION.SDK_INT >= 11) {
ViewCompat.setX(v, x);
} else {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
params.leftMargin = x;
v.setLayoutParams(params);
}
}
public static float getX(final @NonNull View v) {
if (VERSION.SDK_INT >= 11) {
return ViewCompat.getX(v);
} else {
return ((LayoutParams)v.getLayoutParams()).leftMargin;
}
}
public static void swapChildInPlace(ViewGroup parent, View toRemove, View toAdd, int defaultIndex) {
int childIndex = parent.indexOfChild(toRemove);
if (childIndex > -1) parent.removeView(toRemove);
parent.addView(toAdd, childIndex > -1 ? childIndex : defaultIndex);
}
@SuppressWarnings("unchecked")
public static <T extends View> T inflateStub(@NonNull View parent, @IdRes int stubId) {
return (T)((ViewStub)parent.findViewById(stubId)).inflate();
v.setBackground(drawable);
}
@SuppressWarnings("unchecked")
@ -106,10 +50,6 @@ public class ViewUtil {
return (T) parent.findViewById(resId);
}
public static <T extends View> Stub<T> findStubById(@NonNull Activity parent, @IdRes int resId) {
return new Stub<T>((ViewStub)parent.findViewById(resId));
}
private static Animation getAlphaAnimation(float from, float to, int duration) {
final Animation anim = new AlphaAnimation(from, to);
anim.setInterpolator(new FastOutSlowInInterpolator());
@ -177,58 +117,4 @@ public class ViewUtil {
return (int)((dp * context.getResources().getDisplayMetrics().density) + 0.5);
}
public static void updateLayoutParams(@NonNull View view, int width, int height) {
view.getLayoutParams().width = width;
view.getLayoutParams().height = height;
view.requestLayout();
}
public static int getLeftMargin(@NonNull View view) {
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin;
}
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin;
}
public static int getRightMargin(@NonNull View view) {
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin;
}
return ((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin;
}
public static void setLeftMargin(@NonNull View view, int margin) {
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).leftMargin = margin;
} else {
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).rightMargin = margin;
}
view.forceLayout();
view.requestLayout();
}
public static void setTopMargin(@NonNull View view, int margin) {
((ViewGroup.MarginLayoutParams) view.getLayoutParams()).topMargin = margin;
view.requestLayout();
}
public static void setPaddingTop(@NonNull View view, int padding) {
view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(), view.getPaddingBottom());
}
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

@ -718,7 +718,7 @@ public class Recipient implements RecipientModifiedListener {
}
public synchronized @Nullable String getNotificationChannel() {
return !(Build.VERSION.SDK_INT >= 26) ? null : notificationChannel;
return notificationChannel;
}
public void setNotificationChannel(@Nullable String value) {