Unused code cleanup.

This commit is contained in:
Anton Chekulaev 2020-12-02 23:01:04 +11:00
parent 12804a30c0
commit 5e3cb706c4
55 changed files with 45 additions and 5681 deletions

View File

@ -399,13 +399,6 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="org.thoughtcrime.securesms.contactshare.ContactShareEditActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity
android:name="org.thoughtcrime.securesms.contactshare.ContactNameEditActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Theme.Session.DayNight.NoActionBar" />
<activity
android:name="org.thoughtcrime.securesms.ShortcutLauncherActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"

View File

@ -49,8 +49,6 @@ import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ViewUtil;

View File

@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import network.loki.messenger.R;
public class HourglassView extends View {
private final Paint foregroundPaint;
private final Paint backgroundPaint;
private final Paint progressPaint;
private Bitmap empty;
private Bitmap full;
private float percentage;
private int offset;
public HourglassView(Context context) {
this(context, null);
}
public HourglassView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HourglassView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int tint = 0;
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.HourglassView, 0, 0);
this.empty = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.HourglassView_empty, 0));
this.full = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.HourglassView_full, 0));
this.percentage = typedArray.getInt(R.styleable.HourglassView_percentage, 50);
this.offset = typedArray.getInt(R.styleable.HourglassView_offset, 0);
tint = typedArray.getColor(R.styleable.HourglassView_tint, 0);
typedArray.recycle();
}
this.backgroundPaint = new Paint();
this.foregroundPaint = new Paint();
this.progressPaint = new Paint();
this.backgroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
this.foregroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
this.progressPaint.setColor(getResources().getColor(R.color.black));
this.progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
@Override
public void onDraw(Canvas canvas) {
float progressHeight = (full.getHeight() - (offset*2)) * (percentage / 100);
canvas.drawBitmap(full, 0, 0, backgroundPaint);
canvas.drawRect(0, 0, full.getWidth(), offset + progressHeight, progressPaint);
canvas.drawBitmap(empty, 0, 0, foregroundPaint);
}
public void setPercentage(float percentage) {
this.percentage = percentage;
invalidate();
}
public void setTint(int tint) {
this.backgroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
this.foregroundPaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.MULTIPLY));
}
}

View File

@ -1,105 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
import network.loki.messenger.R;
public class ImageDivet extends AppCompatImageView {
private static final float CORNER_OFFSET = 12F;
private static final String[] POSITIONS = new String[] {"bottom_right"};
private Drawable drawable;
private int drawableIntrinsicWidth;
private int drawableIntrinsicHeight;
private int position;
private float density;
public ImageDivet(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(attrs);
}
public ImageDivet(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
public ImageDivet(Context context) {
super(context);
initialize(null);
}
private void initialize(AttributeSet attrs) {
if (attrs != null) {
position = attrs.getAttributeListValue(null, "position", POSITIONS, -1);
}
density = getContext().getResources().getDisplayMetrics().density;
setDrawable();
}
private void setDrawable() {
int attributes[] = new int[] {R.attr.lower_right_divet};
TypedArray drawables = getContext().obtainStyledAttributes(attributes);
switch (position) {
case 0:
drawable = drawables.getDrawable(0);
break;
}
drawableIntrinsicWidth = drawable.getIntrinsicWidth();
drawableIntrinsicHeight = drawable.getIntrinsicHeight();
drawables.recycle();
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
c.save();
computeBounds(c);
drawable.draw(c);
c.restore();
}
public void setPosition(int position) {
this.position = position;
setDrawable();
invalidate();
}
public int getPosition() {
return position;
}
public float getCloseOffset() {
return CORNER_OFFSET * density;
}
public float getFarOffset() {
return getCloseOffset() + drawableIntrinsicHeight;
}
private void computeBounds(Canvas c) {
final int right = getWidth();
final int bottom = getHeight();
switch (position) {
case 0:
drawable.setBounds(
right - drawableIntrinsicWidth,
bottom - drawableIntrinsicHeight,
right,
bottom);
break;
}
}
}

View File

@ -1,168 +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.components;
import android.content.Context;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.RelativeLayout;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.contacts.RecipientsAdapter;
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
/**
* Panel component combining both an editable field with a button for
* a list-based contact selector.
*
* @author Moxie Marlinspike
*/
public class PushRecipientsPanel extends RelativeLayout implements RecipientModifiedListener {
private final String TAG = PushRecipientsPanel.class.getSimpleName();
private RecipientsPanelChangedListener panelChangeListener;
private RecipientsEditor recipientsText;
private View panel;
private static final int RECIPIENTS_MAX_LENGTH = 312;
public PushRecipientsPanel(Context context) {
super(context);
initialize();
}
public PushRecipientsPanel(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public PushRecipientsPanel(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public List<Recipient> getRecipients() {
String rawText = recipientsText.getText().toString();
return getRecipientsFromString(getContext(), rawText, true);
}
public void disable() {
recipientsText.setText("");
panel.setVisibility(View.GONE);
}
public void setPanelChangeListener(RecipientsPanelChangedListener panelChangeListener) {
this.panelChangeListener = panelChangeListener;
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.push_recipients_panel, this, true);
View imageButton = findViewById(R.id.contacts_button);
((MarginLayoutParams) imageButton.getLayoutParams()).topMargin = 0;
panel = findViewById(R.id.recipients_panel);
initRecipientsEditor();
}
private void initRecipientsEditor() {
this.recipientsText = (RecipientsEditor)findViewById(R.id.recipients_text);
List<Recipient> recipients = getRecipients();
for (Recipient recipient : recipients) {
recipient.addListener(this);
}
recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));
recipientsText.populate(recipients);
recipientsText.setOnFocusChangeListener(new FocusChangedListener());
recipientsText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (panelChangeListener != null) {
panelChangeListener.onRecipientsPanelUpdate(getRecipients());
}
recipientsText.setText("");
}
});
}
private @NonNull List<Recipient> getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) {
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
List<Recipient> recipients = new LinkedList<>();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (!TextUtils.isEmpty(token)) {
if (hasBracketedNumber(token)) recipients.add(Recipient.from(context, Address.fromExternal(context, parseBracketedNumber(token)), asynchronous));
else recipients.add(Recipient.from(context, Address.fromExternal(context, token), asynchronous));
}
}
return recipients;
}
private boolean hasBracketedNumber(String recipient) {
int openBracketIndex = recipient.indexOf('<');
return (openBracketIndex != -1) &&
(recipient.indexOf('>', openBracketIndex) != -1);
}
private String parseBracketedNumber(String recipient) {
int begin = recipient.indexOf('<');
int end = recipient.indexOf('>', begin);
String value = recipient.substring(begin + 1, end);
return value;
}
@Override
public void onModified(Recipient recipient) {
recipientsText.populate(getRecipients());
}
private class FocusChangedListener implements View.OnFocusChangeListener {
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus && (panelChangeListener != null)) {
panelChangeListener.onRecipientsPanelUpdate(getRecipients());
}
}
}
public interface RecipientsPanelChangedListener {
public void onRecipientsPanelUpdate(List<Recipient> recipients);
}
}

View File

@ -1,88 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.logging.Log;
import android.widget.Toast;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class RatingManager {
private static final int DAYS_SINCE_INSTALL_THRESHOLD = 7;
private static final int DAYS_UNTIL_REPROMPT_THRESHOLD = 4;
private static final String TAG = RatingManager.class.getSimpleName();
public static void showRatingDialogIfNecessary(Context context) {
if (!TextSecurePreferences.isRatingEnabled(context)) return;
long daysSinceInstall = getDaysSinceInstalled(context);
long laterTimestamp = TextSecurePreferences.getRatingLaterTimestamp(context);
if (daysSinceInstall >= DAYS_SINCE_INSTALL_THRESHOLD &&
System.currentTimeMillis() >= laterTimestamp)
{
showRatingDialog(context);
}
}
private static void showRatingDialog(final Context context) {
new AlertDialog.Builder(context)
.setTitle(R.string.RatingManager_rate_this_app)
.setMessage(R.string.RatingManager_if_you_enjoy_using_this_app_please_take_a_moment)
.setPositiveButton(R.string.RatingManager_rate_now, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TextSecurePreferences.setRatingEnabled(context, false);
startPlayStore(context);
}
})
.setNegativeButton(R.string.RatingManager_no_thanks, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TextSecurePreferences.setRatingEnabled(context, false);
}
})
.setNeutralButton(R.string.RatingManager_later, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
long waitUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(DAYS_UNTIL_REPROMPT_THRESHOLD);
TextSecurePreferences.setRatingLaterTimestamp(context, waitUntil);
}
})
.show();
}
private static void startPlayStore(Context context) {
Uri uri = Uri.parse("market://details?id=" + context.getPackageName());
try {
context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
} catch (ActivityNotFoundException e) {
Log.w(TAG, e);
Toast.makeText(context, R.string.RatingManager_whoops_the_play_store_app_does_not_appear_to_be_installed, Toast.LENGTH_LONG).show();
}
}
private static long getDaysSinceInstalled(Context context) {
try {
long installTimestamp = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0)
.firstInstallTime;
return TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - installTimestamp);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, e);
return 0;
}
}
}

View File

@ -1,213 +0,0 @@
/**
* Modified version of
* https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller
*
* Their license:
*
* 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.
*/
package org.thoughtcrime.securesms.components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
public class RecyclerViewFastScroller extends LinearLayout {
private static final int BUBBLE_ANIMATION_DURATION = 100;
private static final int TRACK_SNAP_RANGE = 5;
private @NonNull TextView bubble;
private @NonNull View handle;
private @Nullable RecyclerView recyclerView;
private int height;
private ObjectAnimator currentAnimator;
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
if (handle.isSelected()) return;
final int offset = recyclerView.computeVerticalScrollOffset();
final int range = recyclerView.computeVerticalScrollRange();
final int extent = recyclerView.computeVerticalScrollExtent();
final int offsetRange = Math.max(range - extent, 1);
setBubbleAndHandlePosition((float) Util.clamp(offset, 0, offsetRange) / offsetRange);
}
};
public interface FastScrollAdapter {
CharSequence getBubbleText(int pos);
}
public RecyclerViewFastScroller(final Context context) {
this(context, null);
}
public RecyclerViewFastScroller(final Context context, final AttributeSet attrs) {
super(context, attrs);
setOrientation(HORIZONTAL);
setClipChildren(false);
setScrollContainer(true);
inflate(context, R.layout.recycler_view_fast_scroller, this);
bubble = ViewUtil.findById(this, R.id.fastscroller_bubble);
handle = ViewUtil.findById(this, R.id.fastscroller_handle);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
height = h;
}
@Override
@TargetApi(11)
public boolean onTouchEvent(@NonNull MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (event.getX() < ViewUtil.getX(handle) - handle.getPaddingLeft() ||
event.getY() < ViewUtil.getY(handle) - handle.getPaddingTop() ||
event.getY() > ViewUtil.getY(handle) + handle.getHeight() + handle.getPaddingBottom())
{
return false;
}
if (currentAnimator != null) {
currentAnimator.cancel();
}
if (bubble.getVisibility() != VISIBLE) {
showBubble();
}
handle.setSelected(true);
case MotionEvent.ACTION_MOVE:
final float y = event.getY();
setBubbleAndHandlePosition(y / height);
setRecyclerViewPosition(y);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handle.setSelected(false);
hideBubble();
return true;
}
return super.onTouchEvent(event);
}
public void setRecyclerView(final @NonNull RecyclerView recyclerView) {
if (this.recyclerView != null) {
this.recyclerView.removeOnScrollListener(onScrollListener);
}
this.recyclerView = recyclerView;
recyclerView.addOnScrollListener(onScrollListener);
recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
if (handle.isSelected()) return true;
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
float proportion = (float)verticalScrollOffset / ((float)verticalScrollRange - height);
setBubbleAndHandlePosition(height * proportion);
return true;
}
});
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (recyclerView != null)
recyclerView.removeOnScrollListener(onScrollListener);
}
private void setRecyclerViewPosition(float y) {
if (recyclerView != null) {
final int itemCount = recyclerView.getAdapter().getItemCount();
float proportion;
if (ViewUtil.getY(handle) == 0) {
proportion = 0f;
} else if (ViewUtil.getY(handle) + handle.getHeight() >= height - TRACK_SNAP_RANGE) {
proportion = 1f;
} else {
proportion = y / (float)height;
}
final int targetPos = Util.clamp((int)(proportion * (float)itemCount), 0, itemCount - 1);
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
final CharSequence bubbleText = ((FastScrollAdapter) recyclerView.getAdapter()).getBubbleText(targetPos);
bubble.setText(bubbleText);
}
}
private void setBubbleAndHandlePosition(float y) {
final int handleHeight = handle.getHeight();
final int bubbleHeight = bubble.getHeight();
final int handleY = Util.clamp((int)((height - handleHeight) * y), 0, height - handleHeight);
ViewUtil.setY(handle, handleY);
ViewUtil.setY(bubble, Util.clamp(handleY - bubbleHeight - bubble.getPaddingBottom() + handleHeight,
0,
height - bubbleHeight));
}
@TargetApi(11)
private void showBubble() {
bubble.setVisibility(VISIBLE);
if (VERSION.SDK_INT >= 11) {
if (currentAnimator != null) currentAnimator.cancel();
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.start();
}
}
@TargetApi(11)
private void hideBubble() {
if (VERSION.SDK_INT >= 11) {
if (currentAnimator != null) currentAnimator.cancel();
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator = null;
}
});
currentAnimator.start();
} else {
bubble.setVisibility(INVISIBLE);
}
}
}

View File

@ -1,230 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
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.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.Util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class SharedContactView extends LinearLayout implements RecipientModifiedListener {
private ImageView avatarView;
private TextView nameView;
private TextView numberView;
private TextView actionButtonView;
private ConversationItemFooter footer;
private Contact contact;
private Locale locale;
private GlideRequests glideRequests;
private EventListener eventListener;
private CornerMask cornerMask;
private int bigCornerRadius;
private int smallCornerRadius;
private final Map<String, Recipient> activeRecipients = new HashMap<>();
public SharedContactView(Context context) {
super(context);
initialize(null);
}
public SharedContactView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
public SharedContactView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(attrs);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public SharedContactView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(attrs);
}
private void initialize(@Nullable AttributeSet attrs) {
inflate(getContext(), R.layout.shared_contact_view, this);
avatarView = findViewById(R.id.contact_avatar);
nameView = findViewById(R.id.contact_name);
numberView = findViewById(R.id.contact_number);
actionButtonView = findViewById(R.id.contact_action_button);
footer = findViewById(R.id.contact_footer);
cornerMask = new CornerMask(this);
bigCornerRadius = getResources().getDimensionPixelOffset(R.dimen.message_corner_radius);
smallCornerRadius = getResources().getDimensionPixelOffset(R.dimen.message_corner_collapse_radius);
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.SharedContactView, 0, 0);
int titleColor = typedArray.getInt(R.styleable.SharedContactView_contact_titleColor, Color.BLACK);
int captionColor = typedArray.getInt(R.styleable.SharedContactView_contact_captionColor, Color.BLACK);
int iconColor = typedArray.getInt(R.styleable.SharedContactView_contact_footerIconColor, Color.BLACK);
float footerAlpha = typedArray.getFloat(R.styleable.SharedContactView_contact_footerAlpha, 1);
typedArray.recycle();
nameView.setTextColor(titleColor);
numberView.setTextColor(captionColor);
footer.setTextColor(captionColor);
footer.setIconColor(iconColor);
footer.setAlpha(footerAlpha);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
cornerMask.mask(canvas);
}
public void setContact(@NonNull Contact contact, @NonNull GlideRequests glideRequests, @NonNull Locale locale) {
this.glideRequests = glideRequests;
this.locale = locale;
this.contact = contact;
Stream.of(activeRecipients.values()).forEach(recipient -> recipient.removeListener(this));
this.activeRecipients.clear();
presentContact(contact);
presentAvatar(contact.getAvatarAttachment() != null ? contact.getAvatarAttachment().getDataUri() : null);
presentActionButtons(ContactUtil.getRecipients(getContext(), contact));
}
public void setSingularStyle() {
cornerMask.setBottomLeftRadius(bigCornerRadius);
cornerMask.setBottomRightRadius(bigCornerRadius);
}
public void setClusteredIncomingStyle() {
cornerMask.setBottomLeftRadius(smallCornerRadius);
cornerMask.setBottomRightRadius(bigCornerRadius);
}
public void setClusteredOutgoingStyle() {
cornerMask.setBottomLeftRadius(bigCornerRadius);
cornerMask.setBottomRightRadius(smallCornerRadius);
}
public void setEventListener(@NonNull EventListener eventListener) {
this.eventListener = eventListener;
}
public @NonNull View getAvatarView() {
return avatarView;
}
public ConversationItemFooter getFooter() {
return footer;
}
@Override
public void onModified(Recipient recipient) {
Util.runOnMain(() -> presentActionButtons(Collections.singletonList(recipient)));
}
private void presentContact(@Nullable Contact contact) {
if (contact != null) {
nameView.setText(ContactUtil.getDisplayName(contact));
numberView.setText(ContactUtil.getDisplayNumber(contact, locale));
} else {
nameView.setText("");
numberView.setText("");
}
}
private void presentAvatar(@Nullable Uri uri) {
if (uri != null) {
glideRequests.load(new DecryptableUri(uri))
.fallback(R.drawable.ic_contact_picture)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.dontAnimate()
.into(avatarView);
} else {
glideRequests.load(R.drawable.ic_contact_picture)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(avatarView);
}
}
private void presentActionButtons(@NonNull List<Recipient> recipients) {
for (Recipient recipient : recipients) {
activeRecipients.put(recipient.getAddress().serialize(), recipient);
}
List<Recipient> pushUsers = new ArrayList<>(recipients.size());
List<Recipient> systemUsers = new ArrayList<>(recipients.size());
for (Recipient recipient : activeRecipients.values()) {
recipient.addListener(this);
if (recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
pushUsers.add(recipient);
} else if (recipient.isSystemContact()) {
systemUsers.add(recipient);
}
}
if (!pushUsers.isEmpty()) {
actionButtonView.setText(R.string.SharedContactView_message);
actionButtonView.setOnClickListener(v -> {
if (eventListener != null) {
eventListener.onMessageClicked(pushUsers);
}
});
} else if (!systemUsers.isEmpty()) {
actionButtonView.setText(R.string.SharedContactView_invite_to_signal);
actionButtonView.setOnClickListener(v -> {
if (eventListener != null) {
eventListener.onInviteClicked(systemUsers);
}
});
} else {
actionButtonView.setText(R.string.SharedContactView_add_to_contacts);
actionButtonView.setOnClickListener(v -> {
if (eventListener != null && contact != null) {
eventListener.onAddToContactsClicked(contact);
}
});
}
}
public interface EventListener {
void onAddToContactsClicked(@NonNull Contact contact);
void onInviteClicked(@NonNull List<Recipient> choices);
void onMessageClicked(@NonNull List<Recipient> choices);
}
}

