mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 19:00:26 +01:00
committed by
Cody Henthorne
parent
98d9b12438
commit
ebccc6db30
@@ -6,7 +6,6 @@
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
@@ -35,8 +34,6 @@ class ThreadTableTest_active {
|
||||
fun setUp() {
|
||||
mockkStatic(RemoteConfig::class)
|
||||
|
||||
every { RemoteConfig.showChatFolders } returns true
|
||||
|
||||
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -30,8 +29,6 @@ class ThreadTableTest_pinned {
|
||||
fun setUp() {
|
||||
mockkStatic(RemoteConfig::class)
|
||||
|
||||
every { RemoteConfig.showChatFolders } returns true
|
||||
|
||||
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package org.thoughtcrime.securesms.color;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MaterialColors {
|
||||
|
||||
public static final MaterialColorList CONVERSATION_PALETTE = new MaterialColorList(new ArrayList<>(Arrays.asList(
|
||||
MaterialColor.PLUM,
|
||||
MaterialColor.CRIMSON,
|
||||
MaterialColor.VERMILLION,
|
||||
MaterialColor.VIOLET,
|
||||
MaterialColor.INDIGO,
|
||||
MaterialColor.TAUPE,
|
||||
MaterialColor.ULTRAMARINE,
|
||||
MaterialColor.BLUE,
|
||||
MaterialColor.TEAL,
|
||||
MaterialColor.FOREST,
|
||||
MaterialColor.WINTERGREEN,
|
||||
MaterialColor.BURLAP,
|
||||
MaterialColor.STEEL
|
||||
)));
|
||||
|
||||
public static class MaterialColorList {
|
||||
|
||||
private final List<MaterialColor> colors;
|
||||
|
||||
private MaterialColorList(List<MaterialColor> colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
public MaterialColor get(int index) {
|
||||
return colors.get(index);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return colors.size();
|
||||
}
|
||||
|
||||
public @Nullable MaterialColor getByColor(Context context, int colorValue) {
|
||||
for (MaterialColor color : colors) {
|
||||
if (color.represents(context, colorValue)) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public @ColorInt int[] asConversationColorArray(@NonNull Context context) {
|
||||
int[] results = new int[colors.size()];
|
||||
int index = 0;
|
||||
|
||||
for (MaterialColor color : colors) {
|
||||
results[index++] = color.toConversationColor(context);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.compose
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.PixelCopy
|
||||
import android.view.View
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
import androidx.compose.ui.layout.boundsInRoot
|
||||
import androidx.compose.ui.layout.boundsInWindow
|
||||
|
||||
/**
|
||||
* Helper class for screenshotting compose views.
|
||||
*
|
||||
* You need to call bind from the compose, passing in the
|
||||
* LocalView.current view with bounds fetched from when the
|
||||
* composable is globally positioned.
|
||||
*
|
||||
* See QrCodeBadge.kt for an example
|
||||
*/
|
||||
class ScreenshotController {
|
||||
private var screenshotCallback: (() -> Bitmap?)? = null
|
||||
|
||||
fun bind(view: View, bounds: Rect?) {
|
||||
if (bounds == null) {
|
||||
screenshotCallback = null
|
||||
return
|
||||
}
|
||||
screenshotCallback = {
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
bounds.width.toInt(),
|
||||
bounds.height.toInt(),
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
PixelCopy.request(
|
||||
(view.context as Activity).window,
|
||||
android.graphics.Rect(bounds.left.toInt(), bounds.top.toInt(), bounds.right.toInt(), bounds.bottom.toInt()),
|
||||
bitmap,
|
||||
{},
|
||||
Handler(Looper.getMainLooper())
|
||||
)
|
||||
} else {
|
||||
val canvas = Canvas(bitmap)
|
||||
.apply {
|
||||
translate(-bounds.left, -bounds.top)
|
||||
}
|
||||
view.draw(canvas)
|
||||
}
|
||||
|
||||
bitmap
|
||||
}
|
||||
}
|
||||
|
||||
fun screenshot(): Bitmap? {
|
||||
return screenshotCallback?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun LayoutCoordinates.getScreenshotBounds(): Rect {
|
||||
return if (Build.VERSION.SDK_INT >= 26) {
|
||||
this.boundsInWindow()
|
||||
} else {
|
||||
this.boundsInRoot()
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package org.thoughtcrime.securesms.contacts;
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.database.AbstractCursor;
|
||||
import android.database.CursorWindow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A convenience class that presents a two-dimensional ArrayList
|
||||
* as a Cursor.
|
||||
*/
|
||||
public class ArrayListCursor extends AbstractCursor {
|
||||
private String[] mColumnNames;
|
||||
private ArrayList<Object>[] mRows;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows) {
|
||||
int colCount = columnNames.length;
|
||||
boolean foundID = false;
|
||||
// Add an _id column if not in columnNames
|
||||
for (int i = 0; i < colCount; ++i) {
|
||||
if (columnNames[i].compareToIgnoreCase("_id") == 0) {
|
||||
mColumnNames = columnNames;
|
||||
foundID = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundID) {
|
||||
mColumnNames = new String[colCount + 1];
|
||||
System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length);
|
||||
mColumnNames[colCount] = "_id";
|
||||
}
|
||||
|
||||
int rowCount = rows.size();
|
||||
mRows = new ArrayList[rowCount];
|
||||
|
||||
for (int i = 0; i < rowCount; ++i) {
|
||||
mRows[i] = rows.get(i);
|
||||
if (!foundID) {
|
||||
mRows[i].add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillWindow(int position, CursorWindow window) {
|
||||
if (position < 0 || position > getCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.acquireReference();
|
||||
try {
|
||||
int oldpos = mPos;
|
||||
mPos = position - 1;
|
||||
window.clear();
|
||||
window.setStartPosition(position);
|
||||
int columnNum = getColumnCount();
|
||||
window.setNumColumns(columnNum);
|
||||
while (moveToNext() && window.allocRow()) {
|
||||
for (int i = 0; i < columnNum; i++) {
|
||||
final Object data = mRows[mPos].get(i);
|
||||
if (data != null) {
|
||||
if (data instanceof byte[]) {
|
||||
byte[] field = (byte[]) data;
|
||||
if (!window.putBlob(field, mPos, i)) {
|
||||
window.freeLastRow();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
String field = data.toString();
|
||||
if (!window.putString(field, mPos, i)) {
|
||||
window.freeLastRow();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!window.putNull(mPos, i)) {
|
||||
window.freeLastRow();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPos = oldpos;
|
||||
} catch (IllegalStateException e){
|
||||
// simply ignore it
|
||||
} finally {
|
||||
window.releaseReference();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mRows.length;
|
||||
}
|
||||
|
||||
public boolean deleteRow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getColumnNames() {
|
||||
return mColumnNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBlob(int columnIndex) {
|
||||
return (byte[]) mRows[mPos].get(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int columnIndex) {
|
||||
Object cell = mRows[mPos].get(columnIndex);
|
||||
return (cell == null) ? null : cell.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int columnIndex) {
|
||||
Number num = (Number) mRows[mPos].get(columnIndex);
|
||||
return num.shortValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int columnIndex) {
|
||||
Number num = (Number) mRows[mPos].get(columnIndex);
|
||||
return num.intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int columnIndex) {
|
||||
Number num = (Number) mRows[mPos].get(columnIndex);
|
||||
return num.longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int columnIndex) {
|
||||
Number num = (Number) mRows[mPos].get(columnIndex);
|
||||
return num.floatValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int columnIndex) {
|
||||
Number num = (Number) mRows[mPos].get(columnIndex);
|
||||
return num.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull(int columnIndex) {
|
||||
return mRows[mPos].get(columnIndex) == null;
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.contacts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class was originally a layer of indirection between
|
||||
* ContactAccessorNewApi and ContactAccessorOldApi, which corresponded
|
||||
* to the API changes between 1.x and 2.x.
|
||||
*
|
||||
* Now that we no longer support 1.x, this class mostly serves as a place
|
||||
* to encapsulate Contact-related logic. It's still a singleton, mostly
|
||||
* just because that's how it's currently called from everywhere.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class ContactAccessor {
|
||||
|
||||
private static final ContactAccessor instance = new ContactAccessor();
|
||||
|
||||
public static ContactAccessor getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ContactData getContactData(Context context, Uri uri) {
|
||||
String displayName = getNameFromContact(context, uri);
|
||||
long id = Long.parseLong(uri.getLastPathSegment());
|
||||
|
||||
ContactData contactData = new ContactData(id, displayName);
|
||||
|
||||
try (Cursor numberCursor = context.getContentResolver().query(Phone.CONTENT_URI,
|
||||
null,
|
||||
Phone.CONTACT_ID + " = ?",
|
||||
new String[] { contactData.id + "" },
|
||||
null))
|
||||
{
|
||||
while (numberCursor != null && numberCursor.moveToNext()) {
|
||||
int type = numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE));
|
||||
String label = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL));
|
||||
String number = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER));
|
||||
String typeLabel = Phone.getTypeLabel(context.getResources(), type, label).toString();
|
||||
|
||||
contactData.numbers.add(new NumberData(typeLabel, number));
|
||||
}
|
||||
}
|
||||
|
||||
return contactData;
|
||||
}
|
||||
|
||||
private String getNameFromContact(Context context, Uri uri) {
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, new String[] { Contacts.DISPLAY_NAME }, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(0);
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static class NumberData implements Parcelable {
|
||||
|
||||
public static final Parcelable.Creator<NumberData> CREATOR = new Parcelable.Creator<NumberData>() {
|
||||
public NumberData createFromParcel(Parcel in) {
|
||||
return new NumberData(in);
|
||||
}
|
||||
|
||||
public NumberData[] newArray(int size) {
|
||||
return new NumberData[size];
|
||||
}
|
||||
};
|
||||
|
||||
public final String number;
|
||||
public final String type;
|
||||
|
||||
public NumberData(String type, String number) {
|
||||
this.type = type;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public NumberData(Parcel in) {
|
||||
number = in.readString();
|
||||
type = in.readString();
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(number);
|
||||
dest.writeString(type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContactData implements Parcelable {
|
||||
|
||||
public static final Parcelable.Creator<ContactData> CREATOR = new Parcelable.Creator<ContactData>() {
|
||||
public ContactData createFromParcel(Parcel in) {
|
||||
return new ContactData(in);
|
||||
}
|
||||
|
||||
public ContactData[] newArray(int size) {
|
||||
return new ContactData[size];
|
||||
}
|
||||
};
|
||||
|
||||
public final long id;
|
||||
public final String name;
|
||||
public final List<NumberData> numbers;
|
||||
|
||||
public ContactData(long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.numbers = new LinkedList<NumberData>();
|
||||
}
|
||||
|
||||
public ContactData(Parcel in) {
|
||||
id = in.readLong();
|
||||
name = in.readString();
|
||||
numbers = new LinkedList<NumberData>();
|
||||
in.readTypedList(numbers, NumberData.CREATOR);
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeLong(id);
|
||||
dest.writeString(name);
|
||||
dest.writeTypedList(numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.contacts;
|
||||
|
||||
/**
|
||||
* Name and number tuple.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class NameAndNumber {
|
||||
public String name;
|
||||
public String number;
|
||||
|
||||
public NameAndNumber(String name, String number) {
|
||||
this.name = name;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public NameAndNumber() {}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.FileUtils
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import org.thoughtcrime.securesms.util.SignalE164Util
|
||||
import org.thoughtcrime.securesms.util.Triple
|
||||
import org.whispersystems.signalservice.api.push.DistributionId
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.thoughtcrime.securesms.media
|
||||
|
||||
import android.media.MediaExtractor
|
||||
import org.thoughtcrime.securesms.video.interfaces.MediaInput
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* A media input source that the system reads directly from the file.
|
||||
*/
|
||||
class FileMediaInput(private val file: File) : MediaInput {
|
||||
@Throws(IOException::class)
|
||||
override fun createExtractor(): MediaExtractor {
|
||||
val extractor = MediaExtractor()
|
||||
extractor.setDataSource(file.absolutePath)
|
||||
return extractor
|
||||
}
|
||||
|
||||
override fun hasSameInput(other: MediaInput): Boolean {
|
||||
return other is FileMediaInput && other.file == this.file
|
||||
}
|
||||
|
||||
override fun close() {}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.messagerequests
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.text.Html
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
@@ -12,7 +13,6 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestBarColorTheme.Co
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.Debouncer
|
||||
import org.thoughtcrime.securesms.util.HtmlUtil
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
@@ -64,7 +64,7 @@ class MessageRequestsBottomView @JvmOverloads constructor(context: Context, attr
|
||||
question.text = HtmlCompat.fromHtml(
|
||||
context.getString(
|
||||
message,
|
||||
HtmlUtil.bold(recipient.getShortDisplayName(context))
|
||||
bold(recipient.getShortDisplayName(context))
|
||||
),
|
||||
0
|
||||
)
|
||||
@@ -95,7 +95,7 @@ class MessageRequestsBottomView @JvmOverloads constructor(context: Context, attr
|
||||
question.text = HtmlCompat.fromHtml(
|
||||
context.getString(
|
||||
R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_they_wont_know_youve_seen_their_messages_until_you_accept,
|
||||
HtmlUtil.bold(recipient.getShortDisplayName(context))
|
||||
bold(recipient.getShortDisplayName(context))
|
||||
),
|
||||
0
|
||||
)
|
||||
@@ -106,7 +106,7 @@ class MessageRequestsBottomView @JvmOverloads constructor(context: Context, attr
|
||||
question.text = HtmlCompat.fromHtml(
|
||||
context.getString(
|
||||
R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_you_removed_them_before,
|
||||
HtmlUtil.bold(recipient.getShortDisplayName(context))
|
||||
bold(recipient.getShortDisplayName(context))
|
||||
),
|
||||
0
|
||||
)
|
||||
@@ -165,4 +165,8 @@ class MessageRequestsBottomView @JvmOverloads constructor(context: Context, attr
|
||||
fun setReportOnClickListener(reportOnClickListener: OnClickListener?) {
|
||||
report.setOnClickListener(reportOnClickListener)
|
||||
}
|
||||
|
||||
fun bold(target: String): String {
|
||||
return "<b>" + Html.escapeHtml(target) + "</b>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.phonenumbers;
|
||||
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -38,15 +37,11 @@ public class NumberUtil {
|
||||
|
||||
/**
|
||||
* Whether or not a number entered by the user is a valid phone or email address. Differs from
|
||||
* {@link #isValidSmsOrEmail(String)} in that it only returns true for numbers that a user would
|
||||
* PhoneNumberUtils.isWellFormedSmsAddress(String) in that it only returns true for numbers that a user would
|
||||
* enter themselves, as opposed to the crazy network prefixes that could theoretically be in an
|
||||
* SMS address.
|
||||
*/
|
||||
public static boolean isVisuallyValidNumberOrEmail(String number) {
|
||||
return isVisuallyValidNumber(number) || isValidEmail(number);
|
||||
}
|
||||
|
||||
public static boolean isValidSmsOrEmail(String number) {
|
||||
return PhoneNumberUtils.isWellFormedSmsAddress(number) || isValidEmail(number);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.recipients;
|
||||
|
||||
public class RecipientFormattingException extends Exception {
|
||||
public RecipientFormattingException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public RecipientFormattingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RecipientFormattingException(String message, Throwable nested) {
|
||||
super(message, nested);
|
||||
}
|
||||
|
||||
public RecipientFormattingException(Throwable nested) {
|
||||
super(nested);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.recipients;
|
||||
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
public class RecipientsFormatter {
|
||||
|
||||
private static String parseBracketedNumber(String recipient) throws RecipientFormattingException {
|
||||
int begin = recipient.indexOf('<');
|
||||
int end = recipient.indexOf('>');
|
||||
String value = recipient.substring(begin + 1, end);
|
||||
|
||||
if (PhoneNumberUtils.isWellFormedSmsAddress(value))
|
||||
return value;
|
||||
else
|
||||
throw new RecipientFormattingException("Bracketed value: " + value + " is not valid.");
|
||||
}
|
||||
|
||||
private static String parseRecipient(String recipient) throws RecipientFormattingException {
|
||||
recipient = recipient.trim();
|
||||
|
||||
if ((recipient.indexOf('<') != -1) && (recipient.indexOf('>') != -1))
|
||||
return parseBracketedNumber(recipient);
|
||||
|
||||
if (PhoneNumberUtils.isWellFormedSmsAddress(recipient))
|
||||
return recipient;
|
||||
|
||||
throw new RecipientFormattingException("Recipient: " + recipient + " is badly formatted.");
|
||||
}
|
||||
|
||||
public static List<String> getRecipients(String rawText) throws RecipientFormattingException {
|
||||
ArrayList<String> results = new ArrayList<String>();
|
||||
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
results.add(parseRecipient(tokenizer.nextToken()));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static String formatNameAndNumber(String name, String number) {
|
||||
// Format like this: Mike Cleron <(650) 555-1234>
|
||||
// Erick Tseng <(650) 555-1212>
|
||||
// Tutankhamun <tutank1341@gmail.com>
|
||||
// (408) 555-1289
|
||||
String formattedNumber = PhoneNumberUtils.formatNumber(number);
|
||||
if (!TextUtils.isEmpty(name) && !name.equals(number)) {
|
||||
return name + " <" + formattedNumber + ">";
|
||||
} else {
|
||||
return formattedNumber;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.ServiceState;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
public class TelephonyServiceState {
|
||||
|
||||
public boolean isConnected(Context context) {
|
||||
ListenThread listenThread = new ListenThread(context);
|
||||
listenThread.start();
|
||||
|
||||
return listenThread.get();
|
||||
}
|
||||
|
||||
private static class ListenThread extends Thread {
|
||||
|
||||
private final Context context;
|
||||
|
||||
private boolean complete;
|
||||
private boolean result;
|
||||
|
||||
public ListenThread(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Looper looper = initializeLooper();
|
||||
ListenCallback callback = new ListenCallback(looper);
|
||||
|
||||
TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
telephonyManager.listen(callback, PhoneStateListener.LISTEN_SERVICE_STATE);
|
||||
|
||||
Looper.loop();
|
||||
|
||||
telephonyManager.listen(callback, PhoneStateListener.LISTEN_NONE);
|
||||
|
||||
set(callback.isConnected());
|
||||
}
|
||||
|
||||
private Looper initializeLooper() {
|
||||
Looper looper = Looper.myLooper();
|
||||
|
||||
if (looper == null) {
|
||||
Looper.prepare();
|
||||
}
|
||||
|
||||
return Looper.myLooper();
|
||||
}
|
||||
|
||||
public synchronized boolean get() {
|
||||
while (!complete) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private synchronized void set(boolean result) {
|
||||
this.result = result;
|
||||
this.complete = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListenCallback extends PhoneStateListener {
|
||||
|
||||
private final Looper looper;
|
||||
private volatile boolean connected;
|
||||
|
||||
public ListenCallback(Looper looper) {
|
||||
this.looper = looper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceStateChanged(ServiceState serviceState) {
|
||||
this.connected = (serviceState.getState() == ServiceState.STATE_IN_SERVICE);
|
||||
looper.quit();
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2015 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.os.Parcelable;
|
||||
|
||||
|
||||
public abstract class CharacterCalculator implements Parcelable {
|
||||
|
||||
public abstract CharacterState calculateCharacters(String messageBody);
|
||||
|
||||
public static class CharacterState {
|
||||
public final int charactersRemaining;
|
||||
public final int messagesSpent;
|
||||
public final int maxTotalMessageSize;
|
||||
public final int maxPrimaryMessageSize;
|
||||
|
||||
public CharacterState(int messagesSpent, int charactersRemaining, int maxTotalMessageSize, int maxPrimaryMessageSize) {
|
||||
this.messagesSpent = messagesSpent;
|
||||
this.charactersRemaining = charactersRemaining;
|
||||
this.maxTotalMessageSize = maxTotalMessageSize;
|
||||
this.maxPrimaryMessageSize = maxPrimaryMessageSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
/**
|
||||
* This exists as a temporary shim to improve the callsites where we'll be setting the expiration timer.
|
||||
*
|
||||
* Until the versions that don't understand expiration timers expire, we'll have to check capabilities before incrementing the version.
|
||||
*
|
||||
* After those old clients expire, we can remove this shim entirely and call the RecipientTable methods directly.
|
||||
*/
|
||||
object ExpirationTimerUtil {
|
||||
|
||||
@JvmStatic
|
||||
fun setExpirationTimer(recipientId: RecipientId, expirationTimeSeconds: Int): Int {
|
||||
val selfCapable = true
|
||||
val recipientCapable = true
|
||||
|
||||
return if (selfCapable && recipientCapable) {
|
||||
SignalDatabase.recipients.setExpireMessagesAndIncrementVersion(recipientId, expirationTimeSeconds)
|
||||
} else {
|
||||
SignalDatabase.recipients.setExpireMessagesWithoutIncrementingVersion(recipientId, expirationTimeSeconds)
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,7 @@ import android.content.DialogInterface
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
@@ -17,24 +14,6 @@ import androidx.fragment.app.Fragment
|
||||
*/
|
||||
object FragmentDialogs {
|
||||
|
||||
fun Fragment.displayInDialogAboveAnchor(
|
||||
anchorView: View,
|
||||
@LayoutRes contentLayoutId: Int,
|
||||
windowDim: Float = -1f,
|
||||
onShow: (DialogInterface, View) -> Unit = { _, _ -> }
|
||||
): DialogInterface {
|
||||
val contentView = LayoutInflater.from(anchorView.context).inflate(contentLayoutId, requireView() as ViewGroup, false)
|
||||
|
||||
contentView.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(contentView.layoutParams.width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(contentView.layoutParams.height, View.MeasureSpec.EXACTLY)
|
||||
)
|
||||
|
||||
contentView.layout(0, 0, contentView.measuredWidth, contentView.measuredHeight)
|
||||
|
||||
return displayInDialogAboveAnchor(anchorView, contentView, windowDim, onShow)
|
||||
}
|
||||
|
||||
@SuppressLint("AlertDialogBuilderUsage")
|
||||
fun Fragment.displayInDialogAboveAnchor(
|
||||
anchorView: View,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
/**
|
||||
* A function which takes 3 inputs and returns 1 output.
|
||||
*/
|
||||
public interface Function3<A, B, C, D> {
|
||||
D apply(A a, B b, C c);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.text.Html;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class HtmlUtil {
|
||||
public static @NonNull String bold(@NonNull String target) {
|
||||
return "<b>" + Html.escapeHtml(target) + "</b>";
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.push.IasTrustStore;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public final class IasKeyStore {
|
||||
|
||||
private IasKeyStore() {
|
||||
}
|
||||
|
||||
public static KeyStore getIasKeyStore(@NonNull Context context) {
|
||||
try {
|
||||
TrustStore contactTrustStore = new IasTrustStore(context);
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance("BKS");
|
||||
keyStore.load(contactTrustStore.getKeyStoreInputStream(), contactTrustStore.getKeyStorePassword().toCharArray());
|
||||
|
||||
return keyStore;
|
||||
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.graphics.PointF;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class MathUtils {
|
||||
|
||||
/**
|
||||
* For more info:
|
||||
* <a href="http://math.stackexchange.com/questions/190111/how-to-check-if-a-point-is-inside-a-rectangle">StackOverflow: How to check point is in rectangle</a>
|
||||
*
|
||||
* @param pt point to check
|
||||
* @param v1 vertex 1 of the triangle
|
||||
* @param v2 vertex 2 of the triangle
|
||||
* @param v3 vertex 3 of the triangle
|
||||
* @return true if point (x, y) is inside the triangle
|
||||
*/
|
||||
public static boolean pointInTriangle(@NonNull PointF pt, @NonNull PointF v1,
|
||||
@NonNull PointF v2, @NonNull PointF v3) {
|
||||
|
||||
boolean b1 = crossProduct(pt, v1, v2) < 0.0f;
|
||||
boolean b2 = crossProduct(pt, v2, v3) < 0.0f;
|
||||
boolean b3 = crossProduct(pt, v3, v1) < 0.0f;
|
||||
|
||||
return (b1 == b2) && (b2 == b3);
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates cross product of vectors AB and AC
|
||||
*
|
||||
* @param a beginning of 2 vectors
|
||||
* @param b end of vector 1
|
||||
* @param c end of vector 2
|
||||
* @return cross product AB * AC
|
||||
*/
|
||||
private static float crossProduct(@NonNull PointF a, @NonNull PointF b, @NonNull PointF c) {
|
||||
return crossProduct(a.x, a.y, b.x, b.y, c.x, c.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates cross product of vectors AB and AC
|
||||
*
|
||||
* @param ax X coordinate of point A
|
||||
* @param ay Y coordinate of point A
|
||||
* @param bx X coordinate of point B
|
||||
* @param by Y coordinate of point B
|
||||
* @param cx X coordinate of point C
|
||||
* @param cy Y coordinate of point C
|
||||
* @return cross product AB * AC
|
||||
*/
|
||||
private static float crossProduct(float ax, float ay, float bx, float by, float cx, float cy) {
|
||||
return (ax - cx) * (by - cy) - (bx - cx) * (ay - cy);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
public class MmsCharacterCalculator extends CharacterCalculator {
|
||||
|
||||
private static final int MAX_SIZE = 5000;
|
||||
|
||||
@Override
|
||||
public CharacterState calculateCharacters(String messageBody) {
|
||||
return new CharacterState(1, MAX_SIZE - messageBody.length(), MAX_SIZE, MAX_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
}
|
||||
|
||||
public static final Creator<MmsCharacterCalculator> CREATOR = new Creator<MmsCharacterCalculator>() {
|
||||
@Override
|
||||
public MmsCharacterCalculator createFromParcel(Parcel in) {
|
||||
return new MmsCharacterCalculator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MmsCharacterCalculator[] newArray(int size) {
|
||||
return new MmsCharacterCalculator[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2015 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
public class PushCharacterCalculator extends CharacterCalculator {
|
||||
private static final int MAX_TOTAL_SIZE = 64 * 1024;
|
||||
private static final int MAX_PRIMARY_SIZE = 2000;
|
||||
@Override
|
||||
public CharacterState calculateCharacters(String messageBody) {
|
||||
return new CharacterState(1, MAX_TOTAL_SIZE - messageBody.length(), MAX_TOTAL_SIZE, MAX_PRIMARY_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
}
|
||||
|
||||
public static final Creator<PushCharacterCalculator> CREATOR = new Creator<PushCharacterCalculator>() {
|
||||
@Override
|
||||
public PushCharacterCalculator createFromParcel(Parcel in) {
|
||||
return new PushCharacterCalculator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PushCharacterCalculator[] newArray(int size) {
|
||||
return new PushCharacterCalculator[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1086,14 +1086,6 @@ object RemoteConfig {
|
||||
value.asLong(8.kibiBytes.inWholeBytes).bytes
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@get:JvmName("libsignalEnforceMinTlsVersion")
|
||||
val libsignalEnforceMinTlsVersion by remoteBoolean(
|
||||
key = "android.libsignalEnforceMinTlsVersion",
|
||||
defaultValue = false,
|
||||
hotSwappable = false
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
val backgroundMessageProcessInterval: Long by remoteValue(
|
||||
key = "android.messageProcessor.alarmIntervalMins",
|
||||
@@ -1131,14 +1123,6 @@ object RemoteConfig {
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/** Whether or not to show chat folders. */
|
||||
@JvmStatic
|
||||
val showChatFolders: Boolean by remoteBoolean(
|
||||
key = "android.showChatFolders.2",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/** Whether or not to use the new pinned chat UI. */
|
||||
@JvmStatic
|
||||
val inlinePinnedChats: Boolean by remoteBoolean(
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
public final class RequestCodes {
|
||||
|
||||
public static final int NOT_SET = -1;
|
||||
|
||||
private RequestCodes() { }
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.ArrayRes;
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
public class ResUtil {
|
||||
|
||||
public static int getColor(Context context, @AttrRes int attr) {
|
||||
final TypedArray styledAttributes = context.obtainStyledAttributes(new int[]{attr});
|
||||
final int result = styledAttributes.getColor(0, -1);
|
||||
styledAttributes.recycle();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int getDrawableRes(Context c, @AttrRes int attr) {
|
||||
return getDrawableRes(c.getTheme(), attr);
|
||||
}
|
||||
|
||||
public static int getDrawableRes(Theme theme, @AttrRes int attr) {
|
||||
final TypedValue out = new TypedValue();
|
||||
theme.resolveAttribute(attr, out, true);
|
||||
return out.resourceId;
|
||||
}
|
||||
|
||||
public static Drawable getDrawable(Context c, @AttrRes int attr) {
|
||||
return AppCompatResources.getDrawable(c, getDrawableRes(c, attr));
|
||||
}
|
||||
|
||||
public static int[] getResourceIds(Context c, @ArrayRes int array) {
|
||||
final TypedArray typedArray = c.getResources().obtainTypedArray(array);
|
||||
final int[] resourceIds = new int[typedArray.length()];
|
||||
for (int i = 0; i < typedArray.length(); i++) {
|
||||
resourceIds[i] = typedArray.getResourceId(i, 0);
|
||||
}
|
||||
typedArray.recycle();
|
||||
return resourceIds;
|
||||
}
|
||||
|
||||
public static float getFloat(@NonNull Context context, @DimenRes int resId) {
|
||||
TypedValue value = new TypedValue();
|
||||
context.getResources().getValue(resId, value, true);
|
||||
return value.getFloat();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
|
||||
/**
|
||||
* A lifecycle-aware observer that will let the changes to the [TextSecurePreferences] be observed.
|
||||
*
|
||||
* @param keysToListeners a map of [TextSecurePreferences] string keys to listeners that should be invoked when the values change.
|
||||
*/
|
||||
class SharedPreferencesLifecycleObserver(private val context: Context, keysToListeners: Map<String, () -> Unit>) : DefaultLifecycleObserver {
|
||||
|
||||
private val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
keysToListeners[key]?.invoke()
|
||||
}
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
TextSecurePreferences.registerListener(context, listener)
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
TextSecurePreferences.unregisterListener(context, listener)
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* FutureTask with a reference identifier tag.
|
||||
*
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class TaggedFutureTask<V> extends FutureTask<V> {
|
||||
private final Object tag;
|
||||
public TaggedFutureTask(Runnable runnable, V result, Object tag) {
|
||||
super(runnable, result);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public TaggedFutureTask(Callable<V> callable, Object tag) {
|
||||
super(callable);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public Object getTag() {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.ObjectsCompat;
|
||||
|
||||
public class Triple<A, B, C> {
|
||||
|
||||
private final A a;
|
||||
private final B b;
|
||||
private final C c;
|
||||
|
||||
public Triple(@Nullable A a, @Nullable B b, @Nullable C c) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public @Nullable A first() {
|
||||
return a;
|
||||
}
|
||||
|
||||
public @Nullable B second() {
|
||||
return b;
|
||||
}
|
||||
|
||||
public @Nullable C third() {
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Triple)) {
|
||||
return false;
|
||||
}
|
||||
Triple<?, ?, ?> t = (Triple<?, ?, ?>) o;
|
||||
return ObjectsCompat.equals(t.a, a) && ObjectsCompat.equals(t.b, b) && ObjectsCompat.equals(t.c, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (a == null ? 0 : a.hashCode()) ^ (b == null ? 0 : b.hashCode()) ^ (c == null ? 0 : c.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.view.View
|
||||
import android.view.animation.AnimationUtils
|
||||
import androidx.annotation.AnimRes
|
||||
|
||||
/**
|
||||
* Runs the given animation on this view, assuming that the view is in an INVISIBLE or HIDDEN state.
|
||||
*/
|
||||
fun View.runRevealAnimation(@AnimRes anim: Int) {
|
||||
animation = AnimationUtils.loadAnimation(context, anim)
|
||||
visible = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given animation on this view, assuming that the view is in a VISIBLE state and will
|
||||
* hide on completion
|
||||
*/
|
||||
fun View.runHideAnimation(@AnimRes anim: Int) {
|
||||
startAnimation(
|
||||
AnimationUtils.loadAnimation(context, anim).apply {
|
||||
setListeners(onAnimationEnd = {
|
||||
visible = false
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WorkerThread extends Thread {
|
||||
|
||||
private final List<Runnable> workQueue;
|
||||
|
||||
public WorkerThread(List<Runnable> workQueue, String name) {
|
||||
super(name);
|
||||
this.workQueue = workQueue;
|
||||
}
|
||||
|
||||
private Runnable getWork() {
|
||||
synchronized (workQueue) {
|
||||
try {
|
||||
while (workQueue.isEmpty())
|
||||
workQueue.wait();
|
||||
|
||||
return workQueue.remove(0);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new AssertionError(ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (;;)
|
||||
getWork().run();
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util.livedata;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MediatorLiveData;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Triple;
|
||||
|
||||
public final class LiveDataTriple<A, B, C> extends MediatorLiveData<Triple<A, B, C>> {
|
||||
private A a;
|
||||
private B b;
|
||||
private C c;
|
||||
|
||||
public LiveDataTriple(@NonNull LiveData<A> liveDataA,
|
||||
@NonNull LiveData<B> liveDataB,
|
||||
@NonNull LiveData<C> liveDataC)
|
||||
{
|
||||
this(liveDataA, liveDataB, liveDataC, null, null, null);
|
||||
}
|
||||
|
||||
public LiveDataTriple(@NonNull LiveData<A> liveDataA,
|
||||
@NonNull LiveData<B> liveDataB,
|
||||
@NonNull LiveData<C> liveDataC,
|
||||
@Nullable A initialA,
|
||||
@Nullable B initialB,
|
||||
@Nullable C initialC)
|
||||
{
|
||||
a = initialA;
|
||||
b = initialB;
|
||||
c = initialC;
|
||||
setValue(new Triple<>(a, b, c));
|
||||
|
||||
if (liveDataA == liveDataB && liveDataA == liveDataC) {
|
||||
|
||||
addSource(liveDataA, a -> {
|
||||
if (a != null) {
|
||||
this.a = a;
|
||||
|
||||
//noinspection unchecked: A is B if live datas are same instance
|
||||
this.b = (B) a;
|
||||
|
||||
//noinspection unchecked: A is C if live datas are same instance
|
||||
this.c = (C) a;
|
||||
}
|
||||
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
} else if (liveDataA == liveDataB) {
|
||||
|
||||
addSource(liveDataA, a -> {
|
||||
if (a != null) {
|
||||
this.a = a;
|
||||
|
||||
//noinspection unchecked: A is B if live datas are same instance
|
||||
this.b = (B) a;
|
||||
}
|
||||
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
addSource(liveDataC, c -> {
|
||||
if (c != null) {
|
||||
this.c = c;
|
||||
}
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
} else if (liveDataA == liveDataC) {
|
||||
|
||||
addSource(liveDataA, a -> {
|
||||
if (a != null) {
|
||||
this.a = a;
|
||||
|
||||
//noinspection unchecked: A is C if live datas are same instance
|
||||
this.c = (C) a;
|
||||
}
|
||||
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
addSource(liveDataB, b -> {
|
||||
if (b != null) {
|
||||
this.b = b;
|
||||
}
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
} else if (liveDataB == liveDataC) {
|
||||
|
||||
addSource(liveDataB, b -> {
|
||||
if (b != null) {
|
||||
this.b = b;
|
||||
|
||||
//noinspection unchecked: A is C if live datas are same instance
|
||||
this.c = (C) b;
|
||||
}
|
||||
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
addSource(liveDataA, a -> {
|
||||
if (a != null) {
|
||||
this.a = a;
|
||||
}
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
addSource(liveDataA, a -> {
|
||||
if (a != null) {
|
||||
this.a = a;
|
||||
}
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
addSource(liveDataB, b -> {
|
||||
if (b != null) {
|
||||
this.b = b;
|
||||
}
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
addSource(liveDataC, c -> {
|
||||
if (c != null) {
|
||||
this.c = c;
|
||||
}
|
||||
setValue(new Triple<>(a, b, c));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user