mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 00:37:47 +00:00
Merge branch 'dev' into strings-squashed
This commit is contained in:
commit
558684a56d
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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?)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ allprojects {
|
||||
}
|
||||
|
||||
project.ext {
|
||||
androidMinimumSdkVersion = 23
|
||||
androidMinimumSdkVersion = 26
|
||||
androidTargetSdkVersion = 34
|
||||
androidCompileSdkVersion = 34
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user