View File

@ -1,35 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.widget.LinearLayout;
public class SquareLinearLayout extends LinearLayout {
@SuppressWarnings("unused")
public SquareLinearLayout(Context context) {
super(context);
}
@SuppressWarnings("unused")
public SquareLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@TargetApi(VERSION_CODES.HONEYCOMB) @SuppressWarnings("unused")
public SquareLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(VERSION_CODES.LOLLIPOP) @SuppressWarnings("unused")
public SquareLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//noinspection SuspiciousNameCombination
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}

View File

@ -1,57 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View.OnClickListener;
public abstract class Reminder {
private CharSequence title;
private CharSequence text;
private OnClickListener okListener;
private OnClickListener dismissListener;
public Reminder(@Nullable CharSequence title,
@NonNull CharSequence text)
{
this.title = title;
this.text = text;
}
public @Nullable CharSequence getTitle() {
return title;
}
public CharSequence getText() {
return text;
}
public OnClickListener getOkListener() {
return okListener;
}
public OnClickListener getDismissListener() {
return dismissListener;
}
public void setOkListener(OnClickListener okListener) {
this.okListener = okListener;
}
public void setDismissListener(OnClickListener dismissListener) {
this.dismissListener = dismissListener;
}
public boolean isDismissable() {
return true;
}
public @NonNull Importance getImportance() {
return Importance.NORMAL;
}
public enum Importance {
NORMAL, ERROR
}
}

View File

@ -1,94 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.util.ViewUtil;
/**
* View to display actionable reminders to the user
*/
public class ReminderView extends LinearLayout {
private ViewGroup container;
private ImageButton closeButton;
private TextView title;
private TextView text;
private OnDismissListener dismissListener;
public ReminderView(Context context) {
super(context);
initialize();
}
public ReminderView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
@TargetApi(VERSION_CODES.HONEYCOMB)
public ReminderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
private void initialize() {
LayoutInflater.from(getContext()).inflate(R.layout.reminder_header, this, true);
container = ViewUtil.findById(this, R.id.container);
closeButton = ViewUtil.findById(this, R.id.cancel);
title = ViewUtil.findById(this, R.id.reminder_title);
text = ViewUtil.findById(this, R.id.reminder_text);
}
public void showReminder(final Reminder reminder) {
if (!TextUtils.isEmpty(reminder.getTitle())) {
title.setText(reminder.getTitle());
title.setVisibility(VISIBLE);
} else {
title.setText("");
title.setVisibility(GONE);
}
text.setText(reminder.getText());
container.setBackgroundResource(reminder.getImportance() == Reminder.Importance.ERROR ? R.drawable.reminder_background_error
: R.drawable.reminder_background_normal);
setOnClickListener(reminder.getOkListener());
closeButton.setVisibility(reminder.isDismissable() ? View.VISIBLE : View.GONE);
closeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
if (reminder.getDismissListener() != null) reminder.getDismissListener().onClick(v);
if (dismissListener != null) dismissListener.onDismiss();
}
});
container.setVisibility(View.VISIBLE);
}
public void setOnDismissListener(OnDismissListener dismissListener) {
this.dismissListener = dismissListener;
}
public void requestDismiss() {
closeButton.performClick();
}
public void hide() {
container.setVisibility(View.GONE);
}
public interface OnDismissListener {
void onDismiss();
}
}

View File

@ -1,169 +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.lang.System;
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;
}
}

View File

@ -1,24 +0,0 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.net.Uri;
import java.util.List;
public abstract class ContactIdentityManager {
public static ContactIdentityManager getInstance(Context context) {
return new ContactIdentityManagerICS(context);
}
protected final Context context;
public ContactIdentityManager(Context context) {
this.context = context.getApplicationContext();
}
public abstract Uri getSelfIdentityUri();
public abstract boolean isSelfIdentityAutoDetected();
public abstract List<Long> getSelfIdentityRawContactIds();
}

View File

@ -1,76 +0,0 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import java.util.LinkedList;
import java.util.List;
class ContactIdentityManagerICS extends ContactIdentityManager {
public ContactIdentityManagerICS(Context context) {
super(context);
}
@Override
public Uri getSelfIdentityUri() {
String[] PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME,
PhoneLookup.LOOKUP_KEY,
PhoneLookup._ID,
};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_URI,
PROJECTION, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
public boolean isSelfIdentityAutoDetected() {
return true;
}
@Override
public List<Long> getSelfIdentityRawContactIds() {
List<Long> results = new LinkedList<Long>();
String[] PROJECTION = new String[] {
ContactsContract.Profile._ID
};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,
PROJECTION, null, null, null);
if (cursor == null || cursor.getCount() == 0)
return null;
while (cursor.moveToNext()) {
results.add(cursor.getLong(0));
}
return results;
} finally {
if (cursor != null)
cursor.close();
}
}
}

View File

@ -26,14 +26,11 @@ import androidx.loader.content.CursorLoader;
import android.text.TextUtils;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.NumberUtil;
import java.util.ArrayList;
@ -45,9 +42,20 @@ import java.util.List;
* @author Jake McGinty
*/
public class ContactsCursorLoader extends CursorLoader {
private static final String TAG = ContactsCursorLoader.class.getSimpleName();
static final int NORMAL_TYPE = 0;
static final int PUSH_TYPE = 1;
static final int NEW_TYPE = 2;
static final int RECENT_TYPE = 3;
static final int DIVIDER_TYPE = 4;
static final String CONTACT_TYPE_COLUMN = "contact_type";
static final String LABEL_COLUMN = "label";
static final String NUMBER_TYPE_COLUMN = "number_type";
static final String NUMBER_COLUMN = "number";
static final String NAME_COLUMN = "name";
public static final class DisplayMode {
public static final int FLAG_PUSH = 1;
public static final int FLAG_SMS = 1 << 1;
@ -55,11 +63,11 @@ public class ContactsCursorLoader extends CursorLoader {
public static final int FLAG_ALL = FLAG_PUSH | FLAG_SMS | FLAG_GROUPS;
}
private static final String[] CONTACT_PROJECTION = new String[]{ContactsDatabase.NAME_COLUMN,
ContactsDatabase.NUMBER_COLUMN,
ContactsDatabase.NUMBER_TYPE_COLUMN,
ContactsDatabase.LABEL_COLUMN,
ContactsDatabase.CONTACT_TYPE_COLUMN};
private static final String[] CONTACT_PROJECTION = new String[]{NAME_COLUMN,
NUMBER_COLUMN,
NUMBER_TYPE_COLUMN,
LABEL_COLUMN,
CONTACT_TYPE_COLUMN};
private static final int RECENT_CONVERSATION_MAX = 25;
@ -158,7 +166,7 @@ public class ContactsCursorLoader extends CursorLoader {
"",
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactsDatabase.DIVIDER_TYPE });
DIVIDER_TYPE });
return groupHeader;
}
@ -175,17 +183,14 @@ public class ContactsCursorLoader extends CursorLoader {
threadRecord.getRecipient().getAddress().serialize(),
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE,
"",
ContactsDatabase.RECENT_TYPE });
RECENT_TYPE });
}
}
return recentConversations;
}
private List<Cursor> getContactsCursors() {
ContactsDatabase contactsDatabase = DatabaseFactory.getContactsDatabase(getContext());
List<Cursor> cursorList = new ArrayList<>(2);
return cursorList;
return new ArrayList<>(2);
/*
if (!Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
return cursorList;
@ -213,7 +218,7 @@ public class ContactsCursorLoader extends CursorLoader {
groupRecord.getEncodedId(),
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"",
ContactsDatabase.NORMAL_TYPE });
NORMAL_TYPE });
}
}
return groupContacts;
@ -225,33 +230,10 @@ public class ContactsCursorLoader extends CursorLoader {
filter,
ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM,
"\u21e2",
ContactsDatabase.NEW_TYPE });
NEW_TYPE });
return newNumberCursor;
}
private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) {
try {
final long startMillis = System.currentTimeMillis();
final MatrixCursor matrix = new MatrixCursor(CONTACT_PROJECTION);
while (cursor.moveToNext()) {
final String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
final Recipient recipient = Recipient.from(getContext(), Address.fromExternal(getContext(), number), false);
if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) {
matrix.addRow(new Object[]{cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN)),
number,
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN)),
cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN)),
ContactsDatabase.NORMAL_TYPE});
}
}
Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");
return matrix;
} finally {
cursor.close();
}
}
private static boolean isCursorListEmpty(List<Cursor> list) {
int sum = 0;
for (Cursor cursor : list) {

View File

@ -1,729 +0,0 @@
/*
* Copyright (C) 2013 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.contacts;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Build;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.Pair;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Database to supply all types of contacts that TextSecure needs to know about
*
* @author Jake McGinty
*/
public class ContactsDatabase {
private static final String TAG = ContactsDatabase.class.getSimpleName();
private static final String CONTACT_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact";
private static final String CALL_MIMETYPE = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call";
private static final String SYNC = "__TS";
static final String NAME_COLUMN = "name";
static final String NUMBER_COLUMN = "number";
static final String NUMBER_TYPE_COLUMN = "number_type";
static final String LABEL_COLUMN = "label";
static final String CONTACT_TYPE_COLUMN = "contact_type";
static final int NORMAL_TYPE = 0;
static final int PUSH_TYPE = 1;
static final int NEW_TYPE = 2;
static final int RECENT_TYPE = 3;
static final int DIVIDER_TYPE = 4;
private final Context context;
public ContactsDatabase(Context context) {
this.context = context;
}
public synchronized void removeDeletedRawContacts(@NonNull Account account) {
Uri currentContactsUri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
String[] projection = new String[] {BaseColumns._ID, RawContacts.SYNC1};
try (Cursor cursor = context.getContentResolver().query(currentContactsUri, projection, RawContacts.DELETED + " = ?", new String[] {"1"}, null)) {
while (cursor != null && cursor.moveToNext()) {
long rawContactId = cursor.getLong(0);
Log.i(TAG, "Deleting raw contact: " + cursor.getString(1) + ", " + rawContactId);
context.getContentResolver().delete(currentContactsUri, RawContacts._ID + " = ?", new String[] {String.valueOf(rawContactId)});
}
}
}
public synchronized void setRegisteredUsers(@NonNull Account account,
@NonNull List<Address> registeredAddressList,
boolean remove)
throws RemoteException, OperationApplicationException
{
Set<Address> registeredAddressSet = new HashSet<>(registeredAddressList);
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Map<Address, SignalContact> currentContacts = getSignalRawContacts(account);
List<List<Address>> registeredChunks = Util.chunk(registeredAddressList, 50);
for (List<Address> registeredChunk : registeredChunks) {
for (Address registeredAddress : registeredChunk) {
if (!currentContacts.containsKey(registeredAddress)) {
Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(registeredAddress);
if (systemContactInfo.isPresent()) {
Log.i(TAG, "Adding number: " + registeredAddress);
addTextSecureRawContact(operations, account, systemContactInfo.get().number,
systemContactInfo.get().name, systemContactInfo.get().id);
}
}
}
if (!operations.isEmpty()) {
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
operations.clear();
}
}
for (Map.Entry<Address, SignalContact> currentContactEntry : currentContacts.entrySet()) {
if (!registeredAddressSet.contains(currentContactEntry.getKey())) {
if (remove) {
Log.i(TAG, "Removing number: " + currentContactEntry.getKey());
removeTextSecureRawContact(operations, account, currentContactEntry.getValue().getId());
}
} else if (!currentContactEntry.getValue().isVoiceSupported()) {
Log.i(TAG, "Adding voice support: " + currentContactEntry.getKey());
addContactVoiceSupport(operations, currentContactEntry.getKey(), currentContactEntry.getValue().getId());
} else if (!Util.isStringEquals(currentContactEntry.getValue().getRawDisplayName(),
currentContactEntry.getValue().getAggregateDisplayName()))
{
Log.i(TAG, "Updating display name: " + currentContactEntry.getKey());
updateDisplayName(operations, currentContactEntry.getValue().getAggregateDisplayName(), currentContactEntry.getValue().getId(), currentContactEntry.getValue().getDisplayNameSource());
}
}
if (!operations.isEmpty()) {
applyOperationsInBatches(context.getContentResolver(), ContactsContract.AUTHORITY, operations, 50);
}
}
@SuppressLint("Recycle")
public @NonNull Cursor querySystemContacts(@Nullable String filter) {
Uri uri;
if (!TextUtils.isEmpty(filter)) {
uri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI, Uri.encode(filter));
} else {
uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
}
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
uri = uri.buildUpon().appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true").build();
}
String[] projection = new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.LABEL};
String sort = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
Map<String, String> projectionMap = new HashMap<String, String>() {{
put(NAME_COLUMN, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
put(NUMBER_COLUMN, ContactsContract.CommonDataKinds.Phone.NUMBER);
put(NUMBER_TYPE_COLUMN, ContactsContract.CommonDataKinds.Phone.TYPE);
put(LABEL_COLUMN, ContactsContract.CommonDataKinds.Phone.LABEL);
}};
String formattedNumber = "REPLACE(REPLACE(REPLACE(REPLACE(data1,' ',''),'-',''),'(',''),')','')";
String excludeSelection = "(" + formattedNumber +" NOT IN " +
"(SELECT data1 FROM view_data WHERE "+formattedNumber+" = data1) " +
"OR "+formattedNumber+" = data1)" +
"AND " + formattedNumber + "NOT IN (SELECT "+formattedNumber+" FROM view_data where mimetype = '"+CONTACT_MIMETYPE+"')" ;
String fallbackSelection = ContactsContract.Data.SYNC2 + " IS NULL OR " + ContactsContract.Data.SYNC2 + " != '" + SYNC + "'";
Cursor cursor;
try {
cursor = context.getContentResolver().query(uri, projection, excludeSelection, null, sort);
} catch (Exception e) {
Log.w(TAG, e);
cursor = context.getContentResolver().query(uri, projection, fallbackSelection, null, sort);
}
return new ProjectionMappingCursor(cursor, projectionMap, new Pair<>(CONTACT_TYPE_COLUMN, NORMAL_TYPE));
}
@SuppressLint("Recycle")
public @NonNull Cursor queryTextSecureContacts(String filter) {
String[] projection = new String[] {ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Data.DATA1};
String sort = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
Map<String, String> projectionMap = new HashMap<String, String>(){{
put(NAME_COLUMN, ContactsContract.Contacts.DISPLAY_NAME);
put(NUMBER_COLUMN, ContactsContract.Data.DATA1);
}};
Cursor cursor;
if (TextUtils.isEmpty(filter)) {
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.MIMETYPE + " = ?",
new String[] {CONTACT_MIMETYPE},
sort);
} else {
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
ContactsContract.Data.MIMETYPE + " = ? AND (" + ContactsContract.Contacts.DISPLAY_NAME + " LIKE ? OR " + ContactsContract.Data.DATA1 + " LIKE ?)",
new String[] {CONTACT_MIMETYPE,
"%" + filter + "%", "%" + filter + "%"},
sort);
if (context.getString(R.string.note_to_self).toLowerCase().contains(filter.toLowerCase())) {
Optional<SystemContactInfo> self = getSystemContactInfo(Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)));
boolean shouldAdd = true;
if (self.isPresent()) {
boolean nameMatch = self.get().name != null && self.get().name.toLowerCase().contains(filter.toLowerCase());
boolean numberMatch = self.get().number != null && self.get().number.contains(filter);
shouldAdd = !nameMatch && !numberMatch;
}
if (shouldAdd) {
MatrixCursor selfCursor = new MatrixCursor(projection);
selfCursor.addRow(new Object[]{ context.getString(R.string.note_to_self), TextSecurePreferences.getLocalNumber(context)});
cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor });
}
}
}
return new ProjectionMappingCursor(cursor, projectionMap,
new Pair<>(LABEL_COLUMN, "TextSecure"),
new Pair<>(NUMBER_TYPE_COLUMN, 0),
new Pair<>(CONTACT_TYPE_COLUMN, PUSH_TYPE));
}
public @Nullable Cursor getNameDetails(long contactId) {
String[] projection = new String[] { ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.CommonDataKinds.StructuredName.PREFIX,
ContactsContract.CommonDataKinds.StructuredName.SUFFIX,
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME };
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE };
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
selection,
args,
null);
}
public @Nullable String getOrganizationName(long contactId) {
String[] projection = new String[] { ContactsContract.CommonDataKinds.Organization.COMPANY };
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE };
try (Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
selection,
args,
null))
{
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
}
}
return null;
}
public @Nullable Cursor getPhoneDetails(long contactId) {
String[] projection = new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.LABEL };
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE };
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
selection,
args,
null);
}
public @Nullable Cursor getEmailDetails(long contactId) {
String[] projection = new String[] { ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.TYPE,
ContactsContract.CommonDataKinds.Email.LABEL };
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE };
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
selection,
args,
null);
}
public @Nullable Cursor getPostalAddressDetails(long contactId) {
String[] projection = new String[] { ContactsContract.CommonDataKinds.StructuredPostal.TYPE,
ContactsContract.CommonDataKinds.StructuredPostal.LABEL,
ContactsContract.CommonDataKinds.StructuredPostal.STREET,
ContactsContract.CommonDataKinds.StructuredPostal.POBOX,
ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD,
ContactsContract.CommonDataKinds.StructuredPostal.CITY,
ContactsContract.CommonDataKinds.StructuredPostal.REGION,
ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,
ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY };
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE };
return context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
selection,
args,
null);
}
public @Nullable Uri getAvatarUri(long contactId) {
String[] projection = new String[] { ContactsContract.CommonDataKinds.Photo.PHOTO_URI };
String selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
String[] args = new String[] { String.valueOf(contactId), ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE };
try (Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
projection,
selection,
args,
null))
{
if (cursor != null && cursor.moveToFirst()) {
String uri = cursor.getString(0);
if (uri != null) {
return Uri.parse(uri);
}
}
}
return null;
}
private void addContactVoiceSupport(List<ContentProviderOperation> operations,
@NonNull Address address, long rawContactId)
{
operations.add(ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
.withSelection(RawContacts._ID + " = ?", new String[] {String.valueOf(rawContactId)})
.withValue(RawContacts.SYNC4, "true")
.build());
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build())
.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
.withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE)
.withValue(ContactsContract.Data.DATA1, address.toPhoneString())
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, address.toPhoneString()))
.withYieldAllowed(true)
.build());
}
private void updateDisplayName(List<ContentProviderOperation> operations,
@Nullable String displayName,
long rawContactId, int displayNameSource)
{
Uri dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
if (displayNameSource != ContactsContract.DisplayNameSources.STRUCTURED_NAME) {
operations.add(ContentProviderOperation.newInsert(dataUri)
.withValue(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, rawContactId)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.build());
} else {
operations.add(ContentProviderOperation.newUpdate(dataUri)
.withSelection(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
new String[] {String.valueOf(rawContactId), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE})
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.build());
}
}
private void addTextSecureRawContact(List<ContentProviderOperation> operations,
Account account, String e164number, String displayName,
long aggregateId)
{
int index = operations.size();
Uri dataUri = ContactsContract.Data.CONTENT_URI.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_NAME, account.name)
.withValue(RawContacts.ACCOUNT_TYPE, account.type)
.withValue(RawContacts.SYNC1, e164number)
.withValue(RawContacts.SYNC4, String.valueOf(true))
.build());
operations.add(ContentProviderOperation.newInsert(dataUri)
.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, index)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.build());
operations.add(ContentProviderOperation.newInsert(dataUri)
.withValueBackReference(ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, index)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, e164number)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_OTHER)
.withValue(ContactsContract.Data.SYNC2, SYNC)
.build());
operations.add(ContentProviderOperation.newInsert(dataUri)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
.withValue(ContactsContract.Data.MIMETYPE, CONTACT_MIMETYPE)
.withValue(ContactsContract.Data.DATA1, e164number)
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_message_s, e164number))
.withYieldAllowed(true)
.build());
operations.add(ContentProviderOperation.newInsert(dataUri)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, index)
.withValue(ContactsContract.Data.MIMETYPE, CALL_MIMETYPE)
.withValue(ContactsContract.Data.DATA1, e164number)
.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name))
.withValue(ContactsContract.Data.DATA3, context.getString(R.string.ContactsDatabase_signal_call_s, e164number))
.withYieldAllowed(true)
.build());
operations.add(ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, aggregateId)
.withValueBackReference(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, index)
.withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER)
.build());
}
private void removeTextSecureRawContact(List<ContentProviderOperation> operations,
Account account, long rowId)
{
operations.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type)
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build())
.withYieldAllowed(true)
.withSelection(BaseColumns._ID + " = ?", new String[] {String.valueOf(rowId)})
.build());
}
private @NonNull Map<Address, SignalContact> getSignalRawContacts(@NonNull Account account) {
Uri currentContactsUri = RawContacts.CONTENT_URI.buildUpon()
.appendQueryParameter(RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.type).build();
Map<Address, SignalContact> signalContacts = new HashMap<>();
Cursor cursor = null;
try {
String[] projection = new String[] {BaseColumns._ID, RawContacts.SYNC1, RawContacts.SYNC4, RawContacts.CONTACT_ID, RawContacts.DISPLAY_NAME_PRIMARY, RawContacts.DISPLAY_NAME_SOURCE};
cursor = context.getContentResolver().query(currentContactsUri, projection, null, null, null);
while (cursor != null && cursor.moveToNext()) {
Address currentAddress = Address.fromExternal(context, cursor.getString(1));
long rawContactId = cursor.getLong(0);
long contactId = cursor.getLong(3);
String supportsVoice = cursor.getString(2);
String rawContactDisplayName = cursor.getString(4);
String aggregateDisplayName = getDisplayName(contactId);
int rawContactDisplayNameSource = cursor.getInt(5);
signalContacts.put(currentAddress, new SignalContact(rawContactId, supportsVoice, rawContactDisplayName, aggregateDisplayName, rawContactDisplayNameSource));
}
} finally {
if (cursor != null)
cursor.close();
}
return signalContacts;
}
private Optional<SystemContactInfo> getSystemContactInfo(@NonNull Address address)
{
if (!address.isPhone()) return Optional.absent();
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address.toPhoneString()));
String[] projection = {ContactsContract.PhoneLookup.NUMBER,
ContactsContract.PhoneLookup._ID,
ContactsContract.PhoneLookup.DISPLAY_NAME};
Cursor numberCursor = null;
Cursor idCursor = null;
try {
numberCursor = context.getContentResolver().query(uri, projection, null, null, null);
while (numberCursor != null && numberCursor.moveToNext()) {
String systemNumber = numberCursor.getString(0);
Address systemAddress = Address.fromExternal(context, systemNumber);
if (systemAddress.equals(address)) {
idCursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + " = ? ",
new String[] {String.valueOf(numberCursor.getLong(1))},
null);
if (idCursor != null && idCursor.moveToNext()) {
return Optional.of(new SystemContactInfo(numberCursor.getString(2),
numberCursor.getString(0),
idCursor.getLong(0)));
}
}
}
} finally {
if (numberCursor != null) numberCursor.close();
if (idCursor != null) idCursor.close();
}
return Optional.absent();
}
private @Nullable String getDisplayName(long contactId) {
Cursor cursor = context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
new String[]{ContactsContract.Contacts.DISPLAY_NAME},
ContactsContract.Contacts._ID + " = ?",
new String[] {String.valueOf(contactId)},
null);
try {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0);
} else {
return null;
}
} finally {
if (cursor != null) cursor.close();
}
}
private void applyOperationsInBatches(@NonNull ContentResolver contentResolver,
@NonNull String authority,
@NonNull List<ContentProviderOperation> operations,
int batchSize)
throws OperationApplicationException, RemoteException
{
List<List<ContentProviderOperation>> batches = Util.chunk(operations, batchSize);
for (List<ContentProviderOperation> batch : batches) {
contentResolver.applyBatch(authority, new ArrayList<>(batch));
}
}
private static class ProjectionMappingCursor extends CursorWrapper {
private final Map<String, String> projectionMap;
private final Pair<String, Object>[] extras;
@SafeVarargs
ProjectionMappingCursor(Cursor cursor,
Map<String, String> projectionMap,
Pair<String, Object>... extras)
{
super(cursor);
this.projectionMap = projectionMap;
this.extras = extras;
}
@Override
public int getColumnCount() {
return super.getColumnCount() + extras.length;
}
@Override
public int getColumnIndex(String columnName) {
for (int i=0;i<extras.length;i++) {
if (extras[i].first.equals(columnName)) {
return super.getColumnCount() + i;
}
}
return super.getColumnIndex(projectionMap.get(columnName));
}
@Override
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
int index = getColumnIndex(columnName);
if (index == -1) throw new IllegalArgumentException("Bad column name!");
else return index;
}
@Override
public String getColumnName(int columnIndex) {
int baseColumnCount = super.getColumnCount();
if (columnIndex >= baseColumnCount) {
int offset = columnIndex - baseColumnCount;
return extras[offset].first;
}
return getReverseProjection(super.getColumnName(columnIndex));
}
@Override
public String[] getColumnNames() {
String[] names = super.getColumnNames();
String[] allNames = new String[names.length + extras.length];
for (int i=0;i<names.length;i++) {
allNames[i] = getReverseProjection(names[i]);
}
for (int i=0;i<extras.length;i++) {
allNames[names.length + i] = extras[i].first;
}
return allNames;
}
@Override
public int getInt(int columnIndex) {
if (columnIndex >= super.getColumnCount()) {
int offset = columnIndex - super.getColumnCount();
return (Integer)extras[offset].second;
}
return super.getInt(columnIndex);
}
@Override
public String getString(int columnIndex) {
if (columnIndex >= super.getColumnCount()) {
int offset = columnIndex - super.getColumnCount();
return (String)extras[offset].second;
}
return super.getString(columnIndex);
}
private @Nullable String getReverseProjection(String columnName) {
for (Map.Entry<String, String> entry : projectionMap.entrySet()) {
if (entry.getValue().equals(columnName)) {
return entry.getKey();
}
}
return null;
}
}
private static class SystemContactInfo {
private final String name;
private final String number;
private final long id;
private SystemContactInfo(String name, String number, long id) {
this.name = name;
this.number = number;
this.id = id;
}
}
private static class SignalContact {
private final long id;
@Nullable private final String supportsVoice;
@Nullable private final String rawDisplayName;
@Nullable private final String aggregateDisplayName;
private final int displayNameSource;
SignalContact(long id,
@Nullable String supportsVoice,
@Nullable String rawDisplayName,
@Nullable String aggregateDisplayName,
int displayNameSource)
{
this.id = id;
this.supportsVoice = supportsVoice;
this.rawDisplayName = rawDisplayName;
this.aggregateDisplayName = aggregateDisplayName;
this.displayNameSource = displayNameSource;
}
public long getId() {
return id;
}
boolean isVoiceSupported() {
return "true".equals(supportsVoice);
}
@Nullable
String getRawDisplayName() {
return rawDisplayName;
}
@Nullable
String getAggregateDisplayName() {
return aggregateDisplayName;
}
int getDisplayNameSource() {
return displayNameSource;
}
}
}

