mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 19:38:45 +00:00
Unused code cleanup.
This commit is contained in:
parent
12804a30c0
commit
5e3cb706c4
@ -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"
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.thoughtcrime.securesms.events;
|
||||
|
||||
|
||||
public class ReminderUpdateEvent {
|
||||
}
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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>
|
@ -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"
|
||||
|
@ -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" />
|
@ -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"/>
|
@ -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"/>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user