View File

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

View File

@ -1,154 +0,0 @@
/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 2008 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.
*/
package org.thoughtcrime.securesms.contacts;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.text.Annotation;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.view.View;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
import org.thoughtcrime.securesms.recipients.RecipientsFormatter;
import network.loki.messenger.R;
/**
* This adapter is used to filter contacts on both name and number.
*/
public class RecipientsAdapter extends ResourceCursorAdapter {
public static final int CONTACT_ID_INDEX = 1;
public static final int TYPE_INDEX = 2;
public static final int NUMBER_INDEX = 3;
public static final int LABEL_INDEX = 4;
public static final int NAME_INDEX = 5;
private final Context mContext;
private final ContentResolver mContentResolver;
private ContactAccessor mContactAccessor;
public RecipientsAdapter(Context context) {
super(context, R.layout.recipient_filter_item, null);
mContext = context;
mContentResolver = context.getContentResolver();
mContactAccessor = ContactAccessor.getInstance();
}
@Override
public final CharSequence convertToString(Cursor cursor) {
String name = cursor.getString(RecipientsAdapter.NAME_INDEX);
int type = cursor.getInt(RecipientsAdapter.TYPE_INDEX);
String number = cursor.getString(RecipientsAdapter.NUMBER_INDEX).trim();
String label = cursor.getString(RecipientsAdapter.LABEL_INDEX);
CharSequence displayLabel = mContactAccessor.phoneTypeToString(mContext, type, label);
if (number.length() == 0) {
return number;
}
if (name == null) {
name = "";
} else {
// Names with commas are the bane of the recipient editor's existence.
// We've worked around them by using spans, but there are edge cases
// where the spans get deleted. Furthermore, having commas in names
// can be confusing to the user since commas are used as separators
// between recipients. The best solution is to simply remove commas
// from names.
name = name.replace(", ", " ")
.replace(",", " "); // Make sure we leave a space between parts of names.
}
String nameAndNumber = RecipientsFormatter.formatNameAndNumber(name, number);
SpannableString out = new SpannableString(nameAndNumber);
int len = out.length();
if (!TextUtils.isEmpty(name)) {
out.setSpan(new Annotation("name", name), 0, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
out.setSpan(new Annotation("name", number), 0, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
String person_id = cursor.getString(RecipientsAdapter.CONTACT_ID_INDEX);
out.setSpan(new Annotation("person_id", person_id), 0, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
out.setSpan(new Annotation("label", displayLabel.toString()), 0, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
out.setSpan(new Annotation("number", number), 0, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return out;
}
@Override
public final void bindView(View view, Context context, Cursor cursor) {
TextView name = (TextView) view.findViewById(R.id.name);
name.setText(cursor.getString(NAME_INDEX));
TextView label = (TextView) view.findViewById(R.id.label);
int type = cursor.getInt(TYPE_INDEX);
label.setText(mContactAccessor.phoneTypeToString(mContext, type, cursor.getString(LABEL_INDEX)));
TextView number = (TextView) view.findViewById(R.id.number);
number.setText("(" + cursor.getString(NUMBER_INDEX) + ")");
}
@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return null;
}
/**
* Returns true if all the characters are meaningful as digits
* in a phone number -- letters, digits, and a few punctuation marks.
*/
public static boolean usefulAsDigits(CharSequence cons) {
int len = cons.length();
for (int i = 0; i < len; i++) {
char c = cons.charAt(i);
if ((c >= '0') && (c <= '9')) {
continue;
}
if ((c == ' ') || (c == '-') || (c == '(') || (c == ')') || (c == '.') || (c == '+')
|| (c == '#') || (c == '*')) {
continue;
}
if ((c >= 'A') && (c <= 'Z')) {
continue;
}
if ((c >= 'a') && (c <= 'z')) {
continue;
}
return false;
}
return true;
}
}

View File

@ -1,416 +0,0 @@
/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 2008 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.
*/
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView;
import android.telephony.PhoneNumberUtils;
import android.text.Annotation;
import android.text.Editable;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.MultiAutoCompleteTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientsFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* Provide UI for editing the recipients of multi-media messages.
*/
public class RecipientsEditor extends AppCompatMultiAutoCompleteTextView {
private int mLongPressedPosition = -1;
private final RecipientsEditorTokenizer mTokenizer;
private char mLastSeparator = ',';
private Context mContext;
public RecipientsEditor(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mTokenizer = new RecipientsEditorTokenizer(context, this);
setTokenizer(mTokenizer);
// For the focus to move to the message body when soft Next is pressed
setImeOptions(EditorInfo.IME_ACTION_NEXT);
/*
* The point of this TextWatcher is that when the user chooses
* an address completion from the AutoCompleteTextView menu, it
* is marked up with Annotation objects to tie it back to the
* address book entry that it came from. If the user then goes
* back and edits that part of the text, it no longer corresponds
* to that address book entry and needs to have the Annotations
* claiming that it does removed.
*/
addTextChangedListener(new TextWatcher() {
private Annotation[] mAffected;
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
mAffected = ((Spanned) s).getSpans(start, start + count,
Annotation.class);
}
public void onTextChanged(CharSequence s, int start,
int before, int after) {
if (before == 0 && after == 1) { // inserting a character
char c = s.charAt(start);
if (c == ',' || c == ';') {
// Remember the delimiter the user typed to end this recipient. We'll
// need it shortly in terminateToken().
mLastSeparator = c;
}
}
}
public void afterTextChanged(Editable s) {
if (mAffected != null) {
for (Annotation a : mAffected) {
s.removeSpan(a);
}
}
mAffected = null;
}
});
}
@Override
public boolean enoughToFilter() {
if (!super.enoughToFilter()) {
return false;
}
// If the user is in the middle of editing an existing recipient, don't offer the
// auto-complete menu. Without this, when the user selects an auto-complete menu item,
// it will get added to the list of recipients so we end up with the old before-editing
// recipient and the new post-editing recipient. As a precedent, gmail does not show
// the auto-complete menu when editing an existing recipient.
int end = getSelectionEnd();
int len = getText().length();
return end == len;
}
public int getRecipientCount() {
return mTokenizer.getNumbers().size();
}
public List<String> getNumbers() {
return mTokenizer.getNumbers();
}
// public Recipients constructContactsFromInput() {
// return RecipientFactory.getRecipientsFromString(mContext, mTokenizer.getRawString(), false);
// }
private boolean isValidAddress(String number, boolean isMms) {
/*if (isMms) {
return MessageUtils.isValidMmsAddress(number);
} else {*/
// TODO: PhoneNumberUtils.isWellFormedSmsAddress() only check if the number is a valid
// GSM SMS address. If the address contains a dialable char, it considers it a well
// formed SMS addr. CDMA doesn't work that way and has a different parser for SMS
// address (see CdmaSmsAddress.parse(String address)). We should definitely fix this!!!
return PhoneNumberUtils.isWellFormedSmsAddress(number);
}
public boolean hasValidRecipient(boolean isMms) {
for (String number : mTokenizer.getNumbers()) {
if (isValidAddress(number, isMms))
return true;
}
return false;
}
/*public boolean hasInvalidRecipient(boolean isMms) {
for (String number : mTokenizer.getNumbers()) {
if (!isValidAddress(number, isMms)) {
/* TODO if (MmsConfig.getEmailGateway() == null) {
return true;
} else if (!MessageUtils.isAlias(number)) {
return true;
}
}
}
return false;
}*/
public String formatInvalidNumbers(boolean isMms) {
StringBuilder sb = new StringBuilder();
for (String number : mTokenizer.getNumbers()) {
if (!isValidAddress(number, isMms)) {
if (sb.length() != 0) {
sb.append(", ");
}
sb.append(number);
}
}
return sb.toString();
}
/*public boolean containsEmail() {
if (TextUtils.indexOf(getText(), '@') == -1)
return false;
List<String> numbers = mTokenizer.getNumbers();
for (String number : numbers) {
if (Mms.isEmailAddress(number))
return true;
}
return false;
}*/
public static CharSequence contactToToken(Recipient c) {
String name = c.getName();
String number = c.getAddress().serialize();
SpannableString s = new SpannableString(RecipientsFormatter.formatNameAndNumber(name, number));
int len = s.length();
if (len == 0) {
return s;
}
s.setSpan(new Annotation("number", c.getAddress().serialize()), 0, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return s;
}
public void populate(List<Recipient> list) {
SpannableStringBuilder sb = new SpannableStringBuilder();
for (Recipient c : list) {
if (sb.length() != 0) {
sb.append(", ");
}
sb.append(contactToToken(c));
}
setText(sb);
}
private int pointToPosition(int x, int y) {
x -= getCompoundPaddingLeft();
y -= getExtendedPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
if (layout == null) {
return -1;
}
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
return off;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final int x = (int) ev.getX();
final int y = (int) ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
mLongPressedPosition = pointToPosition(x, y);
}
return super.onTouchEvent(ev);
}
private static String getNumberAt(Spanned sp, int start, int end, Context context) {
return getFieldAt("number", sp, start, end, context);
}
private static int getSpanLength(Spanned sp, int start, int end, Context context) {
// TODO: there's a situation where the span can lose its annotations:
// - add an auto-complete contact
// - add another auto-complete contact
// - delete that second contact and keep deleting into the first
// - we lose the annotation and can no longer get the span.
// Need to fix this case because it breaks auto-complete contacts with commas in the name.
Annotation[] a = sp.getSpans(start, end, Annotation.class);
if (a.length > 0) {
return sp.getSpanEnd(a[0]);
}
return 0;
}
private static String getFieldAt(String field, Spanned sp, int start, int end,
Context context) {
Annotation[] a = sp.getSpans(start, end, Annotation.class);
String fieldValue = getAnnotation(a, field);
if (TextUtils.isEmpty(fieldValue)) {
fieldValue = TextUtils.substring(sp, start, end);
}
return fieldValue;
}
private static String getAnnotation(Annotation[] a, String key) {
for (int i = 0; i < a.length; i++) {
if (a[i].getKey().equals(key)) {
return a[i].getValue();
}
}
return "";
}
private class RecipientsEditorTokenizer
implements MultiAutoCompleteTextView.Tokenizer {
private final MultiAutoCompleteTextView mList;
private final Context mContext;
RecipientsEditorTokenizer(Context context, MultiAutoCompleteTextView list) {
mList = list;
mContext = context;
}
/**
* Returns the start of the token that ends at offset
* <code>cursor</code> within <code>text</code>.
* It is a method from the MultiAutoCompleteTextView.Tokenizer interface.
*/
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
char c;
while (i > 0 && (c = text.charAt(i - 1)) != ',' && c != ';') {
i--;
}
while (i < cursor && text.charAt(i) == ' ') {
i++;
}
return i;
}
/**
* Returns the end of the token (minus trailing punctuation)
* that begins at offset <code>cursor</code> within <code>text</code>.
* It is a method from the MultiAutoCompleteTextView.Tokenizer interface.
*/
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
char c;
while (i < len) {
if ((c = text.charAt(i)) == ',' || c == ';') {
return i;
} else {
i++;
}
}
return len;
}
/**
* Returns <code>text</code>, modified, if necessary, to ensure that
* it ends with a token terminator (for example a space or comma).
* It is a method from the MultiAutoCompleteTextView.Tokenizer interface.
*/
public CharSequence terminateToken(CharSequence text) {
int i = text.length();
while (i > 0 && text.charAt(i - 1) == ' ') {
i--;
}
char c;
if (i > 0 && ((c = text.charAt(i - 1)) == ',' || c == ';')) {
return text;
} else {
// Use the same delimiter the user just typed.
// This lets them have a mixture of commas and semicolons in their list.
String separator = mLastSeparator + " ";
if (text instanceof Spanned) {
SpannableString sp = new SpannableString(text + separator);
TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
Object.class, sp, 0);
return sp;
} else {
return text + separator;
}
}
}
public String getRawString() {
return mList.getText().toString();
}
public List<String> getNumbers() {
Spanned sp = mList.getText();
int len = sp.length();
List<String> list = new ArrayList<String>();
int start = 0;
int i = 0;
while (i < len + 1) {
char c;
if ((i == len) || ((c = sp.charAt(i)) == ',') || (c == ';')) {
if (i > start) {
list.add(getNumberAt(sp, start, i, mContext));
// calculate the recipients total length. This is so if the name contains
// commas or semis, we'll skip over the whole name to the next
// recipient, rather than parsing this single name into multiple
// recipients.
int spanLen = getSpanLength(sp, start, i, mContext);
if (spanLen > i) {
i = spanLen;
}
}
i++;
while ((i < len) && (sp.charAt(i) == ' ')) {
i++;
}
start = i;
} else {
i++;
}
}
return list;
}
}
static class RecipientContextMenuInfo implements ContextMenuInfo {
final Recipient recipient;
RecipientContextMenuInfo(Recipient r) {
recipient = r;
}
}
}

View File

@ -1,227 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import com.annimon.stream.Stream;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.contactshare.Contact.Phone;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static org.thoughtcrime.securesms.contactshare.Contact.*;
class ContactFieldAdapter extends RecyclerView.Adapter<ContactFieldAdapter.ContactFieldViewHolder> {
private final Locale locale;
private final boolean selectable;
private final List<Field> fields;
private final GlideRequests glideRequests;
public ContactFieldAdapter(@NonNull Locale locale, @NonNull GlideRequests glideRequests, boolean selectable) {
this.locale = locale;
this.glideRequests = glideRequests;
this.selectable = selectable;
this.fields = new ArrayList<>();
}
@Override
public @NonNull ContactFieldViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ContactFieldViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_selectable_contact_field, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ContactFieldViewHolder holder, int position) {
holder.bind(fields.get(position), glideRequests, selectable);
}
@Override
public void onViewRecycled(@NonNull ContactFieldViewHolder holder) {
holder.recycle();
}
@Override
public int getItemCount() {
return fields.size();
}
void setFields(@NonNull Context context,
@Nullable Avatar avatar,
@NonNull List<Phone> phoneNumbers,
@NonNull List<Email> emails,
@NonNull List<PostalAddress> postalAddresses)
{
fields.clear();
if (avatar != null) {
fields.add(new Field(avatar));
}
fields.addAll(Stream.of(phoneNumbers).map(phone -> new Field(context, phone, locale)).toList());
fields.addAll(Stream.of(emails).map(email -> new Field(context, email)).toList());
fields.addAll(Stream.of(postalAddresses).map(address -> new Field(context, address)).toList());
notifyDataSetChanged();
}
static class ContactFieldViewHolder extends RecyclerView.ViewHolder {
private final TextView value;
private final TextView label;
private final ImageView icon;
private final ImageView avatar;
private final CheckBox checkBox;
ContactFieldViewHolder(View itemView) {
super(itemView);
value = itemView.findViewById(R.id.contact_field_value);
label = itemView.findViewById(R.id.contact_field_label);
icon = itemView.findViewById(R.id.contact_field_icon);
avatar = itemView.findViewById(R.id.contact_field_avatar);
checkBox = itemView.findViewById(R.id.contact_field_checkbox);
}
void bind(@NonNull Field field, @NonNull GlideRequests glideRequests, boolean selectable) {
value.setMaxLines(field.maxLines);
value.setText(field.value);
label.setText(field.label);
icon.setImageResource(field.iconResId);
if (field.iconUri != null) {
avatar.setVisibility(View.VISIBLE);
glideRequests.load(field.iconUri).circleCrop().into(avatar);
} else {
avatar.setVisibility(View.GONE);
}
if (selectable) {
checkBox.setVisibility(View.VISIBLE);
checkBox.setOnCheckedChangeListener(null);
checkBox.setChecked(field.isSelected());
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> field.setSelected(isChecked));
} else {
checkBox.setVisibility(View.GONE);
checkBox.setOnCheckedChangeListener(null);
}
}
void recycle() {
checkBox.setOnCheckedChangeListener(null);
}
}
static class Field {
final String value;
final String label;
final int iconResId;
final int maxLines;
final Selectable selectable;
@Nullable
final Uri iconUri;
Field(@NonNull Context context, @NonNull Phone phoneNumber, @NonNull Locale locale) {
this.value = ContactUtil.getPrettyPhoneNumber(phoneNumber, locale);
this.iconResId = R.drawable.ic_call_white_24dp;
this.iconUri = null;
this.maxLines = 1;
this.selectable = phoneNumber;
switch (phoneNumber.getType()) {
case HOME:
label = context.getString(R.string.ContactShareEditActivity_type_home);
break;
case MOBILE:
label = context.getString(R.string.ContactShareEditActivity_type_mobile);
break;
case WORK:
label = context.getString(R.string.ContactShareEditActivity_type_work);
break;
case CUSTOM:
label = phoneNumber.getLabel() != null ? phoneNumber.getLabel() : "";
break;
default:
label = "";
}
}
Field(@NonNull Context context, @NonNull Email email) {
this.value = email.getEmail();
this.iconResId = R.drawable.baseline_email_white_24;
this.iconUri = null;
this.maxLines = 1;
this.selectable = email;
switch (email.getType()) {
case HOME:
label = context.getString(R.string.ContactShareEditActivity_type_home);
break;
case MOBILE:
label = context.getString(R.string.ContactShareEditActivity_type_mobile);
break;
case WORK:
label = context.getString(R.string.ContactShareEditActivity_type_work);
break;
case CUSTOM:
label = email.getLabel() != null ? email.getLabel() : "";
break;
default:
label = "";
}
}
Field(@NonNull Context context, @NonNull PostalAddress postalAddress) {
this.value = postalAddress.toString();
this.iconResId = R.drawable.ic_location_on_white_24dp;
this.iconUri = null;
this.maxLines = 3;
this.selectable = postalAddress;
switch (postalAddress.getType()) {
case HOME:
label = context.getString(R.string.ContactShareEditActivity_type_home);
break;
case WORK:
label = context.getString(R.string.ContactShareEditActivity_type_work);
break;
case CUSTOM:
label = postalAddress.getLabel() != null ? postalAddress.getLabel() : context.getString(R.string.ContactShareEditActivity_type_missing);
break;
default:
label = context.getString(R.string.ContactShareEditActivity_type_missing);
}
}
Field(@NonNull Avatar avatar) {
this.value = "";
this.iconResId = R.drawable.baseline_account_circle_white_24;
this.iconUri = avatar.getAttachment() != null ? avatar.getAttachment().getDataUri() : null;
this.maxLines = 1;
this.selectable = avatar;
this.label = "";
}
void setSelected(boolean selected) {
selectable.setSelected(selected);
}
boolean isSelected() {
return selectable.isSelected();
}
}
}

View File

@ -1,138 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import android.widget.TextView;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import static org.thoughtcrime.securesms.contactshare.Contact.*;
public class ContactNameEditActivity extends PassphraseRequiredActionBarActivity {
public static final String KEY_NAME = "name";
public static final String KEY_CONTACT_INDEX = "contact_index";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private TextView displayNameView;
private ContactNameEditViewModel viewModel;
static Intent getIntent(@NonNull Context context, @NonNull Name name, int contactPosition) {
Intent intent = new Intent(context, ContactNameEditActivity.class);
intent.putExtra(KEY_NAME, name);
intent.putExtra(KEY_CONTACT_INDEX, contactPosition);
return intent;
}
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
super.onCreate(savedInstanceState, ready);
if (getIntent() == null) {
throw new IllegalStateException("You must supply extras to this activity. Please use the #getIntent() method.");
}
Name name = getIntent().getParcelableExtra(KEY_NAME);
if (name == null) {
throw new IllegalStateException("You must supply a name to this activity. Please use the #getIntent() method.");
}
setContentView(R.layout.activity_contact_name_edit);
initializeToolbar();
initializeViews(name);
viewModel = ViewModelProviders.of(this).get(ContactNameEditViewModel.class);
viewModel.setName(name);
viewModel.getDisplayName().observe(this, displayNameView::setText);
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
private void initializeToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitle("");
toolbar.setNavigationIcon(R.drawable.ic_check_white_24dp);
toolbar.setNavigationOnClickListener(v -> {
Intent resultIntent = new Intent();
resultIntent.putExtra(KEY_NAME, viewModel.getName());
resultIntent.putExtra(KEY_CONTACT_INDEX, getIntent().getIntExtra(KEY_CONTACT_INDEX, -1));
setResult(RESULT_OK, resultIntent);
finish();
});
}
private void initializeViews(@NonNull Name name) {
displayNameView = findViewById(R.id.name_edit_display_name);
TextView givenName = findViewById(R.id.name_edit_given_name);
TextView familyName = findViewById(R.id.name_edit_family_name);
TextView middleName = findViewById(R.id.name_edit_middle_name);
TextView prefix = findViewById(R.id.name_edit_prefix);
TextView suffix = findViewById(R.id.name_edit_suffix);
givenName.setText(name.getGivenName());
familyName.setText(name.getFamilyName());
middleName.setText(name.getMiddleName());
prefix.setText(name.getPrefix());
suffix.setText(name.getSuffix());
givenName.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
viewModel.updateGivenName(text);
}
});
familyName.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
viewModel.updateFamilyName(text);
}
});
middleName.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
viewModel.updateMiddleName(text);
}
});
prefix.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
viewModel.updatePrefix(text);
}
});
suffix.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
viewModel.updateSuffix(text);
}
});
}
}

View File

@ -1,132 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import static org.thoughtcrime.securesms.contactshare.Contact.*;
public class ContactNameEditViewModel extends ViewModel {
private final MutableLiveData<String> displayName;
private String givenName;
private String familyName;
private String middleName;
private String prefix;
private String suffix;
public ContactNameEditViewModel() {
this.displayName = new MutableLiveData<>();
}
void setName(@NonNull Name name) {
givenName = name.getGivenName();
familyName = name.getFamilyName();
middleName = name.getMiddleName();
prefix = name.getPrefix();
suffix = name.getSuffix();
displayName.postValue(buildDisplayName());
}
Name getName() {
return new Name(displayName.getValue(), givenName, familyName, prefix, suffix, middleName);
}
LiveData<String> getDisplayName() {
return displayName;
}
void updateGivenName(@NonNull String givenName) {
this.givenName = givenName;
displayName.postValue(buildDisplayName());
}
void updateFamilyName(@NonNull String familyName) {
this.familyName = familyName;
displayName.postValue(buildDisplayName());
}
void updatePrefix(@NonNull String prefix) {
this.prefix = prefix;
displayName.postValue(buildDisplayName());
}
void updateSuffix(@NonNull String suffix) {
this.suffix = suffix;
displayName.postValue(buildDisplayName());
}
void updateMiddleName(@NonNull String middleName) {
this.middleName = middleName;
displayName.postValue(buildDisplayName());
}
private String buildDisplayName() {
boolean isCJKV = isCJKV(givenName) && isCJKV(middleName) && isCJKV(familyName) && isCJKV(prefix) && isCJKV(suffix);
if (isCJKV) {
return joinString(familyName, givenName, prefix, suffix, middleName);
}
return joinString(prefix, givenName, middleName, familyName, suffix);
}
private String joinString(String... values) {
StringBuilder builder = new StringBuilder();
for (String value : values) {
if (!TextUtils.isEmpty(value)) {
builder.append(value).append(' ');
}
}
return builder.toString().trim();
}
private boolean isCJKV(@Nullable String value) {
if (TextUtils.isEmpty(value)) {
return true;
}
for (int offset = 0; offset < value.length(); ) {
int codepoint = Character.codePointAt(value, offset);
if (!isCodepointCJKV(codepoint)) {
return false;
}
offset += Character.charCount(codepoint);
}
return true;
}
private boolean isCodepointCJKV(int codepoint) {
if (codepoint == (int)' ') return true;
Character.UnicodeBlock block = Character.UnicodeBlock.of(codepoint);
return Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS.equals(block) ||
Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A.equals(block) ||
Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B.equals(block) ||
Character.UnicodeBlock.CJK_COMPATIBILITY.equals(block) ||
Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS.equals(block) ||
Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS.equals(block) ||
Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT.equals(block) ||
Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT.equals(block) ||
Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION.equals(block) ||
Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS.equals(block) ||
Character.UnicodeBlock.KANGXI_RADICALS.equals(block) ||
Character.UnicodeBlock.IDEOGRAPHIC_DESCRIPTION_CHARACTERS.equals(block) ||
Character.UnicodeBlock.HIRAGANA.equals(block) ||
Character.UnicodeBlock.KATAKANA.equals(block) ||
Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS.equals(block) ||
Character.UnicodeBlock.HANGUL_JAMO.equals(block) ||
Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO.equals(block) ||
Character.UnicodeBlock.HANGUL_SYLLABLES.equals(block) ||
Character.isIdeographic(codepoint);
}
}

View File

@ -1,389 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import android.text.TextUtils;
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contactshare.Contact.Email;
import org.thoughtcrime.securesms.contactshare.Contact.Name;
import org.thoughtcrime.securesms.contactshare.Contact.Phone;
import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import ezvcard.Ezvcard;
import ezvcard.VCard;
import static org.thoughtcrime.securesms.contactshare.Contact.*;
public class ContactRepository {
private static final String TAG = ContactRepository.class.getSimpleName();
private final Context context;
private final Executor executor;
private final ContactsDatabase contactsDatabase;
ContactRepository(@NonNull Context context,
@NonNull Executor executor,
@NonNull ContactsDatabase contactsDatabase)
{
this.context = context.getApplicationContext();
this.executor = executor;
this.contactsDatabase = contactsDatabase;
}
void getContacts(@NonNull List<Uri> contactUris, @NonNull ValueCallback<List<Contact>> callback) {
executor.execute(() -> {
List<Contact> contacts = new ArrayList<>(contactUris.size());
for (Uri contactUri : contactUris) {
Contact contact;
if (ContactsContract.AUTHORITY.equals(contactUri.getAuthority())) {
contact = getContactFromSystemContacts(ContactUtil.getContactIdFromUri(contactUri));
} else {
contact = getContactFromVcard(contactUri);
}
if (contact != null) {
contacts.add(contact);
}
}
callback.onComplete(contacts);
});
}
@WorkerThread
private @Nullable Contact getContactFromSystemContacts(long contactId) {
Name name = getName(contactId);
if (name == null) {
Log.w(TAG, "Couldn't find a name associated with the provided contact ID.");
return null;
}
List<Phone> phoneNumbers = getPhoneNumbers(contactId);
AvatarInfo avatarInfo = getAvatarInfo(contactId, phoneNumbers);
Avatar avatar = avatarInfo != null ? new Avatar(avatarInfo.uri, avatarInfo.isProfile) : null;
return new Contact(name, null, phoneNumbers, getEmails(contactId), getPostalAddresses(contactId), avatar);
}
@WorkerThread
private @Nullable Contact getContactFromVcard(@NonNull Uri uri) {
Contact contact = null;
try (InputStream stream = PartAuthority.getAttachmentStream(context, uri)) {
VCard vcard = Ezvcard.parse(stream).first();
ezvcard.property.StructuredName vName = vcard.getStructuredName();
List<ezvcard.property.Telephone> vPhones = vcard.getTelephoneNumbers();
List<ezvcard.property.Email> vEmails = vcard.getEmails();
List<ezvcard.property.Address> vPostalAddresses = vcard.getAddresses();
String organization = vcard.getOrganization() != null && !vcard.getOrganization().getValues().isEmpty() ? vcard.getOrganization().getValues().get(0) : null;
String displayName = vcard.getFormattedName() != null ? vcard.getFormattedName().getValue() : null;
if (displayName == null && vName != null) {
displayName = vName.getGiven();
}
if (displayName == null && vcard.getOrganization() != null) {
displayName = organization;
}
if (displayName == null) {
throw new IOException("No valid name.");
}
Name name = new Name(displayName,
vName != null ? vName.getGiven() : null,
vName != null ? vName.getFamily() : null,
vName != null && !vName.getPrefixes().isEmpty() ? vName.getPrefixes().get(0) : null,
vName != null && !vName.getSuffixes().isEmpty() ? vName.getSuffixes().get(0) : null,
null);
List<Phone> phoneNumbers = new ArrayList<>(vPhones.size());
for (ezvcard.property.Telephone vEmail : vPhones) {
String label = !vEmail.getTypes().isEmpty() ? getCleanedVcardType(vEmail.getTypes().get(0).getValue()) : null;
phoneNumbers.add(new Phone(vEmail.getText(), phoneTypeFromVcardType(label), label));
}
List<Email> emails = new ArrayList<>(vEmails.size());
for (ezvcard.property.Email vEmail : vEmails) {
String label = !vEmail.getTypes().isEmpty() ? getCleanedVcardType(vEmail.getTypes().get(0).getValue()) : null;
emails.add(new Email(vEmail.getValue(), emailTypeFromVcardType(label), label));
}
List<PostalAddress> postalAddresses = new ArrayList<>(vPostalAddresses.size());
for (ezvcard.property.Address vPostalAddress : vPostalAddresses) {
String label = !vPostalAddress.getTypes().isEmpty() ? getCleanedVcardType(vPostalAddress.getTypes().get(0).getValue()) : null;
postalAddresses.add(new PostalAddress(postalAddressTypeFromVcardType(label),
label,
vPostalAddress.getStreetAddress(),
vPostalAddress.getPoBox(),
null,
vPostalAddress.getLocality(),
vPostalAddress.getRegion(),
vPostalAddress.getPostalCode(),
vPostalAddress.getCountry()));
}
contact = new Contact(name, organization, phoneNumbers, emails, postalAddresses, null);
} catch (IOException e) {
Log.w(TAG, "Failed to parse the vcard.", e);
}
if (BlobProvider.AUTHORITY.equals(uri.getAuthority())) {
BlobProvider.getInstance().delete(context, uri);
}
return contact;
}
@WorkerThread
private @Nullable Name getName(long contactId) {
try (Cursor cursor = contactsDatabase.getNameDetails(contactId)) {
if (cursor != null && cursor.moveToFirst()) {
String cursorDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME));
String cursorGivenName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME));
String cursorFamilyName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME));
String cursorPrefix = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.PREFIX));
String cursorSuffix = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.SUFFIX));
String cursorMiddleName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME));
Name name = new Name(cursorDisplayName, cursorGivenName, cursorFamilyName, cursorPrefix, cursorSuffix, cursorMiddleName);
if (!name.isEmpty()) {
return name;
}
}
}
String org = contactsDatabase.getOrganizationName(contactId);
if (!TextUtils.isEmpty(org)) {
return new Name(org, org, null, null, null, null);
}
return null;
}
@WorkerThread
private @NonNull List<Phone> getPhoneNumbers(long contactId) {
Map<String, Phone> numberMap = new HashMap<>();
try (Cursor cursor = contactsDatabase.getPhoneDetails(contactId)) {
while (cursor != null && cursor.moveToNext()) {
String cursorNumber = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER));
int cursorType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE));
String cursorLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL));
String number = ContactUtil.getNormalizedPhoneNumber(context, cursorNumber);
Phone existing = numberMap.get(number);
Phone candidate = new Phone(number, phoneTypeFromContactType(cursorType), cursorLabel);
if (existing == null || (existing.getType() == Phone.Type.CUSTOM && existing.getLabel() == null)) {
numberMap.put(number, candidate);
}
}
}
List<Phone> numbers = new ArrayList<>(numberMap.size());
numbers.addAll(numberMap.values());
return numbers;
}
@WorkerThread
private @NonNull List<Email> getEmails(long contactId) {
List<Email> emails = new LinkedList<>();
try (Cursor cursor = contactsDatabase.getEmailDetails(contactId)) {
while (cursor != null && cursor.moveToNext()) {
String cursorEmail = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.ADDRESS));
int cursorType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.TYPE));
String cursorLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.LABEL));
emails.add(new Email(cursorEmail, emailTypeFromContactType(cursorType), cursorLabel));
}
}
return emails;
}
@WorkerThread
private @NonNull List<PostalAddress> getPostalAddresses(long contactId) {
List<PostalAddress> postalAddresses = new LinkedList<>();
try (Cursor cursor = contactsDatabase.getPostalAddressDetails(contactId)) {
while (cursor != null && cursor.moveToNext()) {
int cursorType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.TYPE));
String cursorLabel = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.LABEL));
String cursorStreet = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.STREET));
String cursorPoBox = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.POBOX));
String cursorNeighborhood = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD));
String cursorCity = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.CITY));
String cursorRegion = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.REGION));
String cursorPostal = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE));
String cursorCountry = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY));
postalAddresses.add(new PostalAddress(postalAddressTypeFromContactType(cursorType),
cursorLabel,
cursorStreet,
cursorPoBox,
cursorNeighborhood,
cursorCity,
cursorRegion,
cursorPostal,
cursorCountry));
}
}
return postalAddresses;
}
@WorkerThread
private @Nullable AvatarInfo getAvatarInfo(long contactId, List<Phone> phoneNumbers) {
AvatarInfo systemAvatar = getSystemAvatarInfo(contactId);
if (systemAvatar != null) {
return systemAvatar;
}
for (Phone phoneNumber : phoneNumbers) {
AvatarInfo recipientAvatar = getRecipientAvatarInfo(Address.fromExternal(context, phoneNumber.getNumber()));
if (recipientAvatar != null) {
return recipientAvatar;
}
}
return null;
}
@WorkerThread
private @Nullable AvatarInfo getSystemAvatarInfo(long contactId) {
Uri uri = contactsDatabase.getAvatarUri(contactId);
if (uri != null) {
return new AvatarInfo(uri, false);
}
return null;
}
@WorkerThread
private @Nullable AvatarInfo getRecipientAvatarInfo(@NonNull Address address) {
Recipient recipient = Recipient.from(context, address, false);
ContactPhoto contactPhoto = recipient.getContactPhoto();
if (contactPhoto != null) {
Uri avatarUri = contactPhoto.getUri(context);
if (avatarUri != null) {
return new AvatarInfo(avatarUri, contactPhoto.isProfilePhoto());
}
}
return null;
}
private Phone.Type phoneTypeFromContactType(int type) {
switch (type) {
case ContactsContract.CommonDataKinds.Phone.TYPE_HOME:
return Phone.Type.HOME;
case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE:
return Phone.Type.MOBILE;
case ContactsContract.CommonDataKinds.Phone.TYPE_WORK:
return Phone.Type.WORK;
}
return Phone.Type.CUSTOM;
}
private Phone.Type phoneTypeFromVcardType(@Nullable String type) {
if ("home".equalsIgnoreCase(type)) return Phone.Type.HOME;
else if ("cell".equalsIgnoreCase(type)) return Phone.Type.MOBILE;
else if ("work".equalsIgnoreCase(type)) return Phone.Type.WORK;
else return Phone.Type.CUSTOM;
}
private Email.Type emailTypeFromContactType(int type) {
switch (type) {
case ContactsContract.CommonDataKinds.Email.TYPE_HOME:
return Email.Type.HOME;
case ContactsContract.CommonDataKinds.Email.TYPE_MOBILE:
return Email.Type.MOBILE;
case ContactsContract.CommonDataKinds.Email.TYPE_WORK:
return Email.Type.WORK;
}
return Email.Type.CUSTOM;
}
private Email.Type emailTypeFromVcardType(@Nullable String type) {
if ("home".equalsIgnoreCase(type)) return Email.Type.HOME;
else if ("cell".equalsIgnoreCase(type)) return Email.Type.MOBILE;
else if ("work".equalsIgnoreCase(type)) return Email.Type.WORK;
else return Email.Type.CUSTOM;
}
private PostalAddress.Type postalAddressTypeFromContactType(int type) {
switch (type) {
case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME:
return PostalAddress.Type.HOME;
case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK:
return PostalAddress.Type.WORK;
}
return PostalAddress.Type.CUSTOM;
}
private PostalAddress.Type postalAddressTypeFromVcardType(@Nullable String type) {
if ("home".equalsIgnoreCase(type)) return PostalAddress.Type.HOME;
else if ("work".equalsIgnoreCase(type)) return PostalAddress.Type.WORK;
else return PostalAddress.Type.CUSTOM;
}
private String getCleanedVcardType(@Nullable String type) {
if (TextUtils.isEmpty(type)) return "";
if (type.startsWith("x-") && type.length() > 2) {
return type.substring(2);
}
return type;
}
interface ValueCallback<T> {
void onComplete(@NonNull T value);
}
private static class AvatarInfo {
private final Uri uri;
private final boolean isProfile;
private AvatarInfo(Uri uri, boolean isProfile) {
this.uri = uri;
this.isProfile = isProfile;
}
public Uri getUri() {
return uri;
}
public boolean isProfile() {
return isProfile;
}
}
}

View File

@ -1,140 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import android.app.Activity;
import androidx.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.Toast;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import java.util.ArrayList;
import java.util.List;
import static org.thoughtcrime.securesms.contactshare.Contact.*;
import static org.thoughtcrime.securesms.contactshare.ContactShareEditViewModel.*;
public class ContactShareEditActivity extends PassphraseRequiredActionBarActivity implements ContactShareEditAdapter.EventListener {
public static final String KEY_CONTACTS = "contacts";
private static final String KEY_CONTACT_URIS = "contact_uris";
private static final int CODE_NAME_EDIT = 55;
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ContactShareEditViewModel viewModel;
public static Intent getIntent(@NonNull Context context, @NonNull List<Uri> contactUris) {
ArrayList<Uri> contactUriList = new ArrayList<>(contactUris);
Intent intent = new Intent(context, ContactShareEditActivity.class);
intent.putParcelableArrayListExtra(KEY_CONTACT_URIS, contactUriList);
return intent;
}
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState, boolean ready) {
setContentView(R.layout.activity_contact_share_edit);
if (getIntent() == null) {
throw new IllegalStateException("You must supply extras to this activity. Please use the #getIntent() method.");
}
List<Uri> contactUris = getIntent().getParcelableArrayListExtra(KEY_CONTACT_URIS);
if (contactUris == null) {
throw new IllegalStateException("You must supply contact Uri's to this activity. Please use the #getIntent() method.");
}
View sendButton = findViewById(R.id.contact_share_edit_send);
sendButton.setOnClickListener(v -> onSendClicked(viewModel.getFinalizedContacts()));
RecyclerView contactList = findViewById(R.id.contact_share_edit_list);
contactList.setLayoutManager(new LinearLayoutManager(this));
contactList.getLayoutManager().setAutoMeasureEnabled(true);
ContactShareEditAdapter contactAdapter = new ContactShareEditAdapter(GlideApp.with(this), dynamicLanguage.getCurrentLocale(), this);
contactList.setAdapter(contactAdapter);
ContactRepository contactRepository = new ContactRepository(this,
AsyncTask.THREAD_POOL_EXECUTOR,
DatabaseFactory.getContactsDatabase(this));
viewModel = ViewModelProviders.of(this, new Factory(contactUris, contactRepository)).get(ContactShareEditViewModel.class);
viewModel.getContacts().observe(this, contacts -> {
contactAdapter.setContacts(contacts);
contactList.post(() -> contactList.scrollToPosition(0));
});
viewModel.getEvents().observe(this, this::presentEvent);
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicTheme.onResume(this);
}
private void presentEvent(@Nullable Event event) {
if (event == null) {
return;
}
if (event == Event.BAD_CONTACT) {
Toast.makeText(this, R.string.ContactShareEditActivity_invalid_contact, Toast.LENGTH_SHORT).show();
finish();
}
}
private void onSendClicked(List<Contact> contacts) {
Intent intent = new Intent();
ArrayList<Contact> contactArrayList = new ArrayList<>(contacts.size());
contactArrayList.addAll(contacts);
intent.putExtra(KEY_CONTACTS, contactArrayList);
setResult(Activity.RESULT_OK, intent);
finish();
}
@Override
public void onNameEditClicked(int position, @NonNull Name name) {
startActivityForResult(ContactNameEditActivity.getIntent(this, name, position), CODE_NAME_EDIT);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != CODE_NAME_EDIT || resultCode != RESULT_OK || data == null) {
return;
}
int position = data.getIntExtra(ContactNameEditActivity.KEY_CONTACT_INDEX, -1);
Name name = data.getParcelableExtra(ContactNameEditActivity.KEY_NAME);
if (name != null) {
viewModel.updateContactName(position, name);
}
}
}

View File

@ -1,95 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static org.thoughtcrime.securesms.contactshare.Contact.*;
public class ContactShareEditAdapter extends RecyclerView.Adapter<ContactShareEditAdapter.ContactEditViewHolder> {
private final GlideRequests glideRequests;
private final Locale locale;
private final EventListener eventListener;
private final List<Contact> contacts;
ContactShareEditAdapter(@NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull EventListener eventListener) {
this.glideRequests = glideRequests;
this.locale = locale;
this.eventListener = eventListener;
this.contacts = new ArrayList<>();
}
@Override
public @NonNull ContactEditViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ContactEditViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_editable_contact, parent, false),
locale,
glideRequests);
}
@Override
public void onBindViewHolder(@NonNull ContactEditViewHolder holder, int position) {
holder.bind(position, contacts.get(position), eventListener);
}
@Override
public int getItemCount() {
return contacts.size();
}
void setContacts(@Nullable List<Contact> contacts) {
this.contacts.clear();
if (contacts != null) {
this.contacts.addAll(contacts);
}
notifyDataSetChanged();
}
static class ContactEditViewHolder extends RecyclerView.ViewHolder {
private final TextView name;
private final View nameEditButton;
private final ContactFieldAdapter fieldAdapter;
ContactEditViewHolder(View itemView, @NonNull Locale locale, @NonNull GlideRequests glideRequests) {
super(itemView);
this.name = itemView.findViewById(R.id.editable_contact_name);
this.nameEditButton = itemView.findViewById(R.id.editable_contact_name_edit_button);
this.fieldAdapter = new ContactFieldAdapter(locale, glideRequests, true);
RecyclerView fields = itemView.findViewById(R.id.editable_contact_fields);
fields.setLayoutManager(new LinearLayoutManager(itemView.getContext()));
fields.getLayoutManager().setAutoMeasureEnabled(true);
fields.setAdapter(fieldAdapter);
}
void bind(int position, @NonNull Contact contact, @NonNull EventListener eventListener) {
Context context = itemView.getContext();
name.setText(ContactUtil.getDisplayName(contact));
nameEditButton.setOnClickListener(v -> eventListener.onNameEditClicked(position, contact.getName()));
fieldAdapter.setFields(context, contact.getAvatar(), contact.getPhoneNumbers(), contact.getEmails(), contact.getPostalAddresses());
}
}
interface EventListener {
void onNameEditClicked(int position, @NonNull Name name);
}
}

View File

@ -1,113 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.net.Uri;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.contactshare.Contact.Name;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import java.util.ArrayList;
import java.util.List;
class ContactShareEditViewModel extends ViewModel {
private final MutableLiveData<List<Contact>> contacts;
private final SingleLiveEvent<Event> events;
private final ContactRepository repo;
ContactShareEditViewModel(@NonNull List<Uri> contactUris,
@NonNull ContactRepository contactRepository)
{
contacts = new MutableLiveData<>();
events = new SingleLiveEvent<>();
repo = contactRepository;
repo.getContacts(contactUris, retrieved -> {
if (retrieved.isEmpty()) {
events.postValue(Event.BAD_CONTACT);
} else {
contacts.postValue(retrieved);
}
});
}
@NonNull LiveData<List<Contact>> getContacts() {
return contacts;
}
@NonNull List<Contact> getFinalizedContacts() {
List<Contact> currentContacts = getCurrentContacts();
List<Contact> trimmedContacts = new ArrayList<>(currentContacts.size());
for (Contact contact : currentContacts) {
Contact trimmed = new Contact(contact.getName(),
contact.getOrganization(),
trimSelectables(contact.getPhoneNumbers()),
trimSelectables(contact.getEmails()),
trimSelectables(contact.getPostalAddresses()),
contact.getAvatar() != null && contact.getAvatar().isSelected() ? contact.getAvatar() : null);
trimmedContacts.add(trimmed);
}
return trimmedContacts;
}
@NonNull LiveData<Event> getEvents() {
return events;
}
void updateContactName(int contactPosition, @NonNull Name name) {
if (name.isEmpty()) {
events.postValue(Event.BAD_CONTACT);
return;
}
List<Contact> currentContacts = getCurrentContacts();
Contact original = currentContacts.remove(contactPosition);
currentContacts.add(new Contact(name,
original.getOrganization(),
original.getPhoneNumbers(),
original.getEmails(),
original.getPostalAddresses(),
original.getAvatar()));
contacts.postValue(currentContacts);
}
private <E extends Selectable> List<E> trimSelectables(List<E> selectables) {
return Stream.of(selectables).filter(Selectable::isSelected).toList();
}
@NonNull
private List<Contact> getCurrentContacts() {
List<Contact> currentContacts = contacts.getValue();
return currentContacts != null ? currentContacts : new ArrayList<>();
}
enum Event {
BAD_CONTACT
}
static class Factory extends ViewModelProvider.NewInstanceFactory {
private final List<Uri> contactUris;
private final ContactRepository contactRepository;
Factory(@NonNull List<Uri> contactUris, @NonNull ContactRepository contactRepository) {
this.contactUris = contactUris;
this.contactRepository = contactRepository;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return modelClass.cast(new ContactShareEditViewModel(contactUris, contactRepository));
}
}
}

View File

@ -34,16 +34,6 @@ import network.loki.messenger.R;
public final class ContactUtil {
private static final String TAG = ContactUtil.class.getSimpleName();
public static long getContactIdFromUri(@NonNull Uri uri) {
try {
return Long.parseLong(uri.getLastPathSegment());
} catch (NumberFormatException e) {
return -1;
}
}
public static @NonNull CharSequence getStringSummary(@NonNull Context context, @NonNull Contact contact) {
String contactName = ContactUtil.getDisplayName(contact);
@ -69,159 +59,4 @@ public final class ContactUtil {
return "";
}
public static @NonNull String getDisplayNumber(@NonNull Contact contact, @NonNull Locale locale) {
Phone displayNumber = getPrimaryNumber(contact);
if (displayNumber != null) {
return ContactUtil.getPrettyPhoneNumber(displayNumber, locale);
} else if (contact.getEmails().size() > 0) {
return contact.getEmails().get(0).getEmail();
} else {
return "";
}
}
private static @Nullable Phone getPrimaryNumber(@NonNull Contact contact) {
if (contact.getPhoneNumbers().size() == 0) {
return null;
}
List<Phone> mobileNumbers = Stream.of(contact.getPhoneNumbers()).filter(number -> number.getType() == Phone.Type.MOBILE).toList();
if (mobileNumbers.size() > 0) {
return mobileNumbers.get(0);
}
return contact.getPhoneNumbers().get(0);
}
public static @NonNull String getPrettyPhoneNumber(@NonNull Phone phoneNumber, @NonNull Locale fallbackLocale) {
return getPrettyPhoneNumber(phoneNumber.getNumber(), fallbackLocale);
}
private static @NonNull String getPrettyPhoneNumber(@NonNull String phoneNumber, @NonNull Locale fallbackLocale) {
return phoneNumber;
}
public static @NonNull String getNormalizedPhoneNumber(@NonNull Context context, @NonNull String number) {
Address address = Address.fromExternal(context, number);
return address.serialize();
}
@MainThread
public static void selectRecipientThroughDialog(@NonNull Context context, @NonNull List<Recipient> choices, @NonNull Locale locale, @NonNull RecipientSelectedCallback callback) {
if (choices.size() > 1) {
CharSequence[] values = new CharSequence[choices.size()];
for (int i = 0; i < values.length; i++) {
values[i] = getPrettyPhoneNumber(choices.get(i).getAddress().toPhoneString(), locale);
}
new AlertDialog.Builder(context)
.setItems(values, ((dialog, which) -> callback.onSelected(choices.get(which))))
.create()
.show();
} else {
callback.onSelected(choices.get(0));
}
}
public static List<Recipient> getRecipients(@NonNull Context context, @NonNull Contact contact) {
return Stream.of(contact.getPhoneNumbers()).map(phone -> Recipient.from(context, Address.fromExternal(context, phone.getNumber()), true)).toList();
}
@WorkerThread
public static @NonNull Intent buildAddToContactsIntent(@NonNull Context context, @NonNull Contact contact) {
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
if (!TextUtils.isEmpty(contact.getName().getDisplayName())) {
intent.putExtra(ContactsContract.Intents.Insert.NAME, contact.getName().getDisplayName());
}
if (!TextUtils.isEmpty(contact.getOrganization())) {
intent.putExtra(ContactsContract.Intents.Insert.COMPANY, contact.getOrganization());
}
if (contact.getPhoneNumbers().size() > 0) {
intent.putExtra(ContactsContract.Intents.Insert.PHONE, contact.getPhoneNumbers().get(0).getNumber());
intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE, getSystemType(contact.getPhoneNumbers().get(0).getType()));
}
if (contact.getPhoneNumbers().size() > 1) {
intent.putExtra(ContactsContract.Intents.Insert.SECONDARY_PHONE, contact.getPhoneNumbers().get(1).getNumber());
intent.putExtra(ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE, getSystemType(contact.getPhoneNumbers().get(1).getType()));
}
if (contact.getPhoneNumbers().size() > 2) {
intent.putExtra(ContactsContract.Intents.Insert.TERTIARY_PHONE, contact.getPhoneNumbers().get(2).getNumber());
intent.putExtra(ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE, getSystemType(contact.getPhoneNumbers().get(2).getType()));
}
if (contact.getEmails().size() > 0) {
intent.putExtra(ContactsContract.Intents.Insert.EMAIL, contact.getEmails().get(0).getEmail());
intent.putExtra(ContactsContract.Intents.Insert.EMAIL_TYPE, getSystemType(contact.getEmails().get(0).getType()));
}
if (contact.getEmails().size() > 1) {
intent.putExtra(ContactsContract.Intents.Insert.SECONDARY_EMAIL, contact.getEmails().get(1).getEmail());
intent.putExtra(ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE, getSystemType(contact.getEmails().get(1).getType()));
}
if (contact.getEmails().size() > 2) {
intent.putExtra(ContactsContract.Intents.Insert.TERTIARY_EMAIL, contact.getEmails().get(2).getEmail());
intent.putExtra(ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE, getSystemType(contact.getEmails().get(2).getType()));
}
if (contact.getPostalAddresses().size() > 0) {
intent.putExtra(ContactsContract.Intents.Insert.POSTAL, contact.getPostalAddresses().get(0).toString());
intent.putExtra(ContactsContract.Intents.Insert.POSTAL_TYPE, getSystemType(contact.getPostalAddresses().get(0).getType()));
}
if (contact.getAvatarAttachment() != null && contact.getAvatarAttachment().getDataUri() != null) {
try {
ContentValues values = new ContentValues();
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
values.put(ContactsContract.CommonDataKinds.Photo.PHOTO, Util.readFully(PartAuthority.getAttachmentStream(context, contact.getAvatarAttachment().getDataUri())));
ArrayList<ContentValues> valuesArray = new ArrayList<>(1);
valuesArray.add(values);
intent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, valuesArray);
} catch (IOException e) {
Log.w(TAG, "Failed to read avatar into a byte array.", e);
}
}
return intent;
}
private static int getSystemType(Phone.Type type) {
switch (type) {
case HOME: return ContactsContract.CommonDataKinds.Phone.TYPE_HOME;
case MOBILE: return ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE;
case WORK: return ContactsContract.CommonDataKinds.Phone.TYPE_WORK;
default: return ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM;
}
}
private static int getSystemType(Email.Type type) {
switch (type) {
case HOME: return ContactsContract.CommonDataKinds.Email.TYPE_HOME;
case MOBILE: return ContactsContract.CommonDataKinds.Email.TYPE_MOBILE;
case WORK: return ContactsContract.CommonDataKinds.Email.TYPE_WORK;
default: return ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM;
}
}
private static int getSystemType(PostalAddress.Type type) {
switch (type) {
case HOME: return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME;
case WORK: return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK;
default: return ContactsContract.CommonDataKinds.StructuredPostal.TYPE_CUSTOM;
}
}
public interface RecipientSelectedCallback {
void onSelected(@NonNull Recipient recipient);
}
}

View File

@ -112,11 +112,9 @@ import org.thoughtcrime.securesms.components.identity.UntrustedSendDialog;
import org.thoughtcrime.securesms.components.identity.UnverifiedBannerView;
import org.thoughtcrime.securesms.components.identity.UnverifiedSendDialog;
import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactShareEditActivity;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
@ -138,7 +136,6 @@ import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
@ -282,7 +279,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private static final int PICK_DOCUMENT = 2;
private static final int PICK_AUDIO = 3;
private static final int PICK_CONTACT = 4;
private static final int GET_CONTACT_DETAILS = 5;
// private static final int GET_CONTACT_DETAILS = 5;
// private static final int GROUP_EDIT = 6;
private static final int TAKE_PHOTO = 7;
private static final int ADD_CONTACT = 8;
@ -303,7 +300,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private Button unblockButton;
private Button makeDefaultSmsButton;
private InputAwareLayout container;
protected Stub<ReminderView> reminderView;
private Stub<UnverifiedBannerView> unverifiedBannerView;
private Stub<GroupShareProfileView> groupShareProfileView;
private TypingStatusTextWatcher typingTextWatcher;
@ -613,14 +609,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
break;
case PICK_CONTACT:
if (isSecureText && !isSmsForced()) {
openContactShareEditor(data.getData());
// openContactShareEditor(data.getData());
} else {
addAttachmentContactInfo(data.getData());
}
break;
case GET_CONTACT_DETAILS:
sendSharedContact(data.getParcelableArrayListExtra(ContactShareEditActivity.KEY_CONTACTS));
break;
case TAKE_PHOTO:
if (attachmentManager.getCaptureUri() != null) {
setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE);
@ -887,11 +880,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inputPanel.onKeyboardShown();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(ReminderUpdateEvent event) {
updateReminders(recipient.hasSeenInviteReminder());
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
@ -1487,14 +1475,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
protected void updateReminders(boolean seenInvite) {
Log.i(TAG, "updateReminders(" + seenInvite + ")");
if (reminderView.resolved()) {
reminderView.get().hide();
}
}
private void updateSessionRestoreBanner() {
Set<String> devices = DatabaseFactory.getLokiThreadDatabase(this).getSessionRestoreDevices(threadId);
if (devices.size() > 0) {
@ -1592,7 +1572,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
unblockButton = ViewUtil.findById(this, R.id.unblock_button);
makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button);
container = ViewUtil.findById(this, R.id.layout_container);
reminderView = ViewUtil.findStubById(this, R.id.reminder_stub);
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
groupShareProfileView = ViewUtil.findStubById(this, R.id.group_share_profile_view_stub);
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
@ -1807,7 +1786,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateInputUI(recipient, isSecureText, isDefaultSms);
setActionBarColor(recipient.getColor());
setGroupShareProfileReminder(recipient);
updateReminders(recipient.hasSeenInviteReminder());
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
initializeSecurity(isSecureText, isDefaultSms);
@ -1906,7 +1884,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
if (MediaType.VCARD.equals(mediaType) && isSecureText) {
openContactShareEditor(uri);
return new SettableFuture<>(false);
} else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) {
Media media = new Media(uri, MediaUtil.getMimeType(this, uri), 0, width, height, 0, Optional.absent(), Optional.absent());
@ -1917,11 +1894,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void openContactShareEditor(Uri contactUri) {
Intent intent = ContactShareEditActivity.getIntent(this, Collections.singletonList(contactUri));
startActivityForResult(intent, GET_CONTACT_DETAILS);
}
private void addAttachmentContactInfo(Uri contactUri) {
ContactAccessor contactDataList = ContactAccessor.getInstance();
ContactData contactData = contactDataList.getContactData(this, contactUri);

View File

@ -117,11 +117,4 @@ public class ConversationPopupActivity extends ConversationActivity {
super.sendComplete(threadId);
finish();
}
@Override
protected void updateReminders(boolean seenInvite) {
if (reminderView.resolved()) {
reminderView.get().setVisibility(View.GONE);
}
}
}

View File

@ -37,7 +37,6 @@ public class ConversationSearchViewModel extends AndroidViewModel {
debouncer = new Debouncer(500);
searchRepository = new SearchRepository(context,
DatabaseFactory.getSearchDatabase(context),
DatabaseFactory.getContactsDatabase(context),
DatabaseFactory.getThreadDatabase(context),
ContactAccessor.getInstance(),
SignalExecutors.SERIAL);

View File

@ -1,154 +0,0 @@
package org.thoughtcrime.securesms.conversation;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import network.loki.messenger.R;
public class ConversationTitleView extends RelativeLayout {
@SuppressWarnings("unused")
private static final String TAG = ConversationTitleView.class.getSimpleName();
private View content;
private AvatarImageView avatar;
private TextView title;
private TextView subtitle;
private ImageView verified;
private View subtitleContainer;
public ConversationTitleView(Context context) {
this(context, null);
}
public ConversationTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.content = ViewUtil.findById(this, R.id.content);
this.title = ViewUtil.findById(this, R.id.title);
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
this.verified = ViewUtil.findById(this, R.id.verified_indicator);
this.subtitleContainer = ViewUtil.findById(this, R.id.subtitle_container);
this.avatar = ViewUtil.findById(this, R.id.contact_photo_image);
this.avatar.setEnabled(false);
ViewUtil.setTextViewGravityStart(this.title, getContext());
ViewUtil.setTextViewGravityStart(this.subtitle, getContext());
}
public void setTitle(@NonNull GlideRequests glideRequests, @Nullable Recipient recipient) {
if (recipient == null) setComposeTitle();
else setRecipientTitle(recipient);
if (recipient != null && recipient.isBlocked()) {
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_block_white_18dp, 0, 0, 0);
} else if (recipient != null && recipient.isMuted()) {
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off_white_18dp, 0, 0, 0);
} else {
title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
if (recipient != null) {
this.avatar.setAvatar(glideRequests, recipient, false);
}
}
public void setVerified(boolean verified) {
this.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
}
@Override
public void setOnClickListener(@Nullable OnClickListener listener) {
this.content.setOnClickListener(listener);
this.avatar.setOnClickListener(listener);
}
@Override
public void setOnLongClickListener(@Nullable OnLongClickListener listener) {
this.content.setOnLongClickListener(listener);
this.avatar.setOnLongClickListener(listener);
}
private void setComposeTitle() {
this.title.setText(R.string.ConversationActivity_compose_message);
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
}
private void setRecipientTitle(Recipient recipient) {
if (recipient.isGroupRecipient()) setGroupRecipientTitle(recipient);
else if (recipient.isLocalNumber()) setSelfTitle();
else if (TextUtils.isEmpty(recipient.getName())) setNonContactRecipientTitle(recipient);
else setContactRecipientTitle(recipient);
}
private void setGroupRecipientTitle(Recipient recipient) {
String localNumber = TextSecurePreferences.getLocalNumber(getContext());
this.title.setText(recipient.getName());
this.subtitle.setText(Stream.of(recipient.getParticipants())
.filter(r -> !r.getAddress().serialize().equals(localNumber))
.map(Recipient::toShortString)
.collect(Collectors.joining(", ")));
this.subtitle.setVisibility(View.GONE);
this.subtitleContainer.setVisibility(VISIBLE);
}
private void setSelfTitle() {
this.title.setText(R.string.note_to_self);
this.subtitleContainer.setVisibility(View.GONE);
}
@SuppressLint("SetTextI18n")
private void setNonContactRecipientTitle(Recipient recipient) {
this.title.setText(recipient.getAddress().serialize());
this.subtitleContainer.setVisibility(VISIBLE);
if (TextUtils.isEmpty(recipient.getProfileName())) {
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
} else {
this.subtitle.setText("~" + recipient.getProfileName());
this.subtitle.setVisibility(View.VISIBLE);
}
}
private void setContactRecipientTitle(Recipient recipient) {
this.title.setText(recipient.getName());
if (TextUtils.isEmpty(recipient.getCustomLabel())) {
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
this.subtitleContainer.setVisibility(View.GONE);
} else {
this.subtitle.setText(recipient.getCustomLabel());
this.subtitle.setVisibility(View.VISIBLE);
this.subtitleContainer.setVisibility(View.VISIBLE);
}
}
}

View File

@ -22,7 +22,6 @@ import androidx.annotation.NonNull;
import net.sqlcipher.database.SQLiteDatabase;
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
@ -59,7 +58,6 @@ public class DatabaseFactory {
private final PushDatabase pushDatabase;
private final GroupDatabase groupDatabase;
private final RecipientDatabase recipientDatabase;
private final ContactsDatabase contactsDatabase;
private final GroupReceiptDatabase groupReceiptDatabase;
private final OneTimePreKeyDatabase preKeyDatabase;
private final SignedPreKeyDatabase signedPreKeyDatabase;
@ -131,10 +129,6 @@ public class DatabaseFactory {
return getInstance(context).recipientDatabase;
}
public static ContactsDatabase getContactsDatabase(Context context) {
return getInstance(context).contactsDatabase;
}
public static GroupReceiptDatabase getGroupReceiptDatabase(Context context) {
return getInstance(context).groupReceiptDatabase;
}
@ -225,7 +219,6 @@ public class DatabaseFactory {
this.groupDatabase = new GroupDatabase(context, databaseHelper);
this.recipientDatabase = new RecipientDatabase(context, databaseHelper);
this.groupReceiptDatabase = new GroupReceiptDatabase(context, databaseHelper);
this.contactsDatabase = new ContactsDatabase(context);
this.preKeyDatabase = new OneTimePreKeyDatabase(context, databaseHelper);
this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper);
this.sessionDatabase = new SessionDatabase(context, databaseHelper);

View File

@ -199,7 +199,6 @@ public class RecipientDatabase extends Database {
int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE));
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1;
int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID));
int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES));
int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED));
@ -238,8 +237,8 @@ public class RecipientDatabase extends Database {
VibrateState.fromId(messageVibrateState),
VibrateState.fromId(callVibrateState),
Util.uri(messageRingtone), Util.uri(callRingtone),
color, seenInviteReminder,
defaultSubscriptionId, expireMessages,
color,
defaultSubscriptionId, expireMessages,
RegisteredState.fromId(registeredState),
profileKey, systemDisplayName, systemContactPhoto,
systemPhoneLabel, systemContactUri,
@ -326,13 +325,6 @@ public class RecipientDatabase extends Database {
recipient.resolve().setMuted(until);
}
public void setSeenInviteReminder(@NonNull Recipient recipient, @SuppressWarnings("SameParameterValue") boolean seen) {
ContentValues values = new ContentValues(1);
values.put(SEEN_INVITE_REMINDER, seen ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setHasSeenInviteReminder(seen);
}
public void setExpireMessages(@NonNull Recipient recipient, int expiration) {
recipient.setExpireMessages(expiration);
@ -553,7 +545,6 @@ public class RecipientDatabase extends Database {
private final Uri messageRingtone;
private final Uri callRingtone;
private final MaterialColor color;
private final boolean seenInviteReminder;
private final int defaultSubscriptionId;
private final int expireMessages;
private final RegisteredState registered;
@ -575,10 +566,9 @@ public class RecipientDatabase extends Database {
@Nullable Uri messageRingtone,
@Nullable Uri callRingtone,
@Nullable MaterialColor color,
boolean seenInviteReminder,
int defaultSubscriptionId,
int expireMessages,
@NonNull RegisteredState registered,
@NonNull RegisteredState registered,
@Nullable byte[] profileKey,
@Nullable String systemDisplayName,
@Nullable String systemContactPhoto,
@ -598,7 +588,6 @@ public class RecipientDatabase extends Database {
this.messageRingtone = messageRingtone;
this.callRingtone = callRingtone;
this.color = color;
this.seenInviteReminder = seenInviteReminder;
this.defaultSubscriptionId = defaultSubscriptionId;
this.expireMessages = expireMessages;
this.registered = registered;
@ -643,10 +632,6 @@ public class RecipientDatabase extends Database {
return callRingtone;
}
public boolean hasSeenInviteReminder() {
return seenInviteReminder;
}
public Optional<Integer> getDefaultSubscriptionId() {
return defaultSubscriptionId != -1 ? Optional.of(defaultSubscriptionId) : Optional.absent();
}

View File

@ -2,13 +2,19 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import org.greenrobot.eventbus.EventBus;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.util.CredentialsProvider;
import org.session.libsignal.service.api.util.SleepTimer;
import org.session.libsignal.service.api.util.UptimeSleepTimer;
import org.session.libsignal.service.api.websocket.ConnectivityListener;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
@ -55,14 +61,6 @@ import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository;
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
import org.thoughtcrime.securesms.util.RealtimeSleepTimer;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.SignalServiceMessageSender;
import org.session.libsignal.service.api.util.CredentialsProvider;
import org.session.libsignal.service.api.util.SleepTimer;
import org.session.libsignal.service.api.util.UptimeSleepTimer;
import org.session.libsignal.service.api.websocket.ConnectivityListener;
import dagger.Module;
import dagger.Provides;
@ -232,7 +230,6 @@ public class SignalCommunicationModule {
public void onAuthenticationFailure() {
Log.w(TAG, "onAuthenticationFailure()");
TextSecurePreferences.setUnauthorizedReceived(context, true);
EventBus.getDefault().post(new ReminderUpdateEvent());
}
}

View File

@ -1,619 +0,0 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: DeviceName.proto
package org.thoughtcrime.securesms.devicelist;
public final class DeviceNameProtos {
private DeviceNameProtos() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
}
public interface DeviceNameOrBuilder
extends com.google.protobuf.MessageOrBuilder {
// optional bytes ephemeralPublic = 1;
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
boolean hasEphemeralPublic();
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
com.google.protobuf.ByteString getEphemeralPublic();
// optional bytes syntheticIv = 2;
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
boolean hasSyntheticIv();
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
com.google.protobuf.ByteString getSyntheticIv();
// optional bytes ciphertext = 3;
/**
* <code>optional bytes ciphertext = 3;</code>
*/
boolean hasCiphertext();
/**
* <code>optional bytes ciphertext = 3;</code>
*/
com.google.protobuf.ByteString getCiphertext();
}
/**
* Protobuf type {@code signalservice.DeviceName}
*/
public static final class DeviceName extends
com.google.protobuf.GeneratedMessage
implements DeviceNameOrBuilder {
// Use DeviceName.newBuilder() to construct.
private DeviceName(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
super(builder);
this.unknownFields = builder.getUnknownFields();
}
private DeviceName(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
private static final DeviceName defaultInstance;
public static DeviceName getDefaultInstance() {
return defaultInstance;
}
public DeviceName getDefaultInstanceForType() {
return defaultInstance;
}
private final com.google.protobuf.UnknownFieldSet unknownFields;
@java.lang.Override
public final com.google.protobuf.UnknownFieldSet
getUnknownFields() {
return this.unknownFields;
}
private DeviceName(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
initFields();
int mutable_bitField0_ = 0;
com.google.protobuf.UnknownFieldSet.Builder unknownFields =
com.google.protobuf.UnknownFieldSet.newBuilder();
try {
boolean done = false;
while (!done) {
int tag = input.readTag();
switch (tag) {
case 0:
done = true;
break;
default: {
if (!parseUnknownField(input, unknownFields,
extensionRegistry, tag)) {
done = true;
}
break;
}
case 10: {
bitField0_ |= 0x00000001;
ephemeralPublic_ = input.readBytes();
break;
}
case 18: {
bitField0_ |= 0x00000002;
syntheticIv_ = input.readBytes();
break;
}
case 26: {
bitField0_ |= 0x00000004;
ciphertext_ = input.readBytes();
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
throw e.setUnfinishedMessage(this);
} catch (java.io.IOException e) {
throw new com.google.protobuf.InvalidProtocolBufferException(
e.getMessage()).setUnfinishedMessage(this);
} finally {
this.unknownFields = unknownFields.build();
makeExtensionsImmutable();
}
}
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.thoughtcrime.securesms.devicelist.DeviceNameProtos.internal_static_signalservice_DeviceName_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.thoughtcrime.securesms.devicelist.DeviceNameProtos.internal_static_signalservice_DeviceName_fieldAccessorTable
.ensureFieldAccessorsInitialized(
org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName.class, org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName.Builder.class);
}
public static com.google.protobuf.Parser<DeviceName> PARSER =
new com.google.protobuf.AbstractParser<DeviceName>() {
public DeviceName parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return new DeviceName(input, extensionRegistry);
}
};
@java.lang.Override
public com.google.protobuf.Parser<DeviceName> getParserForType() {
return PARSER;
}
private int bitField0_;
// optional bytes ephemeralPublic = 1;
public static final int EPHEMERALPUBLIC_FIELD_NUMBER = 1;
private com.google.protobuf.ByteString ephemeralPublic_;
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
public boolean hasEphemeralPublic() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
public com.google.protobuf.ByteString getEphemeralPublic() {
return ephemeralPublic_;
}
// optional bytes syntheticIv = 2;
public static final int SYNTHETICIV_FIELD_NUMBER = 2;
private com.google.protobuf.ByteString syntheticIv_;
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
public boolean hasSyntheticIv() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
public com.google.protobuf.ByteString getSyntheticIv() {
return syntheticIv_;
}
// optional bytes ciphertext = 3;
public static final int CIPHERTEXT_FIELD_NUMBER = 3;
private com.google.protobuf.ByteString ciphertext_;
/**
* <code>optional bytes ciphertext = 3;</code>
*/
public boolean hasCiphertext() {
return ((bitField0_ & 0x00000004) == 0x00000004);
}
/**
* <code>optional bytes ciphertext = 3;</code>
*/
public com.google.protobuf.ByteString getCiphertext() {
return ciphertext_;
}
private void initFields() {
ephemeralPublic_ = com.google.protobuf.ByteString.EMPTY;
syntheticIv_ = com.google.protobuf.ByteString.EMPTY;
ciphertext_ = com.google.protobuf.ByteString.EMPTY;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
byte isInitialized = memoizedIsInitialized;
if (isInitialized != -1) return isInitialized == 1;
memoizedIsInitialized = 1;
return true;
}
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
getSerializedSize();
if (((bitField0_ & 0x00000001) == 0x00000001)) {
output.writeBytes(1, ephemeralPublic_);
}
if (((bitField0_ & 0x00000002) == 0x00000002)) {
output.writeBytes(2, syntheticIv_);
}
if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeBytes(3, ciphertext_);
}
getUnknownFields().writeTo(output);
}
private int memoizedSerializedSize = -1;
public int getSerializedSize() {
int size = memoizedSerializedSize;
if (size != -1) return size;
size = 0;
if (((bitField0_ & 0x00000001) == 0x00000001)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(1, ephemeralPublic_);
}
if (((bitField0_ & 0x00000002) == 0x00000002)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(2, syntheticIv_);
}
if (((bitField0_ & 0x00000004) == 0x00000004)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, ciphertext_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
}
private static final long serialVersionUID = 0L;
@java.lang.Override
protected java.lang.Object writeReplace()
throws java.io.ObjectStreamException {
return super.writeReplace();
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(
com.google.protobuf.ByteString data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseDelimitedFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input, extensionRegistry);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parseFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static Builder newBuilder() { return Builder.create(); }
public Builder newBuilderForType() { return newBuilder(); }
public static Builder newBuilder(org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName prototype) {
return newBuilder().mergeFrom(prototype);
}
public Builder toBuilder() { return newBuilder(this); }
@java.lang.Override
protected Builder newBuilderForType(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
Builder builder = new Builder(parent);
return builder;
}
/**
* Protobuf type {@code signalservice.DeviceName}
*/
public static final class Builder extends
com.google.protobuf.GeneratedMessage.Builder<Builder>
implements org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceNameOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.thoughtcrime.securesms.devicelist.DeviceNameProtos.internal_static_signalservice_DeviceName_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.thoughtcrime.securesms.devicelist.DeviceNameProtos.internal_static_signalservice_DeviceName_fieldAccessorTable
.ensureFieldAccessorsInitialized(
org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName.class, org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName.Builder.class);
}
// Construct using org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName.newBuilder()
private Builder() {
maybeForceBuilderInitialization();
}
private Builder(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
}
}
private static Builder create() {
return new Builder();
}
public Builder clear() {
super.clear();
ephemeralPublic_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000001);
syntheticIv_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000002);
ciphertext_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000004);
return this;
}
public Builder clone() {
return create().mergeFrom(buildPartial());
}
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return org.thoughtcrime.securesms.devicelist.DeviceNameProtos.internal_static_signalservice_DeviceName_descriptor;
}
public org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName getDefaultInstanceForType() {
return org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName.getDefaultInstance();
}
public org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName build() {
org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
public org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName buildPartial() {
org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName result = new org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName(this);
int from_bitField0_ = bitField0_;
int to_bitField0_ = 0;
if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
to_bitField0_ |= 0x00000001;
}
result.ephemeralPublic_ = ephemeralPublic_;
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
to_bitField0_ |= 0x00000002;
}
result.syntheticIv_ = syntheticIv_;
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
to_bitField0_ |= 0x00000004;
}
result.ciphertext_ = ciphertext_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
}
public Builder mergeFrom(com.google.protobuf.Message other) {
if (other instanceof org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName) {
return mergeFrom((org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName)other);
} else {
super.mergeFrom(other);
return this;
}
}
public Builder mergeFrom(org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName other) {
if (other == org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName.getDefaultInstance()) return this;
if (other.hasEphemeralPublic()) {
setEphemeralPublic(other.getEphemeralPublic());
}
if (other.hasSyntheticIv()) {
setSyntheticIv(other.getSyntheticIv());
}
if (other.hasCiphertext()) {
setCiphertext(other.getCiphertext());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
public final boolean isInitialized() {
return true;
}
public Builder mergeFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName parsedMessage = null;
try {
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
parsedMessage = (org.thoughtcrime.securesms.devicelist.DeviceNameProtos.DeviceName) e.getUnfinishedMessage();
throw e;
} finally {
if (parsedMessage != null) {
mergeFrom(parsedMessage);
}
}
return this;
}
private int bitField0_;
// optional bytes ephemeralPublic = 1;
private com.google.protobuf.ByteString ephemeralPublic_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
public boolean hasEphemeralPublic() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
public com.google.protobuf.ByteString getEphemeralPublic() {
return ephemeralPublic_;
}
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
public Builder setEphemeralPublic(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000001;
ephemeralPublic_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes ephemeralPublic = 1;</code>
*/
public Builder clearEphemeralPublic() {
bitField0_ = (bitField0_ & ~0x00000001);
ephemeralPublic_ = getDefaultInstance().getEphemeralPublic();
onChanged();
return this;
}
// optional bytes syntheticIv = 2;
private com.google.protobuf.ByteString syntheticIv_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
public boolean hasSyntheticIv() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
public com.google.protobuf.ByteString getSyntheticIv() {
return syntheticIv_;
}
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
public Builder setSyntheticIv(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000002;
syntheticIv_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes syntheticIv = 2;</code>
*/
public Builder clearSyntheticIv() {
bitField0_ = (bitField0_ & ~0x00000002);
syntheticIv_ = getDefaultInstance().getSyntheticIv();
onChanged();
return this;
}
// optional bytes ciphertext = 3;
private com.google.protobuf.ByteString ciphertext_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes ciphertext = 3;</code>
*/
public boolean hasCiphertext() {
return ((bitField0_ & 0x00000004) == 0x00000004);
}
/**
* <code>optional bytes ciphertext = 3;</code>
*/
public com.google.protobuf.ByteString getCiphertext() {
return ciphertext_;
}
/**
* <code>optional bytes ciphertext = 3;</code>
*/
public Builder setCiphertext(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000004;
ciphertext_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes ciphertext = 3;</code>
*/
public Builder clearCiphertext() {
bitField0_ = (bitField0_ & ~0x00000004);
ciphertext_ = getDefaultInstance().getCiphertext();
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:signalservice.DeviceName)
}
static {
defaultInstance = new DeviceName(true);
defaultInstance.initFields();
}
// @@protoc_insertion_point(class_scope:signalservice.DeviceName)
}
private static com.google.protobuf.Descriptors.Descriptor
internal_static_signalservice_DeviceName_descriptor;
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_signalservice_DeviceName_fieldAccessorTable;
public static com.google.protobuf.Descriptors.FileDescriptor
getDescriptor() {
return descriptor;
}
private static com.google.protobuf.Descriptors.FileDescriptor
descriptor;
static {
java.lang.String[] descriptorData = {
"\n\020DeviceName.proto\022\rsignalservice\"N\n\nDev" +
"iceName\022\027\n\017ephemeralPublic\030\001 \001(\014\022\023\n\013synt" +
"heticIv\030\002 \001(\014\022\022\n\nciphertext\030\003 \001(\014B9\n%org" +
".thoughtcrime.securesms.devicelistB\020Devi" +
"ceNameProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
public com.google.protobuf.ExtensionRegistry assignDescriptors(
com.google.protobuf.Descriptors.FileDescriptor root) {
descriptor = root;
internal_static_signalservice_DeviceName_descriptor =
getDescriptor().getMessageTypes().get(0);
internal_static_signalservice_DeviceName_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_signalservice_DeviceName_descriptor,
new java.lang.String[] { "EphemeralPublic", "SyntheticIv", "Ciphertext", });
return null;
}
};
com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
}, assigner);
}
// @@protoc_insertion_point(outer_class_scope)
}

View File

@ -1,51 +0,0 @@
package org.thoughtcrime.securesms.events;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.recipients.Recipient;
public class RedPhoneEvent {
public enum Type {
CALL_CONNECTED,
WAITING_FOR_RESPONDER,
SERVER_FAILURE,
PERFORMING_HANDSHAKE,
HANDSHAKE_FAILED,
CONNECTING_TO_INITIATOR,
CALL_DISCONNECTED,
CALL_RINGING,
SERVER_MESSAGE,
RECIPIENT_UNAVAILABLE,
INCOMING_CALL,
OUTGOING_CALL,
CALL_BUSY,
LOGIN_FAILED,
CLIENT_FAILURE,
DEBUG_INFO,
NO_SUCH_USER
}
private final @NonNull Type type;
private final @NonNull Recipient recipient;
private final @Nullable String extra;
public RedPhoneEvent(@NonNull Type type, @NonNull Recipient recipient, @Nullable String extra) {
this.type = type;
this.recipient = recipient;
this.extra = extra;
}
public @NonNull Type getType() {
return type;
}
public @NonNull Recipient getRecipient() {
return recipient;
}
public @Nullable String getExtra() {
return extra;
}
}

View File

@ -1,5 +0,0 @@
package org.thoughtcrime.securesms.events;
public class ReminderUpdateEvent {
}

View File

@ -52,11 +52,10 @@ public class RefreshAttributesJob extends BaseJob implements InjectableType {
public void onRun() throws IOException {
int registrationId = TextSecurePreferences.getLocalRegistrationId(context);
boolean fetchesMessages = TextSecurePreferences.isFcmDisabled(context);
String pin = TextSecurePreferences.getRegistrationLockPin(context);
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context);
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
signalAccountManager.setAccountAttributes(null, registrationId, fetchesMessages, pin,
signalAccountManager.setAccountAttributes(null, registrationId, fetchesMessages, "",
unidentifiedAccessKey, universalUnidentifiedAccess);
ApplicationContext.getInstance(context)

View File

@ -1,236 +0,0 @@
package org.thoughtcrime.securesms.lock;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import android.text.Editable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
import org.thoughtcrime.securesms.logging.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import java.io.IOException;
public class RegistrationLockDialog {
private static final String TAG = RegistrationLockDialog.class.getSimpleName();
public static void showReminderIfNecessary(@NonNull Context context) {
if (!RegistrationLockReminders.needsReminder(context)) return;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
AlertDialog dialog = new AlertDialog.Builder(context, R.style.Theme_TextSecure_Dialog_Rationale)
.setView(R.layout.registration_lock_reminder_view)
.setCancelable(true)
.setOnCancelListener(d -> RegistrationLockReminders.scheduleReminder(context, false))
.create();
WindowManager windowManager = ServiceUtil.getWindowManager(context);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
dialog.show();
dialog.getWindow().setLayout((int)(metrics.widthPixels * .80), ViewGroup.LayoutParams.WRAP_CONTENT);
EditText pinEditText = dialog.findViewById(R.id.pin);
TextView reminder = dialog.findViewById(R.id.reminder);
assert pinEditText != null;
assert reminder != null;
SpannableString reminderIntro = new SpannableString(context.getString(R.string.RegistrationLockDialog_reminder));
SpannableString reminderText = new SpannableString(context.getString(R.string.RegistrationLockDialog_registration_lock_is_enabled_for_your_phone_number));
SpannableString forgotText = new SpannableString(context.getString(R.string.RegistrationLockDialog_i_forgot_my_pin));
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
dialog.dismiss();
new AlertDialog.Builder(context).setTitle(R.string.RegistrationLockDialog_forgotten_pin)
.setMessage(R.string.RegistrationLockDialog_registration_lock_helps_protect_your_phone_number_from_unauthorized_registration_attempts)
.setPositiveButton(android.R.string.ok, null)
.create()
.show();
}
};
reminderIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, reminderIntro.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
forgotText.setSpan(clickableSpan, 0, forgotText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
reminder.setText(new SpannableStringBuilder(reminderIntro).append(" ").append(reminderText).append(" ").append(forgotText));
reminder.setMovementMethod(LinkMovementMethod.getInstance());
pinEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if (s != null && s.toString().replace(" ", "").equals(TextSecurePreferences.getRegistrationLockPin(context))) {
dialog.dismiss();
RegistrationLockReminders.scheduleReminder(context, true);
}
}
});
}
@SuppressLint("StaticFieldLeak")
public static void showRegistrationLockPrompt(@NonNull Context context, @NonNull SwitchPreferenceCompat preference, @NonNull SignalServiceAccountManager accountManager) {
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(R.string.RegistrationLockDialog_registration_lock)
.setView(R.layout.registration_lock_dialog_view)
.setPositiveButton(R.string.RegistrationLockDialog_enable, null)
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.setOnShowListener(created -> {
Button button = ((AlertDialog) created).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(v -> {
EditText pin = dialog.findViewById(R.id.pin);
EditText repeat = dialog.findViewById(R.id.repeat);
ProgressBar progressBar = dialog.findViewById(R.id.progress);
assert pin != null;
assert repeat != null;
assert progressBar != null;
String pinValue = pin.getText().toString().replace(" ", "");
String repeatValue = repeat.getText().toString().replace(" ", "");
if (pinValue.length() < 4) {
Toast.makeText(context, R.string.RegistrationLockDialog_the_registration_lock_pin_must_be_at_least_four_digits, Toast.LENGTH_LONG).show();
return;
}
if (!pinValue.equals(repeatValue)) {
Toast.makeText(context, R.string.RegistrationLockDialog_the_two_pins_you_entered_do_not_match, Toast.LENGTH_LONG).show();
return;
}
new AsyncTask<Void, Void, Boolean>() {
@Override
protected void onPreExecute() {
progressBar.setVisibility(View.VISIBLE);
progressBar.setIndeterminate(true);
button.setEnabled(false);
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
accountManager.setPin(Optional.of(pinValue));
TextSecurePreferences.setRegistrationLockPin(context, pinValue);
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
return true;
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
}
@Override
protected void onPostExecute(@NonNull Boolean result) {
button.setEnabled(true);
progressBar.setVisibility(View.GONE);
if (result) {
preference.setChecked(true);
created.dismiss();
} else {
Toast.makeText(context, R.string.RegistrationLockDialog_error_connecting_to_the_service, Toast.LENGTH_LONG).show();
}
}
}.execute();
});
});
dialog.show();
}
@SuppressLint("StaticFieldLeak")
public static void showRegistrationUnlockPrompt(@NonNull Context context, @NonNull SwitchPreferenceCompat preference, @NonNull SignalServiceAccountManager accountManager) {
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(R.string.RegistrationLockDialog_disable_registration_lock_pin)
.setView(R.layout.registration_unlock_dialog_view)
.setPositiveButton(R.string.RegistrationLockDialog_disable, null)
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.setOnShowListener(created -> {
Button button = ((AlertDialog) created).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(v -> {
ProgressBar progressBar = dialog.findViewById(R.id.progress);
assert progressBar != null;
new AsyncTask<Void, Void, Boolean>() {
@Override
protected void onPreExecute() {
progressBar.setVisibility(View.VISIBLE);
progressBar.setIndeterminate(true);
button.setEnabled(false);
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
accountManager.setPin(Optional.absent());
return true;
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
progressBar.setVisibility(View.GONE);
button.setEnabled(true);
if (result) {
preference.setChecked(false);
created.dismiss();
} else {
Toast.makeText(context, R.string.RegistrationLockDialog_error_connecting_to_the_service, Toast.LENGTH_LONG).show();
}
}
}.execute();
});
});
dialog.show();
}
}

View File

@ -1,51 +0,0 @@
package org.thoughtcrime.securesms.lock;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
public class RegistrationLockReminders {
private static final NavigableSet<Long> INTERVALS = new TreeSet<Long>() {{
add(TimeUnit.HOURS.toMillis(6));
add(TimeUnit.HOURS.toMillis(12));
add(TimeUnit.DAYS.toMillis(1));
add(TimeUnit.DAYS.toMillis(3));
add(TimeUnit.DAYS.toMillis(7));
}};
public static final long INITIAL_INTERVAL = INTERVALS.first();
public static boolean needsReminder(@NonNull Context context) {
if (!TextSecurePreferences.isRegistrationtLockEnabled(context)) return false;
long lastReminderTime = TextSecurePreferences.getRegistrationLockLastReminderTime(context);
long nextIntervalTime = TextSecurePreferences.getRegistrationLockNextReminderInterval(context);
return System.currentTimeMillis() > lastReminderTime + nextIntervalTime;
}
public static void scheduleReminder(@NonNull Context context, boolean success) {
Long nextReminderInterval;
if (success) {
long timeSinceLastReminder = System.currentTimeMillis() - TextSecurePreferences.getRegistrationLockLastReminderTime(context);
nextReminderInterval = INTERVALS.higher(timeSinceLastReminder);
if (nextReminderInterval == null) nextReminderInterval = INTERVALS.last();
} else {
long lastReminderInterval = TextSecurePreferences.getRegistrationLockNextReminderInterval(context);
nextReminderInterval = INTERVALS.lower(lastReminderInterval);
if (nextReminderInterval == null) nextReminderInterval = INTERVALS.first();
}
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, nextReminderInterval);
}
}

View File

@ -219,17 +219,17 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off);
if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) {
if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
return context.getString(privacySummaryResId, offRes, onRes);
} else {
// if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
// return context.getString(privacySummaryResId, offRes, onRes);
// } else {
return context.getString(privacySummaryResId, offRes, offRes);
}
// }
} else {
if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
return context.getString(privacySummaryResId, onRes, onRes);
} else {
// if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
// return context.getString(privacySummaryResId, onRes, onRes);
// } else {
return context.getString(privacySummaryResId, onRes, offRes);
}
// }
}
}

View File

@ -94,7 +94,6 @@ public class Recipient implements RecipientModifiedListener {
private @NonNull RegisteredState registered = RegisteredState.UNKNOWN;
private @Nullable MaterialColor color;
private boolean seenInviteReminder;
private @Nullable byte[] profileKey;
private @Nullable String profileName;
private @Nullable String profileAvatar;
@ -151,7 +150,6 @@ public class Recipient implements RecipientModifiedListener {
this.messageVibrate = stale.messageVibrate;
this.callVibrate = stale.callVibrate;
this.expireMessages = stale.expireMessages;
this.seenInviteReminder = stale.seenInviteReminder;
this.defaultSubscriptionId = stale.defaultSubscriptionId;
this.registered = stale.registered;
this.notificationChannel = stale.notificationChannel;
@ -179,7 +177,6 @@ public class Recipient implements RecipientModifiedListener {
this.messageVibrate = details.get().messageVibrateState;
this.callVibrate = details.get().callVibrateState;
this.expireMessages = details.get().expireMessages;
this.seenInviteReminder = details.get().seenInviteReminder;
this.defaultSubscriptionId = details.get().defaultSubscriptionId;
this.registered = details.get().registered;
this.notificationChannel = details.get().notificationChannel;
@ -213,7 +210,6 @@ public class Recipient implements RecipientModifiedListener {
Recipient.this.messageVibrate = result.messageVibrateState;
Recipient.this.callVibrate = result.callVibrateState;
Recipient.this.expireMessages = result.expireMessages;
Recipient.this.seenInviteReminder = result.seenInviteReminder;
Recipient.this.defaultSubscriptionId = result.defaultSubscriptionId;
Recipient.this.registered = result.registered;
Recipient.this.notificationChannel = result.notificationChannel;
@ -263,7 +259,6 @@ public class Recipient implements RecipientModifiedListener {
this.messageVibrate = details.messageVibrateState;
this.callVibrate = details.callVibrateState;
this.expireMessages = details.expireMessages;
this.seenInviteReminder = details.seenInviteReminder;
this.defaultSubscriptionId = details.defaultSubscriptionId;
this.registered = details.registered;
this.notificationChannel = details.notificationChannel;
@ -615,18 +610,6 @@ public class Recipient implements RecipientModifiedListener {
notifyListeners();
}
public synchronized boolean hasSeenInviteReminder() {
return seenInviteReminder;
}
public void setHasSeenInviteReminder(boolean value) {
synchronized (this) {
this.seenInviteReminder = value;
}
notifyListeners();
}
public synchronized RegisteredState getRegistered() {
if (isPushGroupRecipient()) return RegisteredState.REGISTERED;
else if (isMmsGroupRecipient()) return RegisteredState.NOT_REGISTERED;

View File

@ -175,7 +175,6 @@ class RecipientProvider {
final int expireMessages;
@NonNull final List<Recipient> participants;
@Nullable final String profileName;
final boolean seenInviteReminder;
final Optional<Integer> defaultSubscriptionId;
@NonNull final RegisteredState registered;
@Nullable final byte[] profileKey;
@ -205,7 +204,6 @@ class RecipientProvider {
this.expireMessages = settings != null ? settings.getExpireMessages() : 0;
this.participants = participants == null ? new LinkedList<>() : participants;
this.profileName = settings != null ? settings.getProfileName() : null;
this.seenInviteReminder = settings != null && settings.hasSeenInviteReminder();
this.defaultSubscriptionId = settings != null ? settings.getDefaultSubscriptionId() : Optional.absent();
this.registered = settings != null ? settings.getRegistered() : RegisteredState.UNKNOWN;
this.profileKey = settings != null ? settings.getProfileKey() : null;

View File

@ -9,7 +9,6 @@ import android.text.TextUtils;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactsDatabase;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.CursorList;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
@ -53,21 +52,18 @@ public class SearchRepository {
private final Context context;
private final SearchDatabase searchDatabase;
private final ContactsDatabase contactsDatabase;
private final ThreadDatabase threadDatabase;
private final ContactAccessor contactAccessor;
private final Executor executor;
public SearchRepository(@NonNull Context context,
@NonNull SearchDatabase searchDatabase,
@NonNull ContactsDatabase contactsDatabase,
@NonNull ThreadDatabase threadDatabase,
@NonNull ContactAccessor contactAccessor,
@NonNull Executor executor)
{
this.context = context.getApplicationContext();
this.searchDatabase = searchDatabase;
this.contactsDatabase = contactsDatabase;
this.threadDatabase = threadDatabase;
this.contactAccessor = contactAccessor;
this.executor = executor;

View File

@ -15,7 +15,6 @@ import androidx.core.app.NotificationCompat;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.backup.BackupProtos;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
import org.session.libsignal.libsignal.util.Medium;
@ -153,11 +152,6 @@ public class TextSecurePreferences {
public static final String SCREEN_LOCK = "pref_android_screen_lock";
public static final String SCREEN_LOCK_TIMEOUT = "pref_android_screen_lock_timeout";
public static final String REGISTRATION_LOCK_PREF = "pref_registration_lock";
private static final String REGISTRATION_LOCK_PIN_PREF = "pref_registration_lock_pin";
private static final String REGISTRATION_LOCK_LAST_REMINDER_TIME = "pref_registration_lock_last_reminder_time";
private static final String REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL = "pref_registration_lock_next_reminder_interval";
private static final String LAST_FULL_CONTACT_SYNC_TIME = "pref_last_full_contact_sync_time";
private static final String NEEDS_FULL_CONTACT_SYNC = "pref_needs_full_contact_sync";
@ -232,38 +226,6 @@ public class TextSecurePreferences {
setLongPreference(context, SCREEN_LOCK_TIMEOUT, value);
}
public static boolean isRegistrationtLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, REGISTRATION_LOCK_PREF, false);
}
public static void setRegistrationtLockEnabled(@NonNull Context context, boolean value) {
setBooleanPreference(context, REGISTRATION_LOCK_PREF, value);
}
public static @Nullable String getRegistrationLockPin(@NonNull Context context) {
return getStringPreference(context, REGISTRATION_LOCK_PIN_PREF, null);
}
public static void setRegistrationLockPin(@NonNull Context context, String pin) {
setStringPreference(context, REGISTRATION_LOCK_PIN_PREF, pin);
}
public static long getRegistrationLockLastReminderTime(@NonNull Context context) {
return getLongPreference(context, REGISTRATION_LOCK_LAST_REMINDER_TIME, 0);
}
public static void setRegistrationLockLastReminderTime(@NonNull Context context, long time) {
setLongPreference(context, REGISTRATION_LOCK_LAST_REMINDER_TIME, time);
}
public static long getRegistrationLockNextReminderInterval(@NonNull Context context) {
return getLongPreference(context, REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL, RegistrationLockReminders.INITIAL_INTERVAL);
}
public static void setRegistrationLockNextReminderInterval(@NonNull Context context, long value) {
setLongPreference(context, REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL, value);
}
public static void setBackupPassphrase(@NonNull Context context, @Nullable String passphrase) {
setStringPreference(context, BACKUP_PASSPHRASE, passphrase);
}

View File

@ -1,15 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package signalservice;
option java_package = "org.thoughtcrime.securesms.devicelist";
option java_outer_classname = "DeviceNameProtos";
message DeviceName {
optional bytes ephemeralPublic = 1;
optional bytes syntheticIv = 2;
optional bytes ciphertext = 3;
}

View File

@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="?attr/actionBarStyle"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/name_edit_display_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:textSize="20sp"
tools:text="Peter Parker"/>
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/name_edit_prefix"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/ContactNameEditActivity_prefix"/>
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/name_edit_given_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/ContactNameEditActivity_given_name"/>
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/name_edit_middle_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/ContactNameEditActivity_middle_name"/>
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/name_edit_family_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/ContactNameEditActivity_family_name"/>
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/name_edit_suffix"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/ContactNameEditActivity_suffix"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@ -153,13 +153,6 @@
android:inflatedId="@+id/unverified_banner"
android:layout="@layout/conversation_activity_unverified_banner_stub" />
<ViewStub
android:id="@+id/reminder_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/reminder"
android:layout="@layout/conversation_activity_reminderview_stub" />
<FrameLayout
android:id="@+id/fragment_content"
android:layout_width="match_parent"

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.reminder.ReminderView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.SharedContactView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/shared_contact_view"
android:layout_width="@dimen/media_bubble_default_dimens"
android:layout_height="wrap_content"
app:contact_titleColor="?conversation_item_received_text_primary_color"
app:contact_captionColor="?conversation_item_received_text_secondary_color"
app:contact_footerIconColor="?conversation_item_received_text_secondary_color"
app:contact_footerAlpha="0.7"/>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.SharedContactView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/shared_contact_view"
android:layout_width="@dimen/media_bubble_default_dimens"
android:layout_height="wrap_content"
app:contact_titleColor="?conversation_item_sent_text_primary_color"
app:contact_captionColor="?conversation_item_sent_text_secondary_color"
app:contact_footerIconColor="?conversation_item_sent_icon_color"/>

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.conversation.ConversationTitleView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/conversation_title_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_gravity="center_vertical"
android:gravity="center_vertical">
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/contact_photo_image"
android:foreground="@drawable/contact_photo_background"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginEnd="10dp"
android:cropToPadding="true"
android:transitionName="contact_photo"
android:clickable="true"
app:inverted="true"
tools:src="@drawable/ic_contact_picture"
android:contentDescription="@string/conversation_list_item_view__contact_photo_image"/>
<LinearLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_toEndOf="@id/contact_photo_image"
android:layout_centerVertical="true">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textSize="18dp"
android:transitionName="recipient_name"
android:drawablePadding="5dp"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
style="@style/TextSecure.TitleTextStyle"
tools:text="Contact name"
tools:ignore="UnusedAttribute"/>
<LinearLayout
android:id="@+id/subtitle_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/verified_indicator"
android:src="@drawable/ic_check_circle_white_18dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="3dp"
android:layout_gravity="bottom"
android:alpha="0.7"
android:visibility="gone"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:layout_gravity="center_vertical|start"
android:gravity="center_vertical"
android:textDirection="ltr"
android:textSize="13dp"
tools:text="(123) 123-1234"
style="@style/TextSecure.SubtitleTextStyle"/>
</LinearLayout>
</LinearLayout>
</org.thoughtcrime.securesms.conversation.ConversationTitleView>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recipients_panel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<org.thoughtcrime.securesms.contacts.RecipientsEditor android:id="@+id/recipients_text"
android:layout_height="wrap_content"
android:capitalize="sentences"
android:autoText="true"
android:singleLine="true"
android:hint="@string/recipients_panel__to"
android:paddingEnd="45dp"
android:textColor="?conversation_editor_text_color"
android:layout_width="fill_parent"/>
<ImageButton android:id="@+id/contacts_button"
android:background="#00000000"
android:layout_width="40dp"
android:layout_height="35dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="4dp"
android:src="@drawable/ic_menu_add_field_holo_light"
android:layout_alignEnd="@id/recipients_text"
android:maxWidth="32dip"
android:maxHeight="32dip" />
</RelativeLayout>

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout android:id="@+id/header_container"
android:background="@color/signal_primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="40dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="18sp"
android:gravity="center_horizontal"
android:text="@string/registration_lock_reminder_view__enter_your_registration_lock_pin"/>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="40dp"
android:paddingStart="80dp"
android:paddingEnd="80dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/pin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberPassword"
android:hint="@string/registration_lock_reminder_view__enter_pin"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView android:id="@+id/reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="40dp"
android:paddingBottom="40dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="15sp"
android:lineSpacingMultiplier="1.3"
tools:text="Reminder: Registration Lock is enabled for your phone number. To help you memorize your Registration Lock PIN, Signal will periodically ask you to confirm it. I forgot my PIN."/>
</LinearLayout>

View File

@ -246,13 +246,6 @@
<attr name="quote_colorSecondary" format="color" />
</declare-styleable>
<declare-styleable name="SharedContactView">
<attr name="contact_titleColor" format="color" />
<attr name="contact_captionColor" format="color" />
<attr name="contact_footerIconColor" format="color" />
<attr name="contact_footerAlpha" format="float" />
</declare-styleable>
<declare-styleable name="LinkPreviewView">
<attr name="linkpreview_type" format="enum">
<enum name="conversation" value="0" />