Unused code cleanup.

This commit is contained in:
Anton Chekulaev 2020-11-30 17:43:06 +11:00
parent 9bf3540d22
commit 4fb4709ec2
65 changed files with 10 additions and 11027 deletions

View File

@ -139,13 +139,6 @@ dependencies {
def canonicalVersionCode = 121
def canonicalVersionName = "1.6.4"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
'arm64-v8a' : 2,
'x86' : 3,
'x86_64' : 4,
'universal' : 5]
android {
flavorDimensions "none"
compileSdkVersion androidCompileSdkVersion
@ -157,7 +150,7 @@ android {
}
defaultConfig {
versionCode canonicalVersionCode * postFixSize
versionCode canonicalVersionCode
versionName canonicalVersionName
minSdkVersion androidMinSdkVersion
@ -182,20 +175,7 @@ android {
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resConfigs autoResConfig()
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
}
compileOptions {
@ -267,12 +247,7 @@ android {
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
def abiName = output.getFilter("ABI") ?: 'universal'
def postFix = abiPostFix.get(abiName, 0)
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
output.versionCodeOverride = canonicalVersionCode
}
}

View File

@ -176,41 +176,14 @@
android:name="org.thoughtcrime.securesms.loki.activities.LinkedDevicesActivity"
android:screenOrientation="portrait" />
<!-- Session -->
<activity
android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|fontScale"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name="org.thoughtcrime.securesms.CountrySelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity
android:name="org.thoughtcrime.securesms.InviteActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName="org.thoughtcrime.securesms.loki.activities.HomeActivity"
android:windowSoftInputMode="stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="PsiClass:HomeActivity" />
</activity>
<activity
android:name="org.thoughtcrime.securesms.PromptMmsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name="org.thoughtcrime.securesms.DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tsdevice" />
</intent-filter>
</activity>
<activity
android:name="org.thoughtcrime.securesms.preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
@ -263,7 +236,7 @@
</activity-alias>
<activity
android:name=".stickers.StickerPackPreviewActivity"
android:name="org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:noHistory="true"
@ -280,16 +253,6 @@
android:scheme="sgnl" />
</intent-filter>
</activity>
<activity
android:name="org.thoughtcrime.securesms.ConversationListArchiveActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:label="@string/AndroidManifest_archived_conversations"
android:launchMode="singleTask"
android:parentActivityName="org.thoughtcrime.securesms.loki.activities.HomeActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="PsiClass:HomeActivity" />
</activity>
<activity
android:name="org.thoughtcrime.securesms.conversation.ConversationActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -300,7 +263,7 @@
android:windowSoftInputMode="stateUnchanged">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="PsiClass:HomeActivity" />
android:value="org.thoughtcrime.securesms.loki.activities.HomeActivity" />
</activity>
<activity
android:name="org.thoughtcrime.securesms.longmessage.LongMessageActivity"
@ -351,11 +314,6 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:theme="@style/Theme.Session.DayNight.NoActionBar"/>
<activity
android:name="org.thoughtcrime.securesms.NewConversationActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Theme.Session.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible" />
<activity
android:name="org.thoughtcrime.securesms.PushContactSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -389,26 +347,6 @@
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
</intent-filter>
</activity>
<activity
android:name="org.thoughtcrime.securesms.registration.WelcomeActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name="org.thoughtcrime.securesms.RegistrationActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name="org.thoughtcrime.securesms.registration.CaptchaActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:launchMode="singleTask"
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name="org.thoughtcrime.securesms.DeviceActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:label="@string/AndroidManifest__linked_devices" />
<activity
android:name="org.thoughtcrime.securesms.stickers.StickerManagementActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -445,41 +383,6 @@
android:noHistory="true"
android:stateNotNeeded="true"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name="org.thoughtcrime.securesms.SmsSendtoActivity">
<intent-filter>
<action android:name="android.intent.action.SENDTO" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact" />
</intent-filter>
</activity>
<activity
android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:theme="@style/NoAnimation.Theme.BlackScreen">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" />
</intent-filter>
</activity>
<activity
android:name="org.thoughtcrime.securesms.RecipientPreferenceActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@ -518,10 +421,6 @@
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.contactshare.SharedContactDetailsActivity"
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"
@ -535,9 +434,6 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name="org.thoughtcrime.securesms.service.WebRtcCallService"
android:enabled="true" />
<service
android:name="org.thoughtcrime.securesms.service.ApplicationMigrationService"
android:enabled="true" />
@ -659,11 +555,6 @@
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.notifications.MessageNotifier$ReminderReceiver">
<intent-filter>
<action android:name="network.loki.securesms.MessageNotifier.REMINDER_ACTION" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.notifications.DeleteNotificationReceiver">
<intent-filter>
<action android:name="network.loki.securesms.DELETE_NOTIFICATION" />

View File

@ -38,9 +38,5 @@ public interface BindableConversationItem extends Unbindable {
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms);
void onStickerClicked(@NonNull StickerLocator stickerLocator);
void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView);
void onAddToContactsClicked(@NonNull Contact contact);
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
}
}

View File

@ -1,331 +0,0 @@
/*
* Copyright (C) 2014-2017 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;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Outline;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.widget.TooltipCompat;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.components.RatingManager;
import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
import org.thoughtcrime.securesms.loki.utilities.ProfilePictureModifiedEvent;
import org.thoughtcrime.securesms.loki.activities.JoinPublicChatActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.search.SearchFragment;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.util.List;
import network.loki.messenger.R;
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
implements ConversationListFragment.ConversationSelectedListener
{
@SuppressWarnings("unused")
private static final String TAG = ConversationListActivity.class.getSimpleName();
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ConversationListFragment conversationListFragment;
private SearchFragment searchFragment;
private SearchToolbar searchToolbar;
private ImageView searchAction;
private ViewGroup fragmentContainer;
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle icicle, boolean ready) {
setContentView(R.layout.conversation_list_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
searchToolbar = findViewById(R.id.search_toolbar);
searchAction = findViewById(R.id.search_action);
fragmentContainer = findViewById(R.id.fragment_container);
conversationListFragment = initFragment(R.id.fragment_container, new ConversationListFragment(), dynamicLanguage.getCurrentLocale());
initializeSearchListener();
RatingManager.showRatingDialogIfNecessary(this);
RegistrationLockDialog.showReminderIfNecessary(this);
TooltipCompat.setTooltipText(searchAction, getText(R.string.SearchToolbar_search_for_conversations_contacts_and_messages));
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
SimpleTask.run(getLifecycle(), () -> {
return Recipient.from(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), false);
}, this::initializeProfileIcon);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = this.getMenuInflater();
menu.clear();
inflater.inflate(R.menu.text_secure_normal, menu);
// menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(this));
super.onPrepareOptionsMenu(menu);
return true;
}
private void initializeSearchListener() {
searchAction.setOnClickListener(v -> {
/* Loki - We don't need contact permissions
Permissions.with(this)
.request(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)
.ifNecessary()
.onAllGranted(() -> searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
searchAction.getY() + (searchAction.getHeight() / 2)))
.withPermanentDenialDialog(getString(R.string.ConversationListActivity_signal_needs_contacts_permission_in_order_to_search_your_contacts_but_it_has_been_permanently_denied))
.execute();
*/
});
searchToolbar.setListener(new SearchToolbar.SearchListener() {
@Override
public void onSearchTextChange(String text) {
String trimmed = text.trim();
if (trimmed.length() > 0) {
if (searchFragment == null) {
searchFragment = SearchFragment.newInstance(dynamicLanguage.getCurrentLocale());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, searchFragment, null)
.commit();
}
searchFragment.updateSearchQuery(trimmed);
} else if (searchFragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(searchFragment)
.commit();
searchFragment = null;
}
}
@Override
public void onSearchClosed() {
if (searchFragment != null) {
getSupportFragmentManager().beginTransaction()
.remove(searchFragment)
.commit();
searchFragment = null;
}
}
});
}
private void initializeProfileIcon(@NonNull Recipient recipient) {
ImageView profilePictureImageView = findViewById(R.id.toolbar_icon);
profilePictureImageView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, view.getWidth(), view.getHeight());
}
});
profilePictureImageView.setClipToOutline(true);
// Display the correct identicon if we're a secondary device
String primaryAddress = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
String profileAddress = (recipient.isLocalNumber() && primaryAddress != null) ? primaryAddress : recipient.getAddress().serialize();
Recipient primaryRecipient = Recipient.from(this, Address.fromSerialized(profileAddress), false);
Drawable fallback = primaryRecipient.getFallbackContactPhotoDrawable(this, false);
GlideApp.with(this)
.load(primaryRecipient.getContactPhoto())
.fallback(fallback)
.error(fallback)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.circleCrop()
.into(profilePictureImageView);
profilePictureImageView.setOnClickListener(v -> handleDisplaySettings());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
// case R.id.menu_new_group: createGroup(); return true;
// case R.id.menu_settings: handleDisplaySettings(); return true;
// case R.id.menu_clear_passphrase: handleClearPassphrase(); return true;
// case R.id.menu_mark_all_read: handleMarkAllRead(); return true;
// case R.id.menu_invite: handleInvite(); return true;
// case R.id.menu_help: handleHelp(); return true;
case R.id.menu_conversation_list_add_public_chat_option: addNewPublicChat(); return true;
}
return false;
}
@Override
public void onCreateConversation(long threadId, Recipient recipient, int distributionType, long lastSeen) {
openConversation(threadId, recipient, distributionType, lastSeen, -1);
}
public void openConversation(long threadId, Recipient recipient, int distributionType, long lastSeen, int startingPosition) {
searchToolbar.clearFocus();
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis());
intent.putExtra(ConversationActivity.LAST_SEEN_EXTRA, lastSeen);
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, startingPosition);
startActivity(intent);
overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
}
@Override
public void onSwitchToArchive() {
Intent intent = new Intent(this, ConversationListArchiveActivity.class);
startActivity(intent);
}
@Override
public void onBackPressed() {
if (searchToolbar.isVisible()) searchToolbar.collapse();
else super.onBackPressed();
}
private void createGroup() {
Intent intent = new Intent(this, GroupCreateActivity.class);
startActivity(intent);
}
private void handleDisplaySettings() {
Intent preferencesIntent = new Intent(this, ApplicationPreferencesActivity.class);
startActivity(preferencesIntent);
}
private void handleClearPassphrase() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(KeyCachingService.CLEAR_KEY_ACTION);
startService(intent);
}
@SuppressLint("StaticFieldLeak")
private void handleMarkAllRead() {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
Context context = ConversationListActivity.this;
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setAllThreadsRead();
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void handleInvite() {
startActivity(new Intent(this, InviteActivity.class));
}
private void handleHelp() {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://support.signal.org")));
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.ConversationListActivity_there_is_no_browser_installed_on_your_device, Toast.LENGTH_LONG).show();
}
}
private void addNewPublicChat() {
startActivity(new Intent(this, JoinPublicChatActivity.class));
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onAvatarModified(ProfilePictureModifiedEvent event) {
Recipient recipient = event.getRecipient();
if (recipient.isLocalNumber() || recipient.isUserMasterDevice()) {
initializeProfileIcon(recipient);
}
}
}

View File

@ -1,74 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import network.loki.messenger.R;
public class ConversationListArchiveActivity extends PassphraseRequiredActionBarActivity
implements ConversationListFragment.ConversationSelectedListener
{
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle icicle, boolean ready) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.AndroidManifest_archived_conversations);
Bundle bundle = new Bundle();
bundle.putBoolean(ConversationListFragment.ARCHIVE, true);
initFragment(android.R.id.content, new ConversationListFragment(), dynamicLanguage.getCurrentLocale(), bundle);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.home: super.onBackPressed(); return true;
}
return false;
}
@Override
public void onCreateConversation(long threadId, Recipient recipient, int distributionType, long lastSeenTime) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.IS_ARCHIVED_EXTRA, true);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
intent.putExtra(ConversationActivity.LAST_SEEN_EXTRA, lastSeenTime);
startActivity(intent);
overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out);
}
@Override
public void onSwitchToArchive() {
throw new AssertionError();
}
}

View File

@ -1,605 +0,0 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener;
import org.thoughtcrime.securesms.components.recyclerview.DeleteItemAnimator;
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
import org.thoughtcrime.securesms.components.reminder.DefaultSmsReminder;
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.OutdatedBuildReminder;
import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.loki.activities.CreatePrivateChatActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import network.loki.messenger.R;
public class ConversationListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>, ActionMode.Callback, ItemClickListener
{
public static final String ARCHIVE = "archive";
@SuppressWarnings("unused")
private static final String TAG = ConversationListFragment.class.getSimpleName();
private static final int[] EMPTY_IMAGES = new int[] { R.drawable.empty_inbox_1,
R.drawable.empty_inbox_2,
R.drawable.empty_inbox_3,
R.drawable.empty_inbox_4,
R.drawable.empty_inbox_5 };
private ActionMode actionMode;
private RecyclerView list;
private ReminderView reminderView;
private View emptyState;
private ImageView emptyImage;
private TextView emptySearch;
private PulsingFloatingActionButton fab;
private Locale locale;
private String queryFilter = "";
private boolean archive;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA);
archive = getArguments().getBoolean(ARCHIVE, false);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
final View view = inflater.inflate(R.layout.conversation_list_fragment, container, false);
reminderView = ViewUtil.findById(view, R.id.reminder);
list = ViewUtil.findById(view, R.id.list);
fab = ViewUtil.findById(view, R.id.fab);
emptyState = ViewUtil.findById(view, R.id.empty_state);
emptyImage = ViewUtil.findById(view, R.id.empty);
emptySearch = ViewUtil.findById(view, R.id.empty_search);
if (archive) fab.hide();
else fab.show();
reminderView.setOnDismissListener(() -> updateReminders(true));
list.setHasFixedSize(true);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setItemAnimator(new DeleteItemAnimator());
new ItemTouchHelper(new ArchiveListenerCallback()).attachToRecyclerView(list);
return view;
}
@Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
setHasOptionsMenu(true);
fab.setOnClickListener(v -> startActivity(new Intent(getActivity(), CreatePrivateChatActivity.class)));
initializeListAdapter();
initializeTypingObserver();
}
@Override
public void onResume() {
super.onResume();
updateReminders(true);
list.getAdapter().notifyDataSetChanged();
EventBus.getDefault().register(this);
}
@Override
public void onPause() {
super.onPause();
fab.stopPulse();
EventBus.getDefault().unregister(this);
}
public ConversationListAdapter getListAdapter() {
return (ConversationListAdapter) list.getAdapter();
}
public void setQueryFilter(String query) {
this.queryFilter = query;
getLoaderManager().restartLoader(0, null, this);
}
public void resetQueryFilter() {
if (!TextUtils.isEmpty(this.queryFilter)) {
setQueryFilter("");
}
}
@SuppressLint("StaticFieldLeak")
private void updateReminders(boolean hide) {
new AsyncTask<Context, Void, Optional<? extends Reminder>>() {
@Override
protected Optional<? extends Reminder> doInBackground(Context... params) {
final Context context = params[0];
if (UnauthorizedReminder.isEligible(context)) {
return Optional.of(new UnauthorizedReminder(context));
} else if (ExpiredBuildReminder.isEligible()) {
return Optional.of(new ExpiredBuildReminder(context));
} else if (ServiceOutageReminder.isEligible(context)) {
ApplicationContext.getInstance(context).getJobManager().add(new ServiceOutageDetectionJob());
return Optional.of(new ServiceOutageReminder(context));
} else if (OutdatedBuildReminder.isEligible()) {
return Optional.of(new OutdatedBuildReminder(context));
} else if (DefaultSmsReminder.isEligible(context)) {
return Optional.of(new DefaultSmsReminder(context));
} else if (Util.isDefaultSmsProvider(context) && SystemSmsImportReminder.isEligible(context)) {
return Optional.of((new SystemSmsImportReminder(context)));
} else if (PushRegistrationReminder.isEligible(context)) {
return Optional.of((new PushRegistrationReminder(context)));
} else if (ShareReminder.isEligible(context)) {
return Optional.of(new ShareReminder(context));
} else if (DozeReminder.isEligible(context)) {
return Optional.of(new DozeReminder(context));
} else {
return Optional.absent();
}
}
@Override
protected void onPostExecute(Optional<? extends Reminder> reminder) {
if (reminder.isPresent() && getActivity() != null && !isRemoving()) {
reminderView.showReminder(reminder.get());
} else if (!reminder.isPresent()) {
reminderView.hide();
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, getActivity());
}
private void initializeListAdapter() {
list.setAdapter(new ConversationListAdapter(getActivity(), GlideApp.with(this), locale, null, this));
getLoaderManager().restartLoader(0, null, this);
}
private void initializeTypingObserver() {
ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypingThreads().observe(this, threadIds -> {
if (threadIds == null) {
threadIds = Collections.emptySet();
}
getListAdapter().setTypingThreads(threadIds);
});
}
@SuppressLint("StaticFieldLeak")
private void handleArchiveAllSelected() {
final Set<Long> selectedConversations = new HashSet<>(getListAdapter().getBatchSelections());
final boolean archive = this.archive;
int snackBarTitleId;
if (archive) snackBarTitleId = R.plurals.ConversationListFragment_moved_conversations_to_inbox;
else snackBarTitleId = R.plurals.ConversationListFragment_conversations_archived;
int count = selectedConversations.size();
String snackBarTitle = getResources().getQuantityString(snackBarTitleId, count, count);
new SnackbarAsyncTask<Void>(getView(), snackBarTitle,
getString(R.string.ConversationListFragment_undo),
getResources().getColor(R.color.amber_500),
Snackbar.LENGTH_LONG, true)
{
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
@Override
protected void executeAction(@Nullable Void parameter) {
for (long threadId : selectedConversations) {
if (!archive) DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
else DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
}
}
@Override
protected void reverseAction(@Nullable Void parameter) {
for (long threadId : selectedConversations) {
if (!archive) DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
else DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@SuppressLint("StaticFieldLeak")
private void handleDeleteAllSelected() {
int conversationsCount = getListAdapter().getBatchSelections().size();
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
alert.setIconAttribute(R.attr.dialog_alert_icon);
alert.setTitle(getActivity().getResources().getQuantityString(R.plurals.ConversationListFragment_delete_selected_conversations,
conversationsCount, conversationsCount));
alert.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationListFragment_this_will_permanently_delete_all_n_selected_conversations,
conversationsCount, conversationsCount));
alert.setCancelable(true);
alert.setPositiveButton(R.string.delete, (dialog, which) -> {
final Set<Long> selectedConversations = (getListAdapter())
.getBatchSelections();
if (!selectedConversations.isEmpty()) {
new AsyncTask<Void, Void, Void>() {
private ProgressDialog dialog;
@Override
protected void onPreExecute() {
dialog = ProgressDialog.show(getActivity(),
getActivity().getString(R.string.ConversationListFragment_deleting),
getActivity().getString(R.string.ConversationListFragment_deleting_selected_conversations),
true, false);
}
@Override
protected Void doInBackground(Void... params) {
Context context = getActivity();
DatabaseFactory.getThreadDatabase(context).deleteConversations(selectedConversations);
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
return null;
}
@Override
protected void onPostExecute(Void result) {
dialog.dismiss();
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
alert.setNegativeButton(android.R.string.cancel, null);
alert.show();
}
private void handleSelectAllThreads() {
getListAdapter().selectAllThreads();
actionMode.setTitle(String.valueOf(getListAdapter().getBatchSelections().size()));
}
private void handleCreateConversation(long threadId, Recipient recipient, int distributionType, long lastSeen) {
((ConversationSelectedListener)getActivity()).onCreateConversation(threadId, recipient, distributionType, lastSeen);
}
@Override
public @NonNull Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new ConversationListLoader(getActivity(), queryFilter, archive);
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> arg0, Cursor cursor) {
if ((cursor == null || cursor.getCount() <= 0) && TextUtils.isEmpty(queryFilter) && !archive) {
list.setVisibility(View.INVISIBLE);
emptyState.setVisibility(View.VISIBLE);
emptySearch.setVisibility(View.INVISIBLE);
emptyImage.setImageResource(EMPTY_IMAGES[(int) (Math.random() * EMPTY_IMAGES.length)]);
fab.startPulse(3 * 1000);
} else if ((cursor == null || cursor.getCount() <= 0) && !TextUtils.isEmpty(queryFilter)) {
list.setVisibility(View.INVISIBLE);
emptyState.setVisibility(View.GONE);
emptySearch.setVisibility(View.VISIBLE);
emptySearch.setText(getString(R.string.ConversationListFragment_no_results_found_for_s_, queryFilter));
} else {
list.setVisibility(View.VISIBLE);
emptyState.setVisibility(View.GONE);
emptySearch.setVisibility(View.INVISIBLE);
fab.stopPulse();
}
getListAdapter().changeCursor(cursor);
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> arg0) {
getListAdapter().changeCursor(null);
}
@Override
public void onItemClick(ConversationListItem item) {
if (actionMode == null) {
handleCreateConversation(item.getThreadId(), item.getRecipient(),
item.getDistributionType(), item.getLastSeen());
} else {
ConversationListAdapter adapter = (ConversationListAdapter)list.getAdapter();
adapter.toggleThreadInBatchSet(item.getThreadId());
if (adapter.getBatchSelections().size() == 0) {
actionMode.finish();
} else {
actionMode.setTitle(String.valueOf(getListAdapter().getBatchSelections().size()));
}
adapter.notifyDataSetChanged();
}
}
@Override
public void onItemLongClick(ConversationListItem item) {
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(ConversationListFragment.this);
getListAdapter().initializeBatchMode(true);
getListAdapter().toggleThreadInBatchSet(item.getThreadId());
getListAdapter().notifyDataSetChanged();
}
@Override
public void onSwitchToArchive() {
((ConversationSelectedListener)getActivity()).onSwitchToArchive();
}
public interface ConversationSelectedListener {
void onCreateConversation(long threadId, Recipient recipient, int distributionType, long lastSeen);
void onSwitchToArchive();
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = getActivity().getMenuInflater();
/*
if (archive) inflater.inflate(R.menu.conversation_list_batch_unarchive, menu);
else inflater.inflate(R.menu.conversation_list_batch_archive, menu);
*/
inflater.inflate(R.menu.conversation_list_batch, menu);
mode.setTitle("1");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
}
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_select_all: handleSelectAllThreads(); return true;
case R.id.menu_delete_selected: handleDeleteAllSelected(); return true;
// case R.id.menu_archive_selected: handleArchiveAllSelected(); return true;
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
getListAdapter().initializeBatchMode(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[] {android.R.attr.statusBarColor});
getActivity().getWindow().setStatusBarColor(color.getColor(0, Color.BLACK));
color.recycle();
}
actionMode = null;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(ReminderUpdateEvent event) {
updateReminders(false);
}
private class ArchiveListenerCallback extends ItemTouchHelper.SimpleCallback {
ArchiveListenerCallback() {
super(0, ItemTouchHelper.RIGHT);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target)
{
return false;
}
@Override
public int getSwipeDirs(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
if (viewHolder.itemView instanceof ConversationListItemAction) {
return 0;
}
if (actionMode != null) {
return 0;
}
return super.getSwipeDirs(recyclerView, viewHolder);
}
@SuppressLint("StaticFieldLeak")
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (viewHolder.itemView instanceof ConversationListItemInboxZero) return;
final long threadId = ((ConversationListItem)viewHolder.itemView).getThreadId();
final int unreadCount = ((ConversationListItem)viewHolder.itemView).getUnreadCount();
if (archive) {
new SnackbarAsyncTask<Long>(getView(),
getResources().getQuantityString(R.plurals.ConversationListFragment_moved_conversations_to_inbox, 1, 1),
getString(R.string.ConversationListFragment_undo),
getResources().getColor(R.color.amber_500),
Snackbar.LENGTH_LONG, false)
{
@Override
protected void executeAction(@Nullable Long parameter) {
DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
}
@Override
protected void reverseAction(@Nullable Long parameter) {
DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
} else {
new SnackbarAsyncTask<Long>(getView(),
getResources().getQuantityString(R.plurals.ConversationListFragment_conversations_archived, 1, 1),
getString(R.string.ConversationListFragment_undo),
getResources().getColor(R.color.amber_500),
Snackbar.LENGTH_LONG, false)
{
@Override
protected void executeAction(@Nullable Long parameter) {
DatabaseFactory.getThreadDatabase(getActivity()).archiveConversation(threadId);
if (unreadCount > 0) {
Context context = getActivity();
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, false);
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
}
}
@Override
protected void reverseAction(@Nullable Long parameter) {
DatabaseFactory.getThreadDatabase(getActivity()).unarchiveConversation(threadId);
if (unreadCount > 0) {
Context context = getActivity();
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, unreadCount);
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
}
}
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState,
boolean isCurrentlyActive)
{
if (viewHolder.itemView instanceof ConversationListItemInboxZero) return;
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
View itemView = viewHolder.itemView;
Paint p = new Paint();
float alpha = 1.0f - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
if (dX > 0) {
Bitmap icon;
if (archive) icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_unarchive_white_36dp);
else icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_archive_white_36dp);
if (alpha > 0) p.setColor(getResources().getColor(R.color.green_500));
else p.setColor(Color.WHITE);
c.drawRect((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom(), p);
c.drawBitmap(icon,
(float) itemView.getLeft() + getResources().getDimension(R.dimen.conversation_list_fragment_archive_padding),
(float) itemView.getTop() + ((float) itemView.getBottom() - (float) itemView.getTop() - icon.getHeight())/2,
p);
}
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
}
}

View File

@ -28,6 +28,7 @@ import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.dd.CircularProgressButton;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.LabeledEditText;
@ -52,7 +53,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.crypto.ProfileCipher;
import org.session.libsignal.service.api.util.StreamDetails;
import org.session.libsignal.service.loki.api.LokiDotNetAPI;

View File

@ -1,238 +0,0 @@
package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.qr.ScanListener;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.session.libsignal.libsignal.IdentityKeyPair;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.ecc.Curve;
import org.session.libsignal.libsignal.ecc.ECPublicKey;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.push.exceptions.NotFoundException;
import org.session.libsignal.service.internal.push.DeviceLimitExceededException;
import java.io.IOException;
import network.loki.messenger.R;
public class DeviceActivity extends PassphraseRequiredActionBarActivity
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
{
private static final String TAG = DeviceActivity.class.getSimpleName();
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private DeviceAddFragment deviceAddFragment;
private DeviceListFragment deviceListFragment;
private DeviceLinkFragment deviceLinkFragment;
@Override
public void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
public void onCreate(Bundle bundle, boolean ready) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.AndroidManifest__linked_devices);
this.deviceAddFragment = new DeviceAddFragment();
this.deviceListFragment = new DeviceListFragment();
this.deviceLinkFragment = new DeviceLinkFragment();
this.deviceListFragment.setAddDeviceButtonListener(this);
this.deviceAddFragment.setScanListener(this);
// if (getIntent().getBooleanExtra("add", false)) {
initFragment(android.R.id.content, deviceAddFragment, dynamicLanguage.getCurrentLocale());
// } else {
// initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.getCurrentLocale());
// }
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
}
return false;
}
@Override
public void onClick(View v) {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.withPermanentDenialDialog(getString(R.string.DeviceActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code))
.onAllGranted(() -> {
getSupportFragmentManager().beginTransaction()
.replace(android.R.id.content, deviceAddFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
})
.onAnyDenied(() -> Toast.makeText(this, R.string.DeviceActivity_unable_to_scan_a_qr_code_without_the_camera_permission, Toast.LENGTH_LONG).show())
.execute();
}
@Override
public void onQrDataFound(final String data) {
Util.runOnMain(() -> {
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
Uri uri = Uri.parse(data);
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
deviceLinkFragment.setSharedElementEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
deviceLinkFragment.setEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
getSupportFragmentManager().beginTransaction()
.addToBackStack(null)
.addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
.replace(android.R.id.content, deviceLinkFragment)
.commit();
} else {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
R.anim.slide_from_bottom, R.anim.slide_to_bottom)
.replace(android.R.id.content, deviceLinkFragment)
.addToBackStack(null)
.commit();
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@SuppressLint("StaticFieldLeak")
@Override
public void onLink(final Uri uri) {
new ProgressDialogAsyncTask<Void, Void, Integer>(this,
R.string.DeviceProvisioningActivity_content_progress_title,
R.string.DeviceProvisioningActivity_content_progress_content)
{
private static final int SUCCESS = 0;
private static final int NO_DEVICE = 1;
private static final int NETWORK_ERROR = 2;
private static final int KEY_ERROR = 3;
private static final int LIMIT_EXCEEDED = 4;
private static final int BAD_CODE = 5;
@Override
protected Integer doInBackground(Void... params) {
boolean isMultiDevice = TextSecurePreferences.isMultiDevice(DeviceActivity.this);
try {
Context context = DeviceActivity.this;
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context);
String verificationCode = accountManager.getNewDeviceVerificationCode();
String ephemeralId = uri.getQueryParameter("uuid");
String publicKeyEncoded = uri.getQueryParameter("pub_key");
if (TextUtils.isEmpty(ephemeralId) || TextUtils.isEmpty(publicKeyEncoded)) {
Log.w(TAG, "UUID or Key is empty!");
return BAD_CODE;
}
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
Optional<byte[]> profileKey = Optional.of(ProfileKeyUtil.getProfileKey(getContext()));
TextSecurePreferences.setMultiDevice(DeviceActivity.this, true);
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, profileKey, verificationCode);
return SUCCESS;
} catch (NotFoundException e) {
Log.w(TAG, e);
TextSecurePreferences.setMultiDevice(DeviceActivity.this, isMultiDevice);
return NO_DEVICE;
} catch (DeviceLimitExceededException e) {
Log.w(TAG, e);
TextSecurePreferences.setMultiDevice(DeviceActivity.this, isMultiDevice);
return LIMIT_EXCEEDED;
} catch (IOException e) {
Log.w(TAG, e);
TextSecurePreferences.setMultiDevice(DeviceActivity.this, isMultiDevice);
return NETWORK_ERROR;
} catch (InvalidKeyException e) {
Log.w(TAG, e);
TextSecurePreferences.setMultiDevice(DeviceActivity.this, isMultiDevice);
return KEY_ERROR;
}
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
Context context = DeviceActivity.this;
switch (result) {
case SUCCESS:
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_success, Toast.LENGTH_SHORT).show();
finish();
return;
case NO_DEVICE:
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_no_device, Toast.LENGTH_LONG).show();
break;
case NETWORK_ERROR:
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_network_error, Toast.LENGTH_LONG).show();
break;
case KEY_ERROR:
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_key_error, Toast.LENGTH_LONG).show();
break;
case LIMIT_EXCEEDED:
Toast.makeText(context, R.string.DeviceProvisioningActivity_sorry_you_have_too_many_devices_linked_already, Toast.LENGTH_LONG).show();
break;
case BAD_CODE:
Toast.makeText(context, R.string.DeviceActivity_sorry_this_is_not_a_valid_device_link_qr_code, Toast.LENGTH_LONG).show();
break;
}
getSupportFragmentManager().popBackStackImmediate();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View File

@ -1,44 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import android.view.Window;
import network.loki.messenger.R;
public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActivity {
@SuppressWarnings("unused")
private static final String TAG = DeviceProvisioningActivity.class.getSimpleName();
@Override
protected void onPreCreate() {
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
}
@Override
protected void onCreate(Bundle bundle, boolean ready) {
assert getSupportActionBar() != null;
getSupportActionBar().hide();
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
.setPositiveButton(R.string.DeviceProvisioningActivity_continue, (dialog1, which) -> {
Intent intent = new Intent(DeviceProvisioningActivity.this, DeviceActivity.class);
intent.putExtra("add", true);
startActivity(intent);
finish();
})
.setNegativeButton(R.string.DeviceProvisioningActivity_cancel, (dialog12, which) -> {
dialog12.dismiss();
finish();
})
.setOnDismissListener(dialog13 -> finish())
.create();
dialog.setIcon(getResources().getDrawable(R.drawable.icon_dialog));
dialog.show();
}
}

View File

@ -1,249 +0,0 @@
package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import androidx.annotation.AnimRes;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.appcompat.app.AlertDialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.loki.fragments.ContactSelectionListFragment;
import org.thoughtcrime.securesms.loki.fragments.ContactSelectionListLoader.DisplayMode;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import java.util.concurrent.ExecutionException;
import network.loki.messenger.R;
public class InviteActivity extends PassphraseRequiredActionBarActivity {
private ContactSelectionListFragment contactsFragment;
private EditText inviteText;
private ViewGroup smsSendFrame;
private Button smsSendButton;
private Animation slideInAnimation;
private Animation slideOutAnimation;
private ImageView heart;
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_CONTACTS);
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
setContentView(R.layout.invite_activity);
assert getSupportActionBar() != null;
getSupportActionBar().setTitle(R.string.AndroidManifest__invite_friends);
initializeResources();
}
private void initializeResources() {
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
View shareButton = ViewUtil.findById(this, R.id.share_button);
View smsButton = ViewUtil.findById(this, R.id.sms_button);
Button smsCancelButton = ViewUtil.findById(this, R.id.cancel_sms_button);
ContactFilterToolbar contactFilter = ViewUtil.findById(this, R.id.contact_filter);
inviteText = ViewUtil.findById(this, R.id.invite_text);
smsSendFrame = ViewUtil.findById(this, R.id.sms_send_frame);
smsSendButton = ViewUtil.findById(this, R.id.send_sms_button);
heart = ViewUtil.findById(this, R.id.heart);
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
updateSmsButtonText();
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
heart.getViewTreeObserver().addOnPreDrawListener(new HeartPreDrawListener());
}
shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener());
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
smsSendButton.setOnClickListener(new SmsSendClickListener());
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
contactFilter.setNavigationIcon(R.drawable.ic_search_white_24dp);
}
private Animation loadAnimation(@AnimRes int animResId) {
final Animation animation = AnimationUtils.loadAnimation(this, animResId);
animation.setInterpolator(new FastOutSlowInInterpolator());
return animation;
}
public void onContactSelected(String number) {
updateSmsButtonText();
}
public void onContactDeselected(String number) {
updateSmsButtonText();
}
private void sendSmsInvites() {
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
contactsFragment.getSelectedContacts()
.toArray(new String[contactsFragment.getSelectedContacts().size()]));
}
private void updateSmsButtonText() {
smsSendButton.setText(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_to_friends,
contactsFragment.getSelectedContacts().size(),
contactsFragment.getSelectedContacts().size()));
smsSendButton.setEnabled(!contactsFragment.getSelectedContacts().isEmpty());
}
@Override public void onBackPressed() {
if (smsSendFrame.getVisibility() == View.VISIBLE) {
cancelSmsSelection();
} else {
super.onBackPressed();
}
}
private void cancelSmsSelection() {
updateSmsButtonText();
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
}
private class ShareClickListener implements OnClickListener {
@Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, inviteText.getText().toString());
sendIntent.setType("text/plain");
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(Intent.createChooser(sendIntent, getString(R.string.InviteActivity_invite_to_signal)));
} else {
Toast.makeText(InviteActivity.this, R.string.InviteActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
}
}
}
private class SmsClickListener implements OnClickListener {
@Override
public void onClick(View v) {
ViewUtil.animateIn(smsSendFrame, slideInAnimation);
}
}
private class SmsCancelClickListener implements OnClickListener {
@Override
public void onClick(View v) {
cancelSmsSelection();
}
}
private class SmsSendClickListener implements OnClickListener {
@Override
public void onClick(View v) {
new AlertDialog.Builder(InviteActivity.this)
.setTitle(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_invites,
contactsFragment.getSelectedContacts().size(),
contactsFragment.getSelectedContacts().size()))
.setMessage(inviteText.getText().toString())
.setPositiveButton(R.string.yes, (dialog, which) -> sendSmsInvites())
.setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss())
.show();
}
}
private class ContactFilterChangedListener implements OnFilterChangedListener {
@Override
public void onFilterChanged(String filter) {
contactsFragment.setQueryFilter(filter);
}
}
private class HeartPreDrawListener implements OnPreDrawListener {
@Override
@TargetApi(VERSION_CODES.LOLLIPOP)
public boolean onPreDraw() {
heart.getViewTreeObserver().removeOnPreDrawListener(this);
final int w = heart.getWidth();
final int h = heart.getHeight();
Animator reveal = ViewAnimationUtils.createCircularReveal(heart,
w / 2, h,
0, (float)Math.sqrt(h*h + (w*w/4)));
reveal.setInterpolator(new FastOutSlowInInterpolator());
reveal.setDuration(800);
reveal.start();
return false;
}
}
@SuppressLint("StaticFieldLeak")
private class SendSmsInvitesAsyncTask extends ProgressDialogAsyncTask<String,Void,Void> {
private final String message;
SendSmsInvitesAsyncTask(Context context, String message) {
super(context, R.string.InviteActivity_sending, R.string.InviteActivity_sending);
this.message = message;
}
@Override
protected Void doInBackground(String... numbers) {
final Context context = getContext();
if (context == null) return null;
for (String number : numbers) {
Recipient recipient = Recipient.from(context, Address.fromExternal(context, number), false);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null);
if (recipient.getContactUri() != null) {
DatabaseFactory.getRecipientDatabase(context).setSeenInviteReminder(recipient, true);
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
final Context context = getContext();
if (context == null) return;
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE).addListener(new Listener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
}
@Override
public void onFailure(ExecutionException e) {}
});
Toast.makeText(context, R.string.InviteActivity_invitations_sent, Toast.LENGTH_LONG).show();
}
}
}

View File

@ -1,110 +0,0 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Toast;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
import network.loki.messenger.R;
/**
* Activity container for starting a new conversation.
*
* @author Moxie Marlinspike
*
*/
public class NewConversationActivity extends ContactSelectionActivity {
@SuppressWarnings("unused")
private static final String TAG = NewConversationActivity.class.getSimpleName();
@Override
public void onCreate(Bundle bundle, boolean ready) {
super.onCreate(bundle, ready);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public void onContactSelected(String number) {
boolean isValid = PublicKeyValidation.isValid(number);
if (!isValid) {
Toast.makeText(this, R.string.fragment_new_conversation_invalid_public_key_message, Toast.LENGTH_SHORT).show();
return;
}
Recipient recipient = Recipient.from(this, Address.fromSerialized(number), true);
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA));
intent.setDataAndType(getIntent().getData(), getIntent().getType());
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
startActivity(intent);
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home: super.onBackPressed(); return true;
case R.id.menu_refresh: handleManualRefresh(); return true;
case R.id.menu_new_group: handleCreateGroup(); return true;
case R.id.menu_invite: handleInvite(); return true;
}
return false;
}
private void handleManualRefresh() {
contactsFragment.setRefreshing(true);
onRefresh();
}
private void handleCreateGroup() {
startActivity(new Intent(this, GroupCreateActivity.class));
}
private void handleInvite() {
startActivity(new Intent(this, InviteActivity.class));
}
// @Override
// protected boolean onPrepareOptionsPanel(View view, Menu menu) {
// MenuInflater inflater = this.getMenuInflater();
// menu.clear();
// inflater.inflate(R.menu.new_conversation_activity, menu);
// super.onPrepareOptionsMenu(menu);
// return true;
// }
}

View File

@ -798,33 +798,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
}
}
private class AboutNumberClickedListener implements ContactPreference.Listener {
@Override
public void onMessageClicked() {
CommunicationActions.startConversation(getContext(), recipient, null);
}
@Override
public void onSecureCallClicked() {
CommunicationActions.startVoiceCall(getActivity(), recipient);
}
@Override
public void onInSecureCallClicked() {
try {
Intent dialIntent = new Intent(Intent.ACTION_DIAL,
Uri.parse("tel:" + recipient.getAddress().serialize()));
startActivity(dialIntent);
} catch (ActivityNotFoundException anfe) {
Log.w(TAG, anfe);
Dialogs.showAlertDialog(getContext(),
getString(R.string.ConversationActivity_calls_not_supported),
getString(R.string.ConversationActivity_this_device_does_not_appear_to_support_dial_actions));
}
}
}
private class CustomNotificationsChangedListener implements Preference.OnPreferenceChangeListener {
@Override

View File

@ -1,968 +0,0 @@
package org.thoughtcrime.securesms;
import android.Manifest;
import android.animation.Animator;
import android.annotation.SuppressLint;
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.appcompat.app.AlertDialog;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.dd.CircularProgressButton;
import net.sqlcipher.database.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.backup.BackupEvent;
import org.thoughtcrime.securesms.backup.FullBackupImporter;
import org.thoughtcrime.securesms.components.LabeledEditText;
import org.thoughtcrime.securesms.components.registration.CallMeCountDownView;
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.registration.CaptchaActivity;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.util.BackupUtilOld;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.session.libsignal.libsignal.IdentityKeyPair;
import org.session.libsignal.libsignal.state.PreKeyRecord;
import org.session.libsignal.libsignal.state.SignedPreKeyRecord;
import org.session.libsignal.libsignal.util.KeyHelper;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.push.exceptions.CaptchaRequiredException;
import org.session.libsignal.service.api.push.exceptions.RateLimitException;
import org.session.libsignal.service.api.util.PhoneNumberFormatter;
import org.session.libsignal.service.internal.push.LockedException;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import network.loki.messenger.R;
/**
* The register account activity. Prompts ths user for their registration information
* and begins the account registration process.
*
* @author Moxie Marlinspike
*
*/
public class RegistrationActivity extends BaseActionBarActivity implements VerificationCodeView.OnCodeEnteredListener {
private static final int PICK_COUNTRY = 1;
private static final int CAPTCHA = 24601;
private static final int SCENE_TRANSITION_DURATION = 250;
private static final int DEBUG_TAP_TARGET = 8;
private static final int DEBUG_TAP_ANNOUNCE = 4;
public static final String RE_REGISTRATION_EXTRA = "re_registration";
private static final String TAG = RegistrationActivity.class.getSimpleName();
private ArrayAdapter<String> countrySpinnerAdapter;
private Spinner countrySpinner;
private LabeledEditText countryCode;
private LabeledEditText number;
private CircularProgressButton createButton;
private TextView title;
private TextView subtitle;
private View registrationContainer;
private View verificationContainer;
private View restoreContainer;
private TextView restoreBackupTime;
private TextView restoreBackupSize;
private TextView restoreBackupProgress;
private CircularProgressButton restoreButton;
private View pinContainer;
private EditText pin;
private CircularProgressButton pinButton;
private TextView pinForgotButton;
private View pinClarificationContainer;
private CallMeCountDownView callMeCountDownView;
private View wrongNumberButton;
private VerificationPinKeyboard keyboard;
private VerificationCodeView verificationCodeView;
private RegistrationState registrationState;
private SignalServiceAccountManager accountManager;
private int debugTapCounter;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.registration_activity);
initializeResources();
initializeSpinner();
initializeNumber();
initializeBackupDetection();
}
@Override
public void onDestroy() {
super.onDestroy();
markAsVerifying(false);
EventBus.getDefault().unregister(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PICK_COUNTRY && resultCode == RESULT_OK && data != null) {
this.countryCode.setText(String.valueOf(data.getIntExtra("country_code", 1)));
setCountryDisplay(data.getStringExtra("country_name"));
} else if (requestCode == CAPTCHA && resultCode == RESULT_OK && data != null) {
registrationState = new RegistrationState(Optional.fromNullable(data.getStringExtra(CaptchaActivity.KEY_TOKEN)), registrationState);
if (data.getBooleanExtra(CaptchaActivity.KEY_IS_SMS, true)) {
handleRegister();
} else {
handlePhoneCallRequest();
}
} else if (requestCode == CAPTCHA) {
Toast.makeText(this, R.string.RegistrationActivity_failed_to_verify_the_captcha, Toast.LENGTH_LONG).show();
createButton.setIndeterminateProgressMode(false);
createButton.setProgress(0);
}
}
private void initializeResources() {
TextView skipButton = findViewById(R.id.skip_button);
TextView restoreSkipButton = findViewById(R.id.skip_restore_button);
this.countrySpinner = findViewById(R.id.country_spinner);
this.countryCode = findViewById(R.id.country_code);
this.number = findViewById(R.id.number);
this.createButton = findViewById(R.id.registerButton);
this.title = findViewById(R.id.verify_header);
this.subtitle = findViewById(R.id.verify_subheader);
this.registrationContainer = findViewById(R.id.registration_container);
this.verificationContainer = findViewById(R.id.verification_container);
this.verificationCodeView = findViewById(R.id.code);
this.keyboard = findViewById(R.id.keyboard);
this.callMeCountDownView = findViewById(R.id.call_me_count_down);
this.wrongNumberButton = findViewById(R.id.wrong_number);
this.restoreContainer = findViewById(R.id.restore_container);
this.restoreBackupSize = findViewById(R.id.backup_size_text);
this.restoreBackupTime = findViewById(R.id.backup_created_text);
this.restoreBackupProgress = findViewById(R.id.backup_progress_text);
this.restoreButton = findViewById(R.id.restore_button);
this.pinContainer = findViewById(R.id.pin_container);
this.pin = findViewById(R.id.pin);
this.pinButton = findViewById(R.id.pinButton);
this.pinForgotButton = findViewById(R.id.forgot_button);
this.pinClarificationContainer = findViewById(R.id.pin_clarification_container);
this.registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, Optional.absent(), Optional.absent());
this.countryCode.getInput().addTextChangedListener(new CountryCodeChangedListener());
this.number.getInput().addTextChangedListener(new NumberChangedListener());
this.createButton.setOnClickListener(v -> handleRegister());
this.callMeCountDownView.setOnClickListener(v -> handlePhoneCallRequest());
skipButton.setOnClickListener(v -> handleCancel());
restoreSkipButton.setOnClickListener(v -> displayInitialView(true));
if (getIntent().getBooleanExtra(RE_REGISTRATION_EXTRA, false)) {
skipButton.setVisibility(View.VISIBLE);
} else {
skipButton.setVisibility(View.INVISIBLE);
}
this.keyboard.setOnKeyPressListener(key -> {
if (key >= 0) verificationCodeView.append(key);
else verificationCodeView.delete();
});
this.verificationCodeView.setOnCompleteListener(this);
EventBus.getDefault().register(this);
}
private void onDebugClick(View view) {
debugTapCounter++;
if (debugTapCounter >= DEBUG_TAP_TARGET) {
startActivity(new Intent(this, LogSubmitActivity.class));
} else if (debugTapCounter >= DEBUG_TAP_ANNOUNCE) {
int remaining = DEBUG_TAP_TARGET - debugTapCounter;
Toast.makeText(this, getResources().getQuantityString(R.plurals.RegistrationActivity_debug_log_hint, remaining, remaining), Toast.LENGTH_SHORT).show();
}
}
@SuppressLint("ClickableViewAccessibility")
private void initializeSpinner() {
this.countrySpinnerAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
this.countrySpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
setCountryDisplay(getString(R.string.RegistrationActivity_select_your_country));
this.countrySpinner.setAdapter(this.countrySpinnerAdapter);
this.countrySpinner.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP) {
Intent intent = new Intent(RegistrationActivity.this, CountrySelectionActivity.class);
startActivityForResult(intent, PICK_COUNTRY);
}
return true;
});
this.countrySpinner.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && event.getAction() == KeyEvent.ACTION_UP) {
Intent intent = new Intent(RegistrationActivity.this, CountrySelectionActivity.class);
startActivityForResult(intent, PICK_COUNTRY);
return true;
}
return false;
});
}
private void initializeNumber() {
}
@SuppressLint("StaticFieldLeak")
private void initializeBackupDetection() {
if (!Permissions.hasAll(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
Log.i(TAG, "Skipping backup detection. We don't have the permission.");
return;
}
if (getIntent().getBooleanExtra(RE_REGISTRATION_EXTRA, false)) return;
new AsyncTask<Void, Void, BackupUtilOld.BackupInfo>() {
@Override
protected @Nullable BackupUtilOld.BackupInfo doInBackground(Void... voids) {
try {
return BackupUtilOld.getLatestBackup(RegistrationActivity.this);
} catch (NoExternalStorageException e) {
Log.w(TAG, e);
return null;
}
}
@Override
protected void onPostExecute(@Nullable BackupUtilOld.BackupInfo backup) {
if (backup != null) displayRestoreView(backup);
}
}.execute();
}
private void setCountryDisplay(String value) {
this.countrySpinnerAdapter.clear();
this.countrySpinnerAdapter.add(value);
}
private String getConfiguredE164Number() {
return PhoneNumberFormatter.formatE164(countryCode.getText().toString(),
number.getText().toString());
}
@SuppressLint("StaticFieldLeak")
private void handleRestore(BackupUtilOld.BackupInfo backup) {
View view = LayoutInflater.from(this).inflate(R.layout.enter_backup_passphrase_dialog, null);
EditText prompt = view.findViewById(R.id.restore_passphrase_input);
new AlertDialog.Builder(this)
.setTitle(R.string.RegistrationActivity_enter_backup_passphrase)
.setView(view)
.setPositiveButton(getString(R.string.RegistrationActivity_restore), (dialog, which) -> {
InputMethodManager inputMethodManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(prompt.getWindowToken(), 0);
restoreButton.setIndeterminateProgressMode(true);
restoreButton.setProgress(50);
final String passphrase = prompt.getText().toString();
new AsyncTask<Void, Void, BackupImportResult>() {
@Override
protected BackupImportResult doInBackground(Void... voids) {
try {
Context context = RegistrationActivity.this;
SQLiteDatabase database = DatabaseFactory.getBackupDatabase(context);
FullBackupImporter.importFromUri(context,
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
database, Uri.fromFile(backup.getFile()), passphrase);
DatabaseFactory.upgradeRestored(context, database);
NotificationChannels.restoreContactNotificationChannels(context);
TextSecurePreferences.setBackupEnabled(context, true);
TextSecurePreferences.setBackupPassphrase(context, passphrase);
return BackupImportResult.SUCCESS;
} catch (FullBackupImporter.DatabaseDowngradeException e) {
Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e);
return BackupImportResult.FAILURE_VERSION_DOWNGRADE;
} catch (IOException e) {
Log.w(TAG, e);
return BackupImportResult.FAILURE_UNKNOWN;
}
}
@Override
protected void onPostExecute(@NonNull BackupImportResult result) {
restoreButton.setIndeterminateProgressMode(false);
restoreButton.setProgress(0);
restoreBackupProgress.setText("");
switch (result) {
case SUCCESS:
displayInitialView(true);
break;
case FAILURE_VERSION_DOWNGRADE:
Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_backup_failure_downgrade, Toast.LENGTH_LONG).show();
break;
case FAILURE_UNKNOWN:
Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_incorrect_backup_passphrase, Toast.LENGTH_LONG).show();
break;
}
}
}.execute();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void handleRegister() {
if (TextUtils.isEmpty(countryCode.getText())) {
Toast.makeText(this, getString(R.string.RegistrationActivity_you_must_specify_your_country_code), Toast.LENGTH_LONG).show();
return;
}
if (TextUtils.isEmpty(number.getText())) {
Toast.makeText(this, getString(R.string.RegistrationActivity_you_must_specify_your_phone_number), Toast.LENGTH_LONG).show();
return;
}
final String e164number = getConfiguredE164Number();
if (!PhoneNumberFormatter.isValidNumber(e164number, countryCode.getText().toString())) {
Dialogs.showAlertDialog(this,
getString(R.string.RegistrationActivity_invalid_number),
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), e164number));
}
}
private void handleRequestVerification(@NonNull String e164number, boolean gcmSupported) {
createButton.setIndeterminateProgressMode(true);
createButton.setProgress(50);
requestVerificationCode(e164number, false, false);
}
@SuppressLint("StaticFieldLeak")
private void requestVerificationCode(@NonNull String e164number, boolean gcmSupported, boolean smsRetrieverSupported) {
new AsyncTask<Void, Void, VerificationRequestResult> () {
@Override
protected @NonNull VerificationRequestResult doInBackground(Void... voids) {
try {
markAsVerifying(true);
String password = Util.getSecret(18);
Optional<String> fcmToken = Optional.absent();
accountManager = AccountManagerFactory.createManager(RegistrationActivity.this, e164number, password);
accountManager.requestSmsVerificationCode(smsRetrieverSupported, registrationState.captchaToken);
return new VerificationRequestResult(password, fcmToken, Optional.absent());
} catch (IOException e) {
Log.w(TAG, "Error during account registration", e);
return new VerificationRequestResult(null, Optional.absent(), Optional.of(e));
}
}
protected void onPostExecute(@NonNull VerificationRequestResult result) {
if (result.exception.isPresent() && result.exception.get() instanceof CaptchaRequiredException) {
requestCaptcha(true);
} else if (result.exception.isPresent()) {
Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show();
createButton.setIndeterminateProgressMode(false);
createButton.setProgress(0);
} else {
registrationState = new RegistrationState(RegistrationState.State.VERIFYING, e164number, result.password, result.fcmToken, Optional.absent());
displayVerificationView(e164number, 64);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void requestCaptcha(boolean isSms) {
startActivityForResult(CaptchaActivity.getIntent(this, isSms), CAPTCHA);
}
private void handleVerificationCodeReceived(@Nullable String code) {
List<Integer> parsedCode = convertVerificationCodeToDigits(code);
for (int i = 0; i < parsedCode.size(); i++) {
int index = i;
verificationCodeView.postDelayed(() -> verificationCodeView.append(parsedCode.get(index)), i * 200);
}
}
private List<Integer> convertVerificationCodeToDigits(@Nullable String code) {
if (code == null || code.length() != 6 || registrationState.state != RegistrationState.State.VERIFYING) {
return Collections.emptyList();
}
List<Integer> result = new LinkedList<>();
try {
for (int i = 0; i < code.length(); i++) {
result.add(Integer.parseInt(Character.toString(code.charAt(i))));
}
} catch (NumberFormatException e) {
Log.w(TAG, "Failed to convert code into digits.",e );
return Collections.emptyList();
}
return result;
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCodeComplete(@NonNull String code) {
this.registrationState = new RegistrationState(RegistrationState.State.CHECKING, this.registrationState);
callMeCountDownView.setVisibility(View.INVISIBLE);
keyboard.displayProgress();
new AsyncTask<Void, Void, Pair<Integer, Long>>() {
@Override
protected Pair<Integer, Long> doInBackground(Void... voids) {
try {
verifyAccount(code, null);
return new Pair<>(1, -1L);
} catch (LockedException e) {
Log.w(TAG, e);
return new Pair<>(2, e.getTimeRemaining());
} catch (IOException e) {
Log.w(TAG, e);
return new Pair<>(3, -1L);
}
}
@Override
protected void onPostExecute(Pair<Integer, Long> result) {
if (result.first == 1) {
keyboard.displaySuccess().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
handleSuccessfulRegistration();
}
});
} else if (result.first == 2) {
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean r) {
registrationState = new RegistrationState(RegistrationState.State.PIN, registrationState);
displayPinView(code, result.second);
}
});
} else {
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
registrationState = new RegistrationState(RegistrationState.State.VERIFYING, registrationState);
callMeCountDownView.setVisibility(View.VISIBLE);
verificationCodeView.clear();
keyboard.displayKeyboard();
}
});
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@SuppressLint("StaticFieldLeak")
private void handleVerifyWithPinClicked(@NonNull String code, @Nullable String pin) {
if (TextUtils.isEmpty(pin) || TextUtils.isEmpty(pin.replace(" ", ""))) {
Toast.makeText(this, R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show();
return;
}
pinButton.setIndeterminateProgressMode(true);
pinButton.setProgress(50);
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... voids) {
try {
verifyAccount(code, pin);
return 1;
} catch (LockedException e) {
Log.w(TAG, e);
return 2;
} catch (RateLimitException e) {
Log.w(TAG, e);
return 3;
} catch (IOException e) {
Log.w(TAG, e);
return 4;
}
}
@Override
protected void onPostExecute(Integer result) {
pinButton.setIndeterminateProgressMode(false);
pinButton.setProgress(0);
if (result == 1) {
TextSecurePreferences.setRegistrationLockPin(RegistrationActivity.this, pin);
TextSecurePreferences.setRegistrationtLockEnabled(RegistrationActivity.this, true);
TextSecurePreferences.setRegistrationLockLastReminderTime(RegistrationActivity.this, System.currentTimeMillis());
TextSecurePreferences.setRegistrationLockNextReminderInterval(RegistrationActivity.this, RegistrationLockReminders.INITIAL_INTERVAL);
handleSuccessfulRegistration();
} else if (result == 2) {
RegistrationActivity.this.pin.setText("");
Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_incorrect_registration_lock_pin, Toast.LENGTH_LONG).show();
} else if (result == 3) {
new AlertDialog.Builder(RegistrationActivity.this)
.setTitle(R.string.RegistrationActivity_too_many_attempts)
.setMessage(R.string.RegistrationActivity_you_have_made_too_many_incorrect_registration_lock_pin_attempts_please_try_again_in_a_day)
.setPositiveButton(android.R.string.ok, null)
.show();
} else if (result == 4) {
Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show();
}
}
}.execute();
}
private void handleForgottenPin(long timeRemaining) {
new AlertDialog.Builder(RegistrationActivity.this)
.setTitle(R.string.RegistrationActivity_oh_no)
.setMessage(getString(R.string.RegistrationActivity_registration_of_this_phone_number_will_be_possible_without_your_registration_lock_pin_after_seven_days_have_passed, (TimeUnit.MILLISECONDS.toDays(timeRemaining) + 1)))
.setPositiveButton(android.R.string.ok, null)
.show();
}
@SuppressLint("StaticFieldLeak")
private void handlePhoneCallRequest() {
if (registrationState.state == RegistrationState.State.VERIFYING) {
callMeCountDownView.startCountDown(300);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
try {
accountManager.requestVoiceVerificationCode(Locale.getDefault(), registrationState.captchaToken);
} catch (CaptchaRequiredException e) {
requestCaptcha(false);
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private void verifyAccount(@NonNull String code, @Nullable String pin) throws IOException {
int registrationId = KeyHelper.generateRegistrationId(false);
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(RegistrationActivity.this);
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(RegistrationActivity.this);
TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId);
SessionUtil.archiveAllSessions(RegistrationActivity.this);
accountManager.verifyAccountWithCode(code, null, registrationId, !registrationState.gcmToken.isPresent(), pin,
unidentifiedAccessKey, universalUnidentifiedAccess);
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(RegistrationActivity.this);
List<PreKeyRecord> records = PreKeyUtil.generatePreKeyRecords(RegistrationActivity.this);
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(RegistrationActivity.this, identityKey, true);
accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records);
if (registrationState.gcmToken.isPresent()) {
accountManager.setGcmId(registrationState.gcmToken);
}
TextSecurePreferences.setFcmToken(RegistrationActivity.this, registrationState.gcmToken.orNull());
TextSecurePreferences.setFcmDisabled(RegistrationActivity.this, !registrationState.gcmToken.isPresent());
TextSecurePreferences.setWebsocketRegistered(RegistrationActivity.this, true);
DatabaseFactory.getIdentityDatabase(RegistrationActivity.this)
.saveIdentity(Address.fromSerialized(registrationState.e164number),
identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED,
true, System.currentTimeMillis(), true);
TextSecurePreferences.setVerifying(RegistrationActivity.this, false);
TextSecurePreferences.setPushRegistered(RegistrationActivity.this, true);
TextSecurePreferences.setLocalNumber(RegistrationActivity.this, registrationState.e164number);
TextSecurePreferences.setPushServerPassword(RegistrationActivity.this, registrationState.password);
TextSecurePreferences.setSignedPreKeyRegistered(RegistrationActivity.this, true);
TextSecurePreferences.setPromptedPushRegistration(RegistrationActivity.this, true);
TextSecurePreferences.setUnauthorizedReceived(RegistrationActivity.this, false);
}
private void handleSuccessfulRegistration() {
ApplicationContext.getInstance(RegistrationActivity.this).getJobManager().add(new RotateCertificateJob(RegistrationActivity.this));
RotateSignedPreKeyListener.schedule(RegistrationActivity.this);
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent == null) {
nextIntent = new Intent(RegistrationActivity.this, ConversationListActivity.class);
}
startActivity(nextIntent);
finish();
}
private void displayRestoreView(@NonNull BackupUtilOld.BackupInfo backup) {
title.animate().translationX(title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
title.setText(R.string.RegistrationActivity_restore_from_backup);
title.clearAnimation();
title.setTranslationX(-1 * title.getWidth());
title.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
subtitle.animate().translationX(subtitle.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
subtitle.setText(R.string.RegistrationActivity_restore_your_messages_and_media_from_a_local_backup);
subtitle.clearAnimation();
subtitle.setTranslationX(-1 * subtitle.getWidth());
subtitle.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
registrationContainer.animate().translationX(registrationContainer.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
registrationContainer.clearAnimation();
registrationContainer.setVisibility(View.INVISIBLE);
registrationContainer.setTranslationX(0);
restoreContainer.setTranslationX(-1 * registrationContainer.getWidth());
restoreContainer.setVisibility(View.VISIBLE);
restoreButton.setProgress(0);
restoreButton.setIndeterminateProgressMode(false);
restoreButton.setOnClickListener(v -> handleRestore(backup));
restoreBackupSize.setText(getString(R.string.RegistrationActivity_backup_size_s, Util.getPrettyFileSize(backup.getSize())));
restoreBackupTime.setText(getString(R.string.RegistrationActivity_backup_timestamp_s, DateUtils.getExtendedRelativeTimeSpanString(RegistrationActivity.this, Locale.US, backup.getTimestamp())));
restoreBackupProgress.setText("");
restoreContainer.animate().translationX(0).setDuration(SCENE_TRANSITION_DURATION).setListener(null).setInterpolator(new OvershootInterpolator()).start();
}
}).start();
}
private void displayInitialView(boolean forwards) {
int startDirectionMultiplier = forwards ? -1 : 1;
int endDirectionMultiplier = forwards ? 1 : -1;
title.animate().translationX(startDirectionMultiplier * title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
title.setText(R.string.registration_activity__verify_your_number);
title.clearAnimation();
title.setTranslationX(endDirectionMultiplier * title.getWidth());
title.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
subtitle.animate().translationX(startDirectionMultiplier * subtitle.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
subtitle.setText(R.string.registration_activity__please_enter_your_mobile_number_to_receive_a_verification_code_carrier_rates_may_apply);
subtitle.clearAnimation();
subtitle.setTranslationX(endDirectionMultiplier * subtitle.getWidth());
subtitle.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
View container;
if (verificationContainer.getVisibility() == View.VISIBLE) container = verificationContainer;
else container = restoreContainer;
container.animate().translationX(startDirectionMultiplier * container.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
container.clearAnimation();
container.setVisibility(View.INVISIBLE);
container.setTranslationX(0);
registrationContainer.setTranslationX(endDirectionMultiplier * registrationContainer.getWidth());
registrationContainer.setVisibility(View.VISIBLE);
createButton.setProgress(0);
createButton.setIndeterminateProgressMode(false);
registrationContainer.animate().translationX(0).setDuration(SCENE_TRANSITION_DURATION).setListener(null).setInterpolator(new OvershootInterpolator()).start();
}
}).start();
}
private void displayVerificationView(@NonNull String e164number, int callCountdown) {
ServiceUtil.getInputMethodManager(this)
.hideSoftInputFromWindow(countryCode.getWindowToken(), 0);
ServiceUtil.getInputMethodManager(this)
.hideSoftInputFromWindow(number.getWindowToken(), 0);
title.animate().translationX(-1 * title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
title.setText(getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, formatNumber(e164number)));
title.clearAnimation();
title.setTranslationX(title.getWidth());
title.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
subtitle.setText("");
registrationContainer.animate().translationX(-1 * registrationContainer.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
registrationContainer.clearAnimation();
registrationContainer.setVisibility(View.INVISIBLE);
registrationContainer.setTranslationX(0);
verificationContainer.setTranslationX(verificationContainer.getWidth());
verificationContainer.setVisibility(View.VISIBLE);
verificationContainer.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
this.callMeCountDownView.startCountDown(callCountdown);
this.wrongNumberButton.setOnClickListener(v -> onWrongNumberClicked());
}
private void displayPinView(String code, long lockedUntil) {
title.animate().translationX(-1 * title.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
title.setText(R.string.RegistrationActivity_registration_lock_pin);
title.clearAnimation();
title.setTranslationX(title.getWidth());
title.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
subtitle.animate().translationX(-1 * subtitle.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
subtitle.setText(R.string.RegistrationActivity_this_phone_number_has_registration_lock_enabled_please_enter_the_registration_lock_pin);
subtitle.clearAnimation();
subtitle.setTranslationX(subtitle.getWidth());
subtitle.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
verificationContainer.animate().translationX(-1 * verificationContainer.getWidth()).setDuration(SCENE_TRANSITION_DURATION).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
verificationContainer.clearAnimation();
verificationContainer.setVisibility(View.INVISIBLE);
verificationContainer.setTranslationX(0);
pinContainer.setTranslationX(pinContainer.getWidth());
pinContainer.setVisibility(View.VISIBLE);
pinContainer.animate().translationX(0).setListener(null).setInterpolator(new OvershootInterpolator()).setDuration(SCENE_TRANSITION_DURATION).start();
}
}).start();
pinButton.setOnClickListener(v -> handleVerifyWithPinClicked(code, pin.getText().toString()));
pinForgotButton.setOnClickListener(v -> handleForgottenPin(lockedUntil));
pin.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 && code.equals(s.toString())) pinClarificationContainer.setVisibility(View.VISIBLE);
else if (pinClarificationContainer.getVisibility() == View.VISIBLE) pinClarificationContainer.setVisibility(View.GONE);
}
});
}
private void handleCancel() {
TextSecurePreferences.setPromptedPushRegistration(RegistrationActivity.this, true);
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent == null) {
nextIntent = new Intent(RegistrationActivity.this, ConversationListActivity.class);
}
startActivity(nextIntent);
finish();
}
private void handlePromptForNoPlayServices(@NonNull String e164number) {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.RegistrationActivity_missing_google_play_services);
dialog.setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services);
dialog.setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> handleRequestVerification(e164number, false));
dialog.setNegativeButton(android.R.string.cancel, null);
dialog.show();
}
private void markAsVerifying(boolean verifying) {
TextSecurePreferences.setVerifying(this, verifying);
if (verifying) {
TextSecurePreferences.setPushRegistered(this, false);
}
}
private String formatNumber(@NonNull String e164Number) {
return e164Number;
}
private void onWrongNumberClicked() {
displayInitialView(false);
registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, Optional.absent(), Optional.absent());
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(BackupEvent event) {
if (event.getCount() == 0) restoreBackupProgress.setText(R.string.RegistrationActivity_checking);
else restoreBackupProgress.setText(getString(R.string.RegistrationActivity_d_messages_so_far, event.getCount()));
}
private class CountryCodeChangedListener implements TextWatcher {
@Override
public void afterTextChanged(Editable s) {
if (TextUtils.isEmpty(s) || !TextUtils.isDigitsOnly(s)) {
setCountryDisplay(getString(R.string.RegistrationActivity_select_your_country));
return;
}
int countryCode = Integer.parseInt(s.toString());
setCountryDisplay("N/A");
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
private class NumberChangedListener implements TextWatcher {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
private static class VerificationRequestResult {
private final String password;
private final Optional<String> fcmToken;
private final Optional<IOException> exception;
private VerificationRequestResult(String password, Optional<String> fcmToken, Optional<IOException> exception) {
this.password = password;
this.fcmToken = fcmToken;
this.exception = exception;
}
}
private static class RegistrationState {
private enum State {
INITIAL, VERIFYING, CHECKING, PIN
}
private final State state;
private final String e164number;
private final String password;
private final Optional<String> gcmToken;
private final Optional<String> captchaToken;
RegistrationState(State state, String e164number, String password, Optional<String> gcmToken, Optional<String> captchaToken) {
this.state = state;
this.e164number = e164number;
this.password = password;
this.gcmToken = gcmToken;
this.captchaToken = captchaToken;
}
RegistrationState(State state, RegistrationState previous) {
this.state = state;
this.e164number = previous.e164number;
this.password = previous.password;
this.gcmToken = previous.gcmToken;
this.captchaToken = previous.captchaToken;
}
RegistrationState(Optional<String> captchaToken, RegistrationState previous) {
this.state = previous.state;
this.e164number = previous.e164number;
this.password = previous.password;
this.gcmToken = previous.gcmToken;
this.captchaToken = captchaToken;
}
}
private enum BackupImportResult {
SUCCESS, FAILURE_VERSION_DOWNGRADE, FAILURE_UNKNOWN
}
}

View File

@ -1,111 +0,0 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.widget.Toast;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Rfc5724Uri;
import java.net.URISyntaxException;
import network.loki.messenger.R;
public class SmsSendtoActivity extends Activity {
private static final String TAG = SmsSendtoActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
startActivity(getNextIntent(getIntent()));
finish();
super.onCreate(savedInstanceState);
}
private Intent getNextIntent(Intent original) {
DestinationAndBody destination;
if (original.getAction().equals(Intent.ACTION_SENDTO)) {
destination = getDestinationForSendTo(original);
} else if (original.getData() != null && "content".equals(original.getData().getScheme())) {
destination = getDestinationForSyncAdapter(original);
} else {
destination = getDestinationForView(original);
}
final Intent nextIntent;
if (TextUtils.isEmpty(destination.destination)) {
nextIntent = new Intent(this, NewConversationActivity.class);
nextIntent.putExtra(ConversationActivity.TEXT_EXTRA, destination.getBody());
Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show();
} else {
Recipient recipient = Recipient.from(this, Address.fromExternal(this, destination.getDestination()), true);
long threadId = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
nextIntent = new Intent(this, ConversationActivity.class);
nextIntent.putExtra(ConversationActivity.TEXT_EXTRA, destination.getBody());
nextIntent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
nextIntent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
}
return nextIntent;
}
private @NonNull DestinationAndBody getDestinationForSendTo(Intent intent) {
return new DestinationAndBody(intent.getData().getSchemeSpecificPart(),
intent.getStringExtra("sms_body"));
}
private @NonNull DestinationAndBody getDestinationForView(Intent intent) {
try {
Rfc5724Uri smsUri = new Rfc5724Uri(intent.getData().toString());
return new DestinationAndBody(smsUri.getPath(), smsUri.getQueryParams().get("body"));
} catch (URISyntaxException e) {
Log.w(TAG, "unable to parse RFC5724 URI from intent", e);
return new DestinationAndBody("", "");
}
}
private @NonNull DestinationAndBody getDestinationForSyncAdapter(Intent intent) {
Cursor cursor = null;
try {
cursor = getContentResolver().query(intent.getData(), null, null, null, null);
if (cursor != null && cursor.moveToNext()) {
return new DestinationAndBody(cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.Data.DATA1)), "");
}
return new DestinationAndBody("", "");
} finally {
if (cursor != null) cursor.close();
}
}
private static class DestinationAndBody {
private final String destination;
private final String body;
private DestinationAndBody(String destination, String body) {
this.destination = destination;
this.body = body;
}
public String getDestination() {
return destination;
}
public String getBody() {
return body;
}
}
}

View File

@ -1,396 +0,0 @@
/*
* Copyright (C) 2016 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;
import android.Manifest;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.components.webrtc.WebRtcAnswerDeclineButton;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallControls;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallScreen;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.SignalProtocolAddress;
import network.loki.messenger.R;
import static org.session.libsignal.libsignal.SessionCipher.SESSION_LOCK;
public class WebRtcCallActivity extends Activity {
private static final String TAG = WebRtcCallActivity.class.getSimpleName();
private static final int STANDARD_DELAY_FINISH = 1000;
public static final int BUSY_SIGNAL_DELAY_FINISH = 5500;
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
private WebRtcCallScreen callScreen;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate()");
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.webrtc_call_activity);
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
initializeResources();
}
@Override
public void onResume() {
Log.i(TAG, "onResume()");
super.onResume();
initializeScreenshotSecurity();
EventBus.getDefault().register(this);
}
@Override
public void onNewIntent(Intent intent){
Log.i(TAG, "onNewIntent");
if (ANSWER_ACTION.equals(intent.getAction())) {
handleAnswerCall();
} else if (DENY_ACTION.equals(intent.getAction())) {
handleDenyCall();
} else if (END_CALL_ACTION.equals(intent.getAction())) {
handleEndCall();
}
}
@Override
public void onPause() {
Log.i(TAG, "onPause");
super.onPause();
EventBus.getDefault().unregister(this);
}
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private void initializeScreenshotSecurity() {
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
private void initializeResources() {
callScreen = ViewUtil.findById(this, R.id.callScreen);
callScreen.setHangupButtonListener(new HangupButtonListener());
callScreen.setIncomingCallActionListener(new IncomingCallActionListener());
callScreen.setAudioMuteButtonListener(new AudioMuteButtonListener());
callScreen.setVideoMuteButtonListener(new VideoMuteButtonListener());
callScreen.setCameraFlipButtonListener(new CameraFlipButtonListener());
callScreen.setSpeakerButtonListener(new SpeakerButtonListener());
callScreen.setBluetoothButtonListener(new BluetoothButtonListener());
}
private void handleSetMuteAudio(boolean enabled) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_AUDIO);
intent.putExtra(WebRtcCallService.EXTRA_MUTE, enabled);
startService(intent);
}
private void handleSetMuteVideo(boolean muted) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_VIDEO);
intent.putExtra(WebRtcCallService.EXTRA_MUTE, muted);
startService(intent);
}
private void handleFlipCamera() {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA);
startService(intent);
}
private void handleAnswerCall() {
WebRtcViewModel event = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
if (event != null) {
Permissions.with(this)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, event.getRecipient().toShortString()),
R.drawable.ic_mic_white_48dp, R.drawable.ic_videocam_white_48dp)
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_answering));
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_ANSWER_CALL);
startService(intent);
})
.onAnyDenied(this::handleDenyCall)
.execute();
}
}
private void handleDenyCall() {
WebRtcViewModel event = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
if (event != null) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_DENY_CALL);
startService(intent);
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ending_call));
delayedFinish();
}
}
private void handleEndCall() {
Log.i(TAG, "Hangup pressed, handling termination now...");
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP);
startService(intent);
}
private void handleIncomingCall(@NonNull WebRtcViewModel event) {
callScreen.setIncomingCall(event.getRecipient());
}
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_dialing));
}
private void handleTerminate(@NonNull Recipient recipient /*, int terminationType */) {
Log.i(TAG, "handleTerminate called");
callScreen.setActiveCall(recipient, getString(R.string.RedPhone_ending_call));
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
delayedFinish();
}
private void handleCallRinging(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ringing));
}
private void handleCallBusy(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_busy));
delayedFinish(BUSY_SIGNAL_DELAY_FINISH);
}
private void handleCallConnected(@NonNull WebRtcViewModel event) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_connected), "", event.getLocalRenderer(), event.getRemoteRenderer());
}
private void handleRecipientUnavailable(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_recipient_unavailable));
delayedFinish();
}
private void handleServerFailure(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_network_failed));
delayedFinish();
}
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.RedPhone_number_not_registered);
dialog.setIconAttribute(R.attr.dialog_alert_icon);
dialog.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice);
dialog.setCancelable(true);
dialog.setPositiveButton(R.string.RedPhone_got_it, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
WebRtcCallActivity.this.handleTerminate(event.getRecipient());
}
});
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
WebRtcCallActivity.this.handleTerminate(event.getRecipient());
}
});
dialog.show();
}
private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) {
final IdentityKey theirIdentity = event.getIdentityKey();
final Recipient recipient = event.getRecipient();
callScreen.setUntrustedIdentity(recipient, theirIdentity);
callScreen.setAcceptIdentityListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
synchronized (SESSION_LOCK) {
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(WebRtcCallActivity.this);
identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.getAddress().serialize(), 1), theirIdentity, true);
}
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, recipient.getAddress());
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
startService(intent);
}
});
callScreen.setCancelIdentityButton(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleTerminate(recipient);
}
});
}
private void delayedFinish() {
delayedFinish(STANDARD_DELAY_FINISH);
}
private void delayedFinish(int delayMillis) {
callScreen.postDelayed(new Runnable() {
public void run() {
WebRtcCallActivity.this.finish();
}
}, delayMillis);
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(final WebRtcViewModel event) {
Log.i(TAG, "Got message from service: " + event);
switch (event.getState()) {
case CALL_CONNECTED: handleCallConnected(event); break;
case NETWORK_FAILURE: handleServerFailure(event); break;
case CALL_RINGING: handleCallRinging(event); break;
case CALL_DISCONNECTED: handleTerminate(event.getRecipient()); break;
case NO_SUCH_USER: handleNoSuchUser(event); break;
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(event); break;
case CALL_INCOMING: handleIncomingCall(event); break;
case CALL_OUTGOING: handleOutgoingCall(event); break;
case CALL_BUSY: handleCallBusy(event); break;
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
}
callScreen.setRemoteVideoEnabled(event.isRemoteVideoEnabled());
callScreen.updateAudioState(event.isBluetoothAvailable(), event.isMicrophoneEnabled());
callScreen.setControlsEnabled(event.getState() != WebRtcViewModel.State.CALL_INCOMING);
callScreen.setLocalVideoState(event.getLocalCameraState());
}
private class HangupButtonListener implements WebRtcCallScreen.HangupButtonListener {
public void onClick() {
handleEndCall();
}
}
private class AudioMuteButtonListener implements WebRtcCallControls.MuteButtonListener {
@Override
public void onToggle(boolean isMuted) {
WebRtcCallActivity.this.handleSetMuteAudio(isMuted);
}
}
private class VideoMuteButtonListener implements WebRtcCallControls.MuteButtonListener {
@Override
public void onToggle(boolean isMuted) {
WebRtcCallActivity.this.handleSetMuteVideo(isMuted);
}
}
private class CameraFlipButtonListener implements WebRtcCallControls.CameraFlipButtonListener {
@Override
public void onToggle() {
WebRtcCallActivity.this.handleFlipCamera();
}
}
private class SpeakerButtonListener implements WebRtcCallControls.SpeakerButtonListener {
@Override
public void onSpeakerChange(boolean isSpeaker) {
AudioManager audioManager = ServiceUtil.getAudioManager(WebRtcCallActivity.this);
audioManager.setSpeakerphoneOn(isSpeaker);
if (isSpeaker && audioManager.isBluetoothScoOn()) {
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
}
}
}
private class BluetoothButtonListener implements WebRtcCallControls.BluetoothButtonListener {
@Override
public void onBluetoothChange(boolean isBluetooth) {
AudioManager audioManager = ServiceUtil.getAudioManager(WebRtcCallActivity.this);
if (isBluetooth) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
}
}
}
private class IncomingCallActionListener implements WebRtcAnswerDeclineButton.AnswerDeclineListener {
@Override
public void onAnswered() {
WebRtcCallActivity.this.handleAnswerCall();
}
@Override
public void onDeclined() {
WebRtcCallActivity.this.handleDenyCall();
}
}
}

View File

@ -1,34 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
import android.view.View.OnClickListener;
import org.thoughtcrime.securesms.RegistrationActivity;
import network.loki.messenger.R;
public class PushRegistrationReminder extends Reminder {
public PushRegistrationReminder(final Context context) {
super(context.getString(R.string.reminder_header_push_title),
context.getString(R.string.reminder_header_push_text));
final OnClickListener okListener = v -> {
Intent intent = new Intent(context, RegistrationActivity.class);
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
context.startActivity(intent);
};
setOkListener(okListener);
}
@Override
public boolean isDismissable() {
return false;
}
public static boolean isEligible(Context context) {
return false;
}
}

View File

@ -1,50 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.View.OnClickListener;
import org.thoughtcrime.securesms.InviteActivity;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class ShareReminder extends Reminder {
public ShareReminder(final @NonNull Context context) {
super(context.getString(R.string.reminder_header_share_title),
context.getString(R.string.reminder_header_share_text));
setDismissListener(new OnClickListener() {
@Override public void onClick(View v) {
TextSecurePreferences.setPromptedShare(context, true);
}
});
setOkListener(new OnClickListener() {
@Override public void onClick(View v) {
TextSecurePreferences.setPromptedShare(context, true);
context.startActivity(new Intent(context, InviteActivity.class));
}
});
}
public static boolean isEligible(final @NonNull Context context) {
if (!TextSecurePreferences.isPushRegistered(context) ||
TextSecurePreferences.hasPromptedShare(context))
{
return false;
}
Cursor cursor = null;
try {
cursor = DatabaseFactory.getThreadDatabase(context).getConversationList();
return cursor.getCount() >= 1;
} finally {
if (cursor != null) cursor.close();
}
}
}

View File

@ -1,42 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.DatabaseMigrationActivity;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
public class SystemSmsImportReminder extends Reminder {
public SystemSmsImportReminder(final Context context) {
super(context.getString(R.string.reminder_header_sms_import_title),
context.getString(R.string.reminder_header_sms_import_text));
final OnClickListener okListener = v -> {
Intent intent = new Intent(context, ApplicationMigrationService.class);
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
context.startService(intent);
Intent nextIntent = new Intent(context, ConversationListActivity.class);
Intent activityIntent = new Intent(context, DatabaseMigrationActivity.class);
activityIntent.putExtra("next_intent", nextIntent);
context.startActivity(activityIntent);
};
final OnClickListener cancelListener = new OnClickListener() {
@Override
public void onClick(View v) {
ApplicationMigrationService.setDatabaseImported(context);
}
};
setOkListener(okListener);
setDismissListener(cancelListener);
}
public static boolean isEligible(Context context) {
return !ApplicationMigrationService.isDatabaseImported(context);
}
}

View File

@ -1,32 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.content.Intent;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.RegistrationActivity;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class UnauthorizedReminder extends Reminder {
public UnauthorizedReminder(final Context context) {
super(context.getString(R.string.UnauthorizedReminder_device_no_longer_registered),
context.getString(R.string.UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device));
setOkListener(v -> {
Intent intent = new Intent(context, RegistrationActivity.class);
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
context.startActivity(intent);
});
}
@Override
public boolean isDismissable() {
return false;
}
public static boolean isEligible(Context context) {
return TextSecurePreferences.isUnauthorizedRecieved(context);
}
}

View File

@ -1,352 +0,0 @@
/*
* Copyright (C) 2016 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.components.webrtc;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.core.view.ViewCompat;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VerifySpan;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.webrtc.SurfaceViewRenderer;
import org.session.libsignal.libsignal.IdentityKey;
/**
* A UI widget that encapsulates the entire in-call screen
* for both initiators and responders.
*
* @author Moxie Marlinspike
*
*/
public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedListener {
@SuppressWarnings("unused")
private static final String TAG = WebRtcCallScreen.class.getSimpleName();
private ImageView photo;
private SurfaceViewRenderer localRenderer;
private PercentFrameLayout localRenderLayout;
private PercentFrameLayout remoteRenderLayout;
private TextView name;
private TextView phoneNumber;
private TextView label;
private TextView elapsedTime;
private View untrustedIdentityContainer;
private TextView untrustedIdentityExplanation;
private Button acceptIdentityButton;
private Button cancelIdentityButton;
private TextView status;
private FloatingActionButton endCallButton;
private WebRtcCallControls controls;
private RelativeLayout expandedInfo;
private ViewGroup callHeader;
private WebRtcAnswerDeclineButton incomingCallButton;
private Recipient recipient;
private boolean minimized;
public WebRtcCallScreen(Context context) {
super(context);
initialize();
}
public WebRtcCallScreen(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public WebRtcCallScreen(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @Nullable String sas, SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
setCard(personInfo, message);
setConnected(localRenderer, remoteRenderer);
incomingCallButton.stopRingingAnimation();
incomingCallButton.setVisibility(View.GONE);
endCallButton.show();
}
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message) {
setCard(personInfo, message);
incomingCallButton.stopRingingAnimation();
incomingCallButton.setVisibility(View.GONE);
endCallButton.show();
}
public void setIncomingCall(Recipient personInfo) {
setCard(personInfo, getContext().getString(R.string.CallScreen_Incoming_call));
endCallButton.hide();
incomingCallButton.setVisibility(View.VISIBLE);
incomingCallButton.startRingingAnimation();
}
public void setUntrustedIdentity(Recipient personInfo, IdentityKey untrustedIdentity) {
String name = recipient.toShortString();
String introduction = String.format(getContext().getString(R.string.WebRtcCallScreen_new_safety_numbers), name, name);
SpannableString spannableString = new SpannableString(introduction + " " + getContext().getString(R.string.WebRtcCallScreen_you_may_wish_to_verify_this_contact));
spannableString.setSpan(new VerifySpan(getContext(), personInfo.getAddress(), untrustedIdentity),
introduction.length()+1, spannableString.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setPersonInfo(personInfo);
incomingCallButton.stopRingingAnimation();
incomingCallButton.setVisibility(View.GONE);
this.status.setText(R.string.WebRtcCallScreen_new_safety_number_title);
this.untrustedIdentityContainer.setVisibility(View.VISIBLE);
this.untrustedIdentityExplanation.setText(spannableString);
this.untrustedIdentityExplanation.setMovementMethod(LinkMovementMethod.getInstance());
this.endCallButton.hide();
}
public void setIncomingCallActionListener(WebRtcAnswerDeclineButton.AnswerDeclineListener listener) {
incomingCallButton.setAnswerDeclineListener(listener);
}
public void setAudioMuteButtonListener(WebRtcCallControls.MuteButtonListener listener) {
this.controls.setAudioMuteButtonListener(listener);
}
public void setVideoMuteButtonListener(WebRtcCallControls.MuteButtonListener listener) {
this.controls.setVideoMuteButtonListener(listener);
}
public void setCameraFlipButtonListener(WebRtcCallControls.CameraFlipButtonListener listener) {
this.controls.setCameraFlipButtonListener(listener);
}
public void setSpeakerButtonListener(WebRtcCallControls.SpeakerButtonListener listener) {
this.controls.setSpeakerButtonListener(listener);
}
public void setBluetoothButtonListener(WebRtcCallControls.BluetoothButtonListener listener) {
this.controls.setBluetoothButtonListener(listener);
}
public void setHangupButtonListener(final HangupButtonListener listener) {
endCallButton.setOnClickListener(v -> listener.onClick());
}
public void setAcceptIdentityListener(OnClickListener listener) {
this.acceptIdentityButton.setOnClickListener(listener);
}
public void setCancelIdentityButton(OnClickListener listener) {
this.cancelIdentityButton.setOnClickListener(listener);
}
public void updateAudioState(boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
this.controls.updateAudioState(isBluetoothAvailable);
this.controls.setMicrophoneEnabled(isMicrophoneEnabled);
}
public void setControlsEnabled(boolean enabled) {
this.controls.setControlsEnabled(enabled);
}
public void setLocalVideoState(@NonNull CameraState cameraState) {
this.controls.setVideoAvailable(cameraState.getCameraCount() > 0);
this.controls.setVideoEnabled(cameraState.isEnabled());
this.controls.setCameraFlipAvailable(cameraState.getCameraCount() > 1);
this.controls.setCameraFlipClickable(cameraState.getActiveDirection() != CameraState.Direction.PENDING);
this.controls.setCameraFlipButtonEnabled(cameraState.getActiveDirection() == CameraState.Direction.BACK);
if (this.localRenderer != null) {
this.localRenderer.setMirror(cameraState.getActiveDirection() == CameraState.Direction.FRONT);
}
if (this.localRenderLayout.isHidden() == cameraState.isEnabled()) {
this.localRenderLayout.setHidden(!cameraState.isEnabled());
this.localRenderLayout.requestLayout();
this.localRenderer.setVisibility(cameraState.isEnabled() ? VISIBLE : INVISIBLE);
}
}
public void setRemoteVideoEnabled(boolean enabled) {
if (enabled && this.remoteRenderLayout.isHidden()) {
this.photo.setVisibility(View.INVISIBLE);
setMinimized(true);
this.remoteRenderLayout.setHidden(false);
this.remoteRenderLayout.requestLayout();
if (localRenderLayout.isHidden()) this.controls.displayVideoTooltip(callHeader);
} else if (!enabled && !this.remoteRenderLayout.isHidden()){
setMinimized(false);
this.photo.setVisibility(View.VISIBLE);
this.remoteRenderLayout.setHidden(true);
this.remoteRenderLayout.requestLayout();
}
}
public boolean isVideoEnabled() {
return controls.isVideoEnabled();
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.webrtc_call_screen, this, true);
this.elapsedTime = findViewById(R.id.elapsedTime);
this.photo = findViewById(R.id.photo);
this.localRenderLayout = findViewById(R.id.local_render_layout);
this.remoteRenderLayout = findViewById(R.id.remote_render_layout);
this.phoneNumber = findViewById(R.id.phoneNumber);
this.name = findViewById(R.id.name);
this.label = findViewById(R.id.label);
this.status = findViewById(R.id.callStateLabel);
this.controls = findViewById(R.id.inCallControls);
this.endCallButton = findViewById(R.id.hangup_fab);
this.incomingCallButton = findViewById(R.id.answer_decline_button);
this.untrustedIdentityContainer = findViewById(R.id.untrusted_layout);
this.untrustedIdentityExplanation = findViewById(R.id.untrusted_explanation);
this.acceptIdentityButton = findViewById(R.id.accept_safety_numbers);
this.cancelIdentityButton = findViewById(R.id.cancel_safety_numbers);
this.expandedInfo = findViewById(R.id.expanded_info);
this.callHeader = findViewById(R.id.call_info_1);
this.localRenderLayout.setHidden(true);
this.remoteRenderLayout.setHidden(true);
this.minimized = false;
this.remoteRenderLayout.setOnClickListener(v -> setMinimized(!minimized));
}
private void setConnected(SurfaceViewRenderer localRenderer,
SurfaceViewRenderer remoteRenderer)
{
if (localRenderLayout.getChildCount() == 0 && remoteRenderLayout.getChildCount() == 0) {
if (localRenderer.getParent() != null) {
((ViewGroup)localRenderer.getParent()).removeView(localRenderer);
}
if (remoteRenderer.getParent() != null) {
((ViewGroup)remoteRenderer.getParent()).removeView(remoteRenderer);
}
localRenderLayout.setPosition(7, 70, 25, 25);
localRenderLayout.setSquare(true);
remoteRenderLayout.setPosition(0, 0, 100, 100);
localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
remoteRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
localRenderer.setMirror(true);
localRenderer.setZOrderMediaOverlay(true);
localRenderLayout.addView(localRenderer);
remoteRenderLayout.addView(remoteRenderer);
this.localRenderer = localRenderer;
}
}
private void setPersonInfo(final @NonNull Recipient recipient) {
this.recipient = recipient;
this.recipient.addListener(this);
GlideApp.with(getContext().getApplicationContext())
.load(recipient.getContactPhoto())
.fallback(recipient.getFallbackContactPhoto().asCallCard(getContext()))
.error(recipient.getFallbackContactPhoto().asCallCard(getContext()))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(this.photo);
this.name.setText(recipient.getName());
if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) {
this.phoneNumber.setText(recipient.getAddress().serialize() + " (~" + recipient.getProfileName() + ")");
} else {
this.phoneNumber.setText(recipient.getAddress().serialize());
}
}
private void setCard(Recipient recipient, String status) {
setPersonInfo(recipient);
this.status.setText(status);
this.untrustedIdentityContainer.setVisibility(View.GONE);
}
private void setMinimized(boolean minimized) {
if (minimized) {
ViewCompat.animate(callHeader).translationY(-1 * expandedInfo.getHeight());
ViewCompat.animate(status).alpha(0);
ViewCompat.animate(endCallButton).translationY(endCallButton.getHeight() + ViewUtil.dpToPx(getContext(), 40));
ViewCompat.animate(endCallButton).alpha(0);
this.minimized = true;
} else {
ViewCompat.animate(callHeader).translationY(0);
ViewCompat.animate(status).alpha(1);
ViewCompat.animate(endCallButton).translationY(0);
ViewCompat.animate(endCallButton).alpha(1).withEndAction(() -> {
// Note: This is to work around an Android bug, see #6225
endCallButton.requestLayout();
});
this.minimized = false;
}
}
@Override
public void onModified(Recipient recipient) {
Util.runOnMain(() -> {
if (recipient == WebRtcCallScreen.this.recipient) {
setPersonInfo(recipient);
}
});
}
public interface HangupButtonListener {
void onClick();
}
}

View File

@ -1,248 +0,0 @@
package org.thoughtcrime.securesms.contactshare;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.Toolbar;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
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.Map;
import network.loki.messenger.R;
import static org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
public class SharedContactDetailsActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener {
private static final int CODE_ADD_EDIT_CONTACT = 2323;
private static final String KEY_CONTACT = "contact";
private ContactFieldAdapter contactFieldAdapter;
private TextView nameView;
private TextView numberView;
private ImageView avatarView;
private View addButtonView;
private View inviteButtonView;
private ViewGroup engageContainerView;
private View messageButtonView;
private View callButtonView;
private GlideRequests glideRequests;
private Contact contact;
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private final Map<String, Recipient> activeRecipients = new HashMap<>();
public static Intent getIntent(@NonNull Context context, @NonNull Contact contact) {
Intent intent = new Intent(context, SharedContactDetailsActivity.class);
intent.putExtra(KEY_CONTACT, contact);
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_shared_contact_details);
if (getIntent() == null) {
throw new IllegalStateException("You must supply arguments to this activity. Please use the #getIntent() method.");
}
contact = getIntent().getParcelableExtra(KEY_CONTACT);
if (contact == null) {
throw new IllegalStateException("You must supply a contact to this activity. Please use the #getIntent() method.");
}
initToolbar();
initViews();
presentContact(contact);
presentActionButtons(ContactUtil.getRecipients(this, contact));
presentAvatar(contact.getAvatarAttachment() != null ? contact.getAvatarAttachment().getDataUri() : null);
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onCreate(this);
dynamicTheme.onResume(this);
}
private void initToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setLogo(null);
getSupportActionBar().setTitle("");
toolbar.setNavigationOnClickListener(v -> onBackPressed());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int[] attrs = {R.attr.shared_contact_details_titlebar};
TypedArray array = obtainStyledAttributes(attrs);
int color = array.getResourceId(0, android.R.color.black);
array.recycle();
getWindow().setStatusBarColor(getResources().getColor(color));
}
}
private void initViews() {
nameView = findViewById(R.id.contact_details_name);
numberView = findViewById(R.id.contact_details_number);
avatarView = findViewById(R.id.contact_details_avatar);
addButtonView = findViewById(R.id.contact_details_add_button);
inviteButtonView = findViewById(R.id.contact_details_invite_button);
engageContainerView = findViewById(R.id.contact_details_engage_container);
messageButtonView = findViewById(R.id.contact_details_message_button);
callButtonView = findViewById(R.id.contact_details_call_button);
contactFieldAdapter = new ContactFieldAdapter(dynamicLanguage.getCurrentLocale(), glideRequests, false);
RecyclerView list = findViewById(R.id.contact_details_fields);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(contactFieldAdapter);
glideRequests = GlideApp.with(this);
}
@Override
public void onModified(Recipient recipient) {
Util.runOnMain(() -> presentActionButtons(Collections.singletonList(recipient)));
}
@SuppressLint("StaticFieldLeak")
private void presentContact(@Nullable Contact contact) {
this.contact = contact;
if (contact != null) {
nameView.setText(ContactUtil.getDisplayName(contact));
numberView.setText(ContactUtil.getDisplayNumber(contact, dynamicLanguage.getCurrentLocale()));
addButtonView.setOnClickListener(v -> {
new AsyncTask<Void, Void, Intent>() {
@Override
protected Intent doInBackground(Void... voids) {
return ContactUtil.buildAddToContactsIntent(SharedContactDetailsActivity.this, contact);
}
@Override
protected void onPostExecute(Intent intent) {
startActivityForResult(intent, CODE_ADD_EDIT_CONTACT);
}
}.execute();
});
contactFieldAdapter.setFields(this, null, contact.getPhoneNumbers(), contact.getEmails(), contact.getPostalAddresses());
} else {
nameView.setText("");
numberView.setText("");
}
}
public void presentAvatar(@Nullable Uri uri) {
if (uri != null) {
glideRequests.load(new DecryptableUri(uri))
.fallback(R.drawable.ic_contact_picture)
.circleCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.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()) {
engageContainerView.setVisibility(View.VISIBLE);
inviteButtonView.setVisibility(View.GONE);
messageButtonView.setOnClickListener(v -> {
ContactUtil.selectRecipientThroughDialog(this, pushUsers, dynamicLanguage.getCurrentLocale(), recipient -> {
CommunicationActions.startConversation(this, recipient, null);
});
});
callButtonView.setOnClickListener(v -> {
ContactUtil.selectRecipientThroughDialog(this, pushUsers, dynamicLanguage.getCurrentLocale(), recipient -> CommunicationActions.startVoiceCall(this, recipient));
});
} else if (!systemUsers.isEmpty()) {
inviteButtonView.setVisibility(View.VISIBLE);
engageContainerView.setVisibility(View.GONE);
inviteButtonView.setOnClickListener(v -> {
ContactUtil.selectRecipientThroughDialog(this, systemUsers, dynamicLanguage.getCurrentLocale(), recipient -> {
CommunicationActions.composeSmsThroughDefaultApp(this, recipient.getAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
});
});
} else {
inviteButtonView.setVisibility(View.GONE);
engageContainerView.setVisibility(View.GONE);
}
}
private void clearView() {
nameView.setText("");
numberView.setText("");
inviteButtonView.setVisibility(View.GONE);
engageContainerView.setVisibility(View.GONE);
}
}

View File

@ -91,7 +91,6 @@ import org.thoughtcrime.securesms.MediaOverviewActivity;
import org.thoughtcrime.securesms.MuteDialog;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.PromptMmsActivity;
import org.thoughtcrime.securesms.RegistrationActivity;
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
@ -119,7 +118,6 @@ import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.InviteReminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contactshare.Contact;
@ -215,7 +213,6 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
@ -230,7 +227,6 @@ import org.thoughtcrime.securesms.util.views.Stub;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsignal.service.loki.protocol.mentions.Mention;
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager;
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol;
@ -315,7 +311,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private ConversationFragment fragment;
private Button unblockButton;
private Button makeDefaultSmsButton;
private Button registerButton;
private InputAwareLayout container;
protected Stub<ReminderView> reminderView;
private Stub<UnverifiedBannerView> unverifiedBannerView;
@ -1032,12 +1027,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
startActivityForResult(intent, SMS_DEFAULT);
}
private void handleRegisterForSignal() {
Intent intent = new Intent(this, RegistrationActivity.class);
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
startActivity(intent);
}
private void handleInviteLink() {
String inviteText = getString(R.string.ConversationActivity_lets_switch_to_signal, getString(R.string.install_url));
@ -1256,25 +1245,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void handleDial(final Recipient recipient, boolean isSecure) {
if (recipient == null) return;
if (isSecure) {
CommunicationActions.startVoiceCall(this, recipient);
} else {
try {
Intent dialIntent = new Intent(Intent.ACTION_DIAL,
Uri.parse("tel:" + recipient.getAddress().serialize()));
startActivity(dialIntent);
} catch (ActivityNotFoundException anfe) {
Log.w(TAG, anfe);
Dialogs.showAlertDialog(this,
getString(R.string.ConversationActivity_calls_not_supported),
getString(R.string.ConversationActivity_this_device_does_not_appear_to_support_dial_actions));
}
}
}
private void handleDisplayGroupRecipients() {
new GroupMembersDialog(this, getRecipient()).display();
}
@ -1566,9 +1536,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
protected void updateReminders(boolean seenInvite) {
Log.i(TAG, "updateReminders(" + seenInvite + ")");
if (UnauthorizedReminder.isEligible(this)) {
reminderView.get().showReminder(new UnauthorizedReminder(this));
} else if (ExpiredBuildReminder.isEligible()) {
if (ExpiredBuildReminder.isEligible()) {
reminderView.get().showReminder(new ExpiredBuildReminder(this));
} else if (ServiceOutageReminder.isEligible(this)) {
ApplicationContext.getInstance(this).getJobManager().add(new ServiceOutageDetectionJob());
@ -1686,7 +1654,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
emojiDrawerStub = ViewUtil.findStubById(this, R.id.emoji_drawer_stub);
unblockButton = ViewUtil.findById(this, R.id.unblock_button);
makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button);
registerButton = ViewUtil.findById(this, R.id.register_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);
@ -1738,7 +1705,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
*/
unblockButton.setOnClickListener(v -> handleUnblock());
makeDefaultSmsButton.setOnClickListener(v -> handleMakeDefaultSms());
registerButton.setOnClickListener(v -> handleRegisterForSignal());
composeText.setOnKeyListener(composeKeyPressedListener);
composeText.addTextChangedListener(composeKeyPressedListener);
@ -2132,27 +2098,22 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
unblockButton.setVisibility(View.GONE);
inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
} else if (recipient.isBlocked()) {
unblockButton.setVisibility(View.VISIBLE);
inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
} else if (!isSecureText && isPushGroupConversation()) {
unblockButton.setVisibility(View.GONE);
inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.VISIBLE);
} else if (!isSecureText && !isDefaultSms) {
unblockButton.setVisibility(View.GONE);
inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
} else {
inputPanel.setVisibility(View.VISIBLE);
unblockButton.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE);
}
}

View File

@ -68,7 +68,6 @@ import org.thoughtcrime.securesms.components.ConversationTypingView;
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity;
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
import org.thoughtcrime.securesms.database.Address;
@ -1035,49 +1034,6 @@ public class ConversationFragment extends Fragment
startActivity(StickerPackPreviewActivity.getIntent(sticker.getPackId(), sticker.getPackKey()));
}
}
@Override
public void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView) {
if (getContext() != null && getActivity() != null) {
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), avatarTransitionView, "avatar").toBundle();
ActivityCompat.startActivity(getActivity(), SharedContactDetailsActivity.getIntent(getContext(), contact), bundle);
}
}
@Override
public void onAddToContactsClicked(@NonNull Contact contactWithAvatar) {
if (getContext() != null) {
new AsyncTask<Void, Void, Intent>() {
@Override
protected Intent doInBackground(Void... voids) {
return ContactUtil.buildAddToContactsIntent(getContext(), contactWithAvatar);
}
@Override
protected void onPostExecute(Intent intent) {
startActivityForResult(intent, CODE_ADD_EDIT_CONTACT);
}
}.execute();
}
}
@Override
public void onMessageSharedContactClicked(@NonNull List<Recipient> choices) {
if (getContext() == null) return;
ContactUtil.selectRecipientThroughDialog(getContext(), choices, locale, recipient -> {
CommunicationActions.startConversation(getContext(), recipient, null);
});
}
@Override
public void onInviteSharedContactClicked(@NonNull List<Recipient> choices) {
if (getContext() == null) return;
ContactUtil.selectRecipientThroughDialog(getContext(), choices, locale, recipient -> {
CommunicationActions.composeSmsThroughDefaultApp(getContext(), recipient.getAddress(), getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
});
}
}
private class ActionModeCallback implements ActionMode.Callback {

View File

@ -161,9 +161,8 @@ public class ConversationItem extends TapJackingProofLinearLayout
private @NonNull Set<MessageRecord> batchSelected = new HashSet<>();
private Recipient conversationRecipient;
private Stub<ConversationItemThumbnail> mediaThumbnailStub;
private Stub<MessageAudioView> audioViewStub;
private Stub<MessageAudioView> audioViewStub;
private Stub<DocumentView> documentViewStub;
private Stub<SharedContactView> sharedContactStub;
private Stub<LinkPreviewView> linkPreviewStub;
private Stub<StickerView> stickerStub;
private @Nullable EventListener eventListener;
@ -174,8 +173,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
private final AttachmentDownloadClickListener downloadClickListener = new AttachmentDownloadClickListener();
private final SlideClickPassthroughListener singleDownloadClickListener = new SlideClickPassthroughListener(downloadClickListener);
private final SharedContactEventListener sharedContactEventListener = new SharedContactEventListener();
private final SharedContactClickListener sharedContactClickListener = new SharedContactClickListener();
private final LinkPreviewClickListener linkPreviewClickListener = new LinkPreviewClickListener();
private final Context context;
@ -213,7 +210,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub));
this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub));
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
this.sharedContactStub = new Stub<>(findViewById(R.id.shared_contact_view_stub));
this.linkPreviewStub = new Stub<>(findViewById(R.id.link_preview_stub));
this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub));
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
@ -417,7 +413,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
return hasThumbnail(messageRecord) &&
!hasAudio(messageRecord) &&
!hasDocument(messageRecord) &&
!hasSharedContact(messageRecord) &&
!hasSticker(messageRecord);
}
@ -426,7 +421,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
!hasThumbnail(messageRecord) &&
!hasAudio(messageRecord) &&
hasDocument(messageRecord) &&
!hasSharedContact(messageRecord) &&
!hasSticker(messageRecord) &&
!hasQuote(messageRecord);
}
@ -436,7 +430,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
!hasThumbnail(messageRecord) &&
!hasAudio(messageRecord) &&
!hasDocument(messageRecord) &&
!hasSharedContact(messageRecord) &&
!hasSticker(messageRecord) &&
!hasQuote(messageRecord);
}
@ -456,10 +449,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getQuote() != null;
}
private boolean hasSharedContact(MessageRecord messageRecord) {
return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getSharedContacts().isEmpty();
}
private boolean hasLinkPreview(MessageRecord messageRecord) {
return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getLinkPreviews().isEmpty();
}
@ -547,30 +536,11 @@ public class ConversationItem extends TapJackingProofLinearLayout
{
boolean showControls = !messageRecord.isFailed();
if (hasSharedContact(messageRecord)) {
sharedContactStub.get().setVisibility(VISIBLE);
if (audioViewStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
sharedContactStub.get().setContact(((MediaMmsMessageRecord) messageRecord).getSharedContacts().get(0), glideRequests, locale);
sharedContactStub.get().setEventListener(sharedContactEventListener);
sharedContactStub.get().setOnClickListener(sharedContactClickListener);
sharedContactStub.get().setOnLongClickListener(passthroughClickListener);
setSharedContactCorners(messageRecord, previousRecord, nextRecord, isGroupThread);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footer.setVisibility(GONE);
} else if (hasLinkPreview(messageRecord)) {
if (hasLinkPreview(messageRecord)) {
linkPreviewStub.get().setVisibility(View.VISIBLE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
@ -606,7 +576,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
audioViewStub.get().setVisibility(View.VISIBLE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
@ -623,7 +592,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
documentViewStub.get().setVisibility(View.VISIBLE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
@ -644,7 +612,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
//noinspection ConstantConditions
@ -662,7 +629,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
@ -691,7 +657,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE);
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
@ -764,17 +729,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
mediaThumbnailStub.get().setCorners(topLeft, topRight, bottomRight, bottomLeft);
}
private void setSharedContactCorners(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread) {
if (isSingularMessage(current, previous, next, isGroupThread) || isEndOfMessageCluster(current, next, isGroupThread)) {
sharedContactStub.get().setSingularStyle();
} else if (current.isOutgoing()) {
sharedContactStub.get().setClusteredOutgoingStyle();
} else {
sharedContactStub.get().setClusteredIncomingStyle();
}
}
private void setLinkPreviewCorners(@NonNull MessageRecord current, @NonNull Optional<MessageRecord> previous, @NonNull Optional<MessageRecord> next, boolean isGroupThread, boolean bigImage) {
int defaultRadius = readDimen(R.dimen.message_corner_radius);
int collapseRadius = readDimen(R.dimen.message_corner_collapse_radius);
@ -910,7 +864,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
footer.setVisibility(GONE);
stickerFooter.setVisibility(GONE);
if (sharedContactStub.resolved()) sharedContactStub.get().getFooter().setVisibility(GONE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().getFooter().setVisibility(GONE);
boolean differentTimestamps = next.isPresent() && !DateUtils.isSameExtendedRelativeTimestamp(context, locale, next.get().getTimestamp(), current.getTimestamp());
@ -927,8 +880,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) {
if (hasSticker(messageRecord)) {
return stickerFooter;
} else if (hasSharedContact(messageRecord)) {
return sharedContactStub.get().getFooter();
} else if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getDisplayBody(getContext()))) {
return mediaThumbnailStub.get().getFooter();
} else {
@ -1150,46 +1101,6 @@ public class ConversationItem extends TapJackingProofLinearLayout
});
}
private class SharedContactEventListener implements SharedContactView.EventListener {
@Override
public void onAddToContactsClicked(@NonNull Contact contact) {
if (eventListener != null && batchSelected.isEmpty()) {
eventListener.onAddToContactsClicked(contact);
} else {
passthroughClickListener.onClick(sharedContactStub.get());
}
}
@Override
public void onInviteClicked(@NonNull List<Recipient> choices) {
if (eventListener != null && batchSelected.isEmpty()) {
eventListener.onInviteSharedContactClicked(choices);
} else {
passthroughClickListener.onClick(sharedContactStub.get());
}
}
@Override
public void onMessageClicked(@NonNull List<Recipient> choices) {
if (eventListener != null && batchSelected.isEmpty()) {
eventListener.onMessageSharedContactClicked(choices);
} else {
passthroughClickListener.onClick(sharedContactStub.get());
}
}
}
private class SharedContactClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getSharedContacts().isEmpty()) {
eventListener.onSharedContactDetailsClicked(((MmsMessageRecord) messageRecord).getSharedContacts().get(0), sharedContactStub.get().getAvatarView());
} else {
passthroughClickListener.onClick(view);
}
}
}
private class LinkPreviewClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {

View File

@ -51,7 +51,6 @@ import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.MessageSenderEventListener;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository;
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
import org.thoughtcrime.securesms.util.RealtimeSleepTimer;
@ -88,7 +87,6 @@ import network.loki.messenger.BuildConfig;
PushGroupUpdateJob.class,
AvatarDownloadJob.class,
RotateSignedPreKeyJob.class,
WebRtcCallService.class,
RetrieveProfileJob.class,
MultiDeviceVerifiedUpdateJob.class,
CreateProfileActivity.class,

View File

@ -1,123 +0,0 @@
package org.thoughtcrime.securesms.events;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.webrtc.SurfaceViewRenderer;
import org.session.libsignal.libsignal.IdentityKey;
public class WebRtcViewModel {
public enum State {
// Normal states
CALL_INCOMING,
CALL_OUTGOING,
CALL_CONNECTED,
CALL_RINGING,
CALL_BUSY,
CALL_DISCONNECTED,
// Error states
NETWORK_FAILURE,
RECIPIENT_UNAVAILABLE,
NO_SUCH_USER,
UNTRUSTED_IDENTITY,
}
private final @NonNull State state;
private final @NonNull Recipient recipient;
private final @Nullable IdentityKey identityKey;
private final boolean remoteVideoEnabled;
private final boolean isBluetoothAvailable;
private final boolean isMicrophoneEnabled;
private final CameraState localCameraState;
private final SurfaceViewRenderer localRenderer;
private final SurfaceViewRenderer remoteRenderer;
public WebRtcViewModel(@NonNull State state,
@NonNull Recipient recipient,
@NonNull CameraState localCameraState,
@NonNull SurfaceViewRenderer localRenderer,
@NonNull SurfaceViewRenderer remoteRenderer,
boolean remoteVideoEnabled,
boolean isBluetoothAvailable,
boolean isMicrophoneEnabled)
{
this(state,
recipient,
null,
localCameraState,
localRenderer,
remoteRenderer,
remoteVideoEnabled,
isBluetoothAvailable,
isMicrophoneEnabled);
}
public WebRtcViewModel(@NonNull State state,
@NonNull Recipient recipient,
@Nullable IdentityKey identityKey,
@NonNull CameraState localCameraState,
@NonNull SurfaceViewRenderer localRenderer,
@NonNull SurfaceViewRenderer remoteRenderer,
boolean remoteVideoEnabled,
boolean isBluetoothAvailable,
boolean isMicrophoneEnabled)
{
this.state = state;
this.recipient = recipient;
this.localCameraState = localCameraState;
this.localRenderer = localRenderer;
this.remoteRenderer = remoteRenderer;
this.identityKey = identityKey;
this.remoteVideoEnabled = remoteVideoEnabled;
this.isBluetoothAvailable = isBluetoothAvailable;
this.isMicrophoneEnabled = isMicrophoneEnabled;
}
public @NonNull State getState() {
return state;
}
public @NonNull Recipient getRecipient() {
return recipient;
}
public @NonNull CameraState getLocalCameraState() {
return localCameraState;
}
public @Nullable IdentityKey getIdentityKey() {
return identityKey;
}
public boolean isRemoteVideoEnabled() {
return remoteVideoEnabled;
}
public boolean isBluetoothAvailable() {
return isBluetoothAvailable;
}
public boolean isMicrophoneEnabled() {
return isMicrophoneEnabled;
}
public SurfaceViewRenderer getLocalRenderer() {
return localRenderer;
}
public SurfaceViewRenderer getRemoteRenderer() {
return remoteRenderer;
}
public @NonNull String toString() {
return "[State: " + state + ", recipient: " + recipient.getAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]";
}
}

View File

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.text.TextUtils;
import android.util.Pair;
@ -86,7 +85,6 @@ import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
@ -111,12 +109,6 @@ import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
import org.session.libsignal.service.api.messages.calls.AnswerMessage;
import org.session.libsignal.service.api.messages.calls.BusyMessage;
import org.session.libsignal.service.api.messages.calls.HangupMessage;
import org.session.libsignal.service.api.messages.calls.IceUpdateMessage;
import org.session.libsignal.service.api.messages.calls.OfferMessage;
import org.session.libsignal.service.api.messages.calls.SignalServiceCallMessage;
import org.session.libsignal.service.api.messages.multidevice.ReadMessage;
import org.session.libsignal.service.api.messages.multidevice.RequestMessage;
import org.session.libsignal.service.api.messages.multidevice.SentTranscriptMessage;
@ -325,15 +317,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
else if (syncMessage.getOpenGroups().isPresent()) SyncMessagesProtocol.handleOpenGroupSyncMessage(context, content, syncMessage.getOpenGroups().get());
else if (syncMessage.getBlockedList().isPresent()) SyncMessagesProtocol.handleBlockedContactsSyncMessage(context, content, syncMessage.getBlockedList().get());
else Log.w(TAG, "Contains no known sync types...");
} else if (content.getCallMessage().isPresent()) {
Log.i(TAG, "Got call message...");
SignalServiceCallMessage message = content.getCallMessage().get();
if (message.getOfferMessage().isPresent()) handleCallOfferMessage(content, message.getOfferMessage().get(), smsMessageId);
else if (message.getAnswerMessage().isPresent()) handleCallAnswerMessage(content, message.getAnswerMessage().get());
else if (message.getIceUpdateMessages().isPresent()) handleCallIceUpdateMessage(content, message.getIceUpdateMessages().get());
else if (message.getHangupMessage().isPresent()) handleCallHangupMessage(content, message.getHangupMessage().get(), smsMessageId);
else if (message.getBusyMessage().isPresent()) handleCallBusyMessage(content, message.getBusyMessage().get());
} else if (content.getReceiptMessage().isPresent()) {
SignalServiceReceiptMessage message = content.getReceiptMessage().get();
@ -382,86 +365,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
}
}
private void handleCallOfferMessage(@NonNull SignalServiceContent content,
@NonNull OfferMessage message,
@NonNull Optional<Long> smsMessageId)
{
Log.w(TAG, "handleCallOfferMessage...");
if (smsMessageId.isPresent()) {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
database.markAsMissedCall(smsMessageId.get());
} else {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_INCOMING_CALL);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromSerialized(content.getSender()));
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription());
intent.putExtra(WebRtcCallService.EXTRA_TIMESTAMP, content.getTimestamp());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent);
else context.startService(intent);
}
}
private void handleCallAnswerMessage(@NonNull SignalServiceContent content,
@NonNull AnswerMessage message)
{
Log.i(TAG, "handleCallAnswerMessage...");
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_RESPONSE_MESSAGE);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromSerialized(content.getSender()));
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_DESCRIPTION, message.getDescription());
context.startService(intent);
}
private void handleCallIceUpdateMessage(@NonNull SignalServiceContent content,
@NonNull List<IceUpdateMessage> messages)
{
Log.w(TAG, "handleCallIceUpdateMessage... " + messages.size());
for (IceUpdateMessage message : messages) {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_ICE_MESSAGE);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromSerialized(content.getSender()));
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP, message.getSdp());
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_MID, message.getSdpMid());
intent.putExtra(WebRtcCallService.EXTRA_ICE_SDP_LINE_INDEX, message.getSdpMLineIndex());
context.startService(intent);
}
}
private void handleCallHangupMessage(@NonNull SignalServiceContent content,
@NonNull HangupMessage message,
@NonNull Optional<Long> smsMessageId)
{
Log.i(TAG, "handleCallHangupMessage");
if (smsMessageId.isPresent()) {
DatabaseFactory.getSmsDatabase(context).markAsMissedCall(smsMessageId.get());
} else {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_REMOTE_HANGUP);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromSerialized(content.getSender()));
context.startService(intent);
}
}
private void handleCallBusyMessage(@NonNull SignalServiceContent content,
@NonNull BusyMessage message)
{
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_REMOTE_BUSY);
intent.putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId());
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, Address.fromSerialized(content.getSender()));
context.startService(intent);
}
private void handleEndSessionMessage(@NonNull SignalServiceContent content,
@NonNull Optional<Long> smsMessageId)
{
@ -1509,8 +1412,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else {
return sender.isBlocked();
}
} else if (content.getCallMessage().isPresent() || content.getTypingMessage().isPresent()) {
return sender.isBlocked();
} else if (content.getSyncMessage().isPresent()) {
return SyncMessagesProtocol.shouldIgnoreSyncMessage(context, sender);
}

View File

@ -59,7 +59,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.session.libsignal.service.internal.util.Util;
import java.util.HashSet;
@ -147,9 +146,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
StatusBarNotification[] activeNotifications = notifications.getActiveNotifications();
for (StatusBarNotification activeNotification : activeNotifications) {
if (activeNotification.getId() != CallNotificationBuilder.WEBRTC_NOTIFICATION) {
notifications.cancel(activeNotification.getId());
}
notifications.cancel(activeNotification.getId());
}
} catch (Throwable e) {
// XXX Appears to be a ROM bug, see #6043
@ -169,7 +166,6 @@ public class DefaultMessageNotifier implements MessageNotifier {
boolean validNotification = false;
if (notification.getId() != SUMMARY_NOTIFICATION_ID &&
notification.getId() != CallNotificationBuilder.WEBRTC_NOTIFICATION &&
notification.getId() != KeyCachingService.SERVICE_RUNNING_ID &&
notification.getId() != IncomingMessageObserver.FOREGROUND_ID &&
notification.getId() != PENDING_MESSAGES_ID)

View File

@ -1,229 +0,0 @@
package org.thoughtcrime.securesms.preferences;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.ContactsContract;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import android.widget.Toast;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.LogSubmitActivity;
import org.thoughtcrime.securesms.RegistrationActivity;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import org.session.libsignal.service.api.push.exceptions.AuthorizationFailedException;
import java.io.IOException;
import network.loki.messenger.R;
public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
private static final String TAG = AdvancedPreferenceFragment.class.getSimpleName();
private static final String PUSH_MESSAGING_PREF = "pref_toggle_push_messaging";
private static final String SUBMIT_DEBUG_LOG_PREF = "pref_submit_debug_logs";
private static final int PICK_IDENTITY_CONTACT = 1;
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
initializeIdentitySelection();
Preference submitDebugLog = this.findPreference(SUBMIT_DEBUG_LOG_PREF);
submitDebugLog.setOnPreferenceClickListener(new SubmitDebugLogListener());
submitDebugLog.setSummary(getVersion(getActivity()));
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_advanced);
}
@Override
public void onResume() {
super.onResume();
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__advanced);
initializePushMessagingToggle();
}
@Override
public void onActivityResult(int reqCode, int resultCode, Intent data) {
super.onActivityResult(reqCode, resultCode, data);
Log.i(TAG, "Got result: " + resultCode + " for req: " + reqCode);
if (resultCode == Activity.RESULT_OK && reqCode == PICK_IDENTITY_CONTACT) {
handleIdentitySelection(data);
}
}
private void initializePushMessagingToggle() {
CheckBoxPreference preference = (CheckBoxPreference)this.findPreference(PUSH_MESSAGING_PREF);
if (TextSecurePreferences.isPushRegistered(getActivity())) {
preference.setChecked(true);
preference.setSummary(TextSecurePreferences.getLocalNumber(getActivity()));
} else {
preference.setChecked(false);
preference.setSummary(R.string.preferences__free_private_messages_and_calls);
}
preference.setOnPreferenceChangeListener(new PushMessagingClickListener());
}
private void initializeIdentitySelection() {
ContactIdentityManager identity = ContactIdentityManager.getInstance(getActivity());
Preference preference = this.findPreference(TextSecurePreferences.IDENTITY_PREF);
if (identity.isSelfIdentityAutoDetected()) {
this.getPreferenceScreen().removePreference(preference);
} else {
Uri contactUri = identity.getSelfIdentityUri();
if (contactUri != null) {
String contactName = ContactAccessor.getInstance().getNameFromContact(getActivity(), contactUri);
preference.setSummary(String.format(getString(R.string.ApplicationPreferencesActivity_currently_s),
contactName));
}
preference.setOnPreferenceClickListener(new IdentityPreferenceClickListener());
}
}
private @NonNull String getVersion(@Nullable Context context) {
try {
if (context == null) return "";
String app = context.getString(R.string.app_name);
String version = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
return String.format("%s %s", app, version);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, e);
return context.getString(R.string.app_name);
}
}
private class IdentityPreferenceClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivityForResult(intent, PICK_IDENTITY_CONTACT);
return true;
}
}
private void handleIdentitySelection(Intent data) {
Uri contactUri = data.getData();
if (contactUri != null) {
TextSecurePreferences.setIdentityContactUri(getActivity(), contactUri.toString());
initializeIdentitySelection();
}
}
private class SubmitDebugLogListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
final Intent intent = new Intent(getActivity(), LogSubmitActivity.class);
startActivity(intent);
return true;
}
}
private class PushMessagingClickListener implements Preference.OnPreferenceChangeListener {
private static final int SUCCESS = 0;
private static final int NETWORK_ERROR = 1;
private class DisablePushMessagesTask extends ProgressDialogAsyncTask<Void, Void, Integer> {
private final CheckBoxPreference checkBoxPreference;
public DisablePushMessagesTask(final CheckBoxPreference checkBoxPreference) {
super(getActivity(), R.string.ApplicationPreferencesActivity_unregistering, R.string.ApplicationPreferencesActivity_unregistering_from_signal_messages_and_calls);
this.checkBoxPreference = checkBoxPreference;
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
switch (result) {
case NETWORK_ERROR:
Toast.makeText(getActivity(),
R.string.ApplicationPreferencesActivity_error_connecting_to_server,
Toast.LENGTH_LONG).show();
break;
case SUCCESS:
TextSecurePreferences.setPushRegistered(getActivity(), false);
initializePushMessagingToggle();
break;
}
}
@Override
protected Integer doInBackground(Void... params) {
try {
Context context = getActivity();
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context);
try {
accountManager.setGcmId(Optional.absent());
} catch (AuthorizationFailedException e) {
Log.w(TAG, e);
}
return SUCCESS;
} catch (IOException ioe) {
Log.w(TAG, ioe);
return NETWORK_ERROR;
}
}
}
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
if (((CheckBoxPreference)preference).isChecked()) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setIconAttribute(R.attr.dialog_info_icon);
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls);
builder.setMessage(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls_by_unregistering);
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new DisablePushMessagesTask((CheckBoxPreference)preference).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
builder.show();
} else {
Intent nextIntent = new Intent(getActivity(), ApplicationPreferencesActivity.class);
Intent intent = new Intent(getActivity(), RegistrationActivity.class);
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
intent.putExtra("next_intent", nextIntent);
startActivity(intent);
}
return false;
}
}
}

View File

@ -14,8 +14,6 @@ import network.loki.messenger.R;
public class ContactPreference extends Preference {
private ImageView messageButton;
private ImageView callButton;
private ImageView secureCallButton;
private Listener listener;
private boolean secure;
@ -49,8 +47,6 @@ public class ContactPreference extends Preference {
super.onBindViewHolder(view);
this.messageButton = (ImageView) view.findViewById(R.id.message);
this.callButton = (ImageView) view.findViewById(R.id.call);
this.secureCallButton = (ImageView) view.findViewById(R.id.secure_call);
if (listener != null) setListener(listener);
setSecure(secure);
@ -59,9 +55,6 @@ public class ContactPreference extends Preference {
public void setSecure(boolean secure) {
this.secure = secure;
if (secureCallButton != null) secureCallButton.setVisibility(secure ? View.VISIBLE : View.GONE);
if (callButton != null) callButton.setVisibility(secure ? View.GONE : View.VISIBLE);
int color;
if (secure) {
@ -71,16 +64,12 @@ public class ContactPreference extends Preference {
}
if (messageButton != null) messageButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (secureCallButton != null) secureCallButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (callButton != null) callButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
public void setListener(Listener listener) {
this.listener = listener;
if (this.messageButton != null) this.messageButton.setOnClickListener(v -> listener.onMessageClicked());
if (this.secureCallButton != null) this.secureCallButton.setOnClickListener(v -> listener.onSecureCallClicked());
if (this.callButton != null) this.callButton.setOnClickListener(v -> listener.onInSecureCallClicked());
}
public interface Listener {

View File

@ -1,26 +0,0 @@
package org.thoughtcrime.securesms.push;
import android.content.Context;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.session.libsignal.service.api.SignalServiceAccountManager;
import network.loki.messenger.BuildConfig;
public class AccountManagerFactory {
private static final String TAG = AccountManagerFactory.class.getSimpleName();
public static SignalServiceAccountManager createManager(Context context) {
return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(context),
TextSecurePreferences.getLocalNumber(context),
TextSecurePreferences.getPushServerPassword(context),
BuildConfig.USER_AGENT);
}
public static SignalServiceAccountManager createManager(final Context context, String number, String password) {
return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(number),
number, password, BuildConfig.USER_AGENT);
}
}

View File

@ -1,65 +0,0 @@
package org.thoughtcrime.securesms.registration;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.thoughtcrime.securesms.BaseActionBarActivity;
import network.loki.messenger.R;
public class CaptchaActivity extends BaseActionBarActivity {
public static final String KEY_TOKEN = "token";
public static final String KEY_IS_SMS = "is_sms";
private static final String SIGNAL_SCHEME = "signalcaptcha://";
public static Intent getIntent(@NonNull Context context, boolean isSms) {
Intent intent = new Intent(context, CaptchaActivity.class);
intent.putExtra(KEY_IS_SMS, isSms);
return intent;
}
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.captcha_activity);
WebView webView = findViewById(R.id.registration_captcha_web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.clearCache(true);
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url != null && url.startsWith(SIGNAL_SCHEME)) {
handleToken(url.substring(SIGNAL_SCHEME.length()));
return true;
}
return false;
}
});
webView.loadUrl("https://signalcaptchas.org/registration/generate.html");
}
public void handleToken(String token) {
if (!TextUtils.isEmpty(token)) {
Intent result = new Intent();
result.putExtra(KEY_TOKEN, token);
result.putExtra(KEY_IS_SMS, getIntent().getBooleanExtra(KEY_IS_SMS, true));
setResult(RESULT_OK, result);
} else {
setResult(RESULT_CANCELED);
}
finish();
}
}

View File

@ -1,71 +0,0 @@
package org.thoughtcrime.securesms.registration;
import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.BaseActionBarActivity;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import network.loki.messenger.R;
public class WelcomeActivity extends BaseActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.registration_welcome_activity);
findViewById(R.id.welcome_terms_button).setOnClickListener(v -> onTermsClicked());
findViewById(R.id.welcome_continue_button).setOnClickListener(v -> onContinueClicked());
}
@Override
protected void onResume() {
super.onResume();
if (TextSecurePreferences.getWasUnlinked(this)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.dialog_device_unlink_title);
builder.setMessage(R.string.dialog_device_unlink_message);
builder.setPositiveButton(R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
TextSecurePreferences.setWasUnlinked(getBaseContext(), false);
}
});
builder.show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private void onTermsClicked() {
CommunicationActions.openBrowserLink(this, "https://github.com/loki-project/loki-messenger-android/blob/master/privacy-policy.md");
}
private void onContinueClicked() {
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
.withRationaleDialog(getString(R.string.activity_landing_permission_dialog_message), R.drawable.ic_baseline_folder_48)
.onAnyResult(() -> {
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
if (nextIntent == null) {
throw new IllegalStateException("Was not supplied a next_intent.");
}
startActivity(nextIntent);
finish();
})
.execute();
}
}

View File

@ -1,180 +0,0 @@
package org.thoughtcrime.securesms.search;
import android.annotation.SuppressLint;
import androidx.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.ConversationListActivity;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.search.model.MessageResult;
import org.thoughtcrime.securesms.search.model.SearchResult;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import java.util.Locale;
/**
* A fragment that is displayed to do full-text search of messages, groups, and contacts.
*/
public class SearchFragment extends Fragment implements SearchListAdapter.EventListener {
public static final String TAG = "SearchFragment";
public static final String EXTRA_LOCALE = "locale";
private TextView noResultsView;
private RecyclerView listView;
private StickyHeaderDecoration listDecoration;
private SearchViewModel viewModel;
private SearchListAdapter listAdapter;
private String pendingQuery;
private Locale locale;
public static SearchFragment newInstance(@NonNull Locale locale) {
Bundle args = new Bundle();
args.putSerializable(EXTRA_LOCALE, locale);
SearchFragment fragment = new SearchFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.locale = (Locale) getArguments().getSerializable(EXTRA_LOCALE);
SearchRepository searchRepository = new SearchRepository(getContext(),
DatabaseFactory.getSearchDatabase(getContext()),
DatabaseFactory.getContactsDatabase(getContext()),
DatabaseFactory.getThreadDatabase(getContext()),
ContactAccessor.getInstance(),
SignalExecutors.SERIAL);
viewModel = ViewModelProviders.of(this, new SearchViewModel.Factory(searchRepository)).get(SearchViewModel.class);
if (pendingQuery != null) {
viewModel.updateQuery(pendingQuery);
pendingQuery = null;
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_search, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
noResultsView = view.findViewById(R.id.search_no_results);
listView = view.findViewById(R.id.search_list);
listAdapter = new SearchListAdapter(GlideApp.with(this), this, locale);
listDecoration = new StickyHeaderDecoration(listAdapter, false, false);
listView.setAdapter(listAdapter);
listView.addItemDecoration(listDecoration);
listView.setLayoutManager(new LinearLayoutManager(getContext()));
}
@Override
public void onStart() {
super.onStart();
viewModel.getSearchResult().observe(this, result -> {
result = result != null ? result : SearchResult.EMPTY;
listAdapter.updateResults(result);
if (result.isEmpty()) {
if (TextUtils.isEmpty(viewModel.getLastQuery().trim())) {
noResultsView.setVisibility(View.GONE);
} else {
noResultsView.setVisibility(View.VISIBLE);
noResultsView.setText(getString(R.string.SearchFragment_no_results, viewModel.getLastQuery()));
}
} else {
noResultsView.setVisibility(View.VISIBLE);
noResultsView.setText("");
}
});
}
@Override
public void onConversationClicked(@NonNull ThreadRecord threadRecord) {
ConversationListActivity conversationList = (ConversationListActivity) getActivity();
if (conversationList != null) {
conversationList.onCreateConversation(threadRecord.getThreadId(),
threadRecord.getRecipient(),
threadRecord.getDistributionType(),
threadRecord.getLastSeen());
}
}
@Override
public void onContactClicked(@NonNull Recipient contact) {
Intent intent = new Intent(getContext(), ConversationActivity.class);
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, contact.getAddress());
long existingThread = DatabaseFactory.getThreadDatabase(getContext()).getThreadIdIfExistsFor(contact);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
startActivity(intent);
}
@SuppressLint("StaticFieldLeak")
@Override
public void onMessageClicked(@NonNull MessageResult message) {
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... voids) {
int startingPosition = DatabaseFactory.getMmsSmsDatabase(getContext()).getMessagePositionInConversation(message.threadId, message.receivedTimestampMs);
startingPosition = Math.max(0, startingPosition);
return startingPosition;
}
@Override
protected void onPostExecute(Integer startingPosition) {
ConversationListActivity conversationList = (ConversationListActivity) getActivity();
if (conversationList != null) {
conversationList.openConversation(message.threadId,
message.conversationRecipient,
ThreadDatabase.DistributionTypes.DEFAULT,
-1,
startingPosition);
}
}
}.execute();
}
public void updateSearchQuery(@NonNull String query) {
if (viewModel != null) {
viewModel.updateQuery(query);
} else {
pendingQuery = query;
}
}
}

View File

@ -1,218 +0,0 @@
package org.thoughtcrime.securesms.search;
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.TextView;
import org.thoughtcrime.securesms.ConversationListItem;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.search.model.MessageResult;
import org.thoughtcrime.securesms.search.model.SearchResult;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import java.util.Collections;
import java.util.Locale;
class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.SearchResultViewHolder>
implements StickyHeaderDecoration.StickyHeaderAdapter<SearchListAdapter.HeaderViewHolder>
{
private static final int TYPE_CONVERSATIONS = 1;
private static final int TYPE_CONTACTS = 2;
private static final int TYPE_MESSAGES = 3;
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final Locale locale;
@NonNull
private SearchResult searchResult = SearchResult.EMPTY;
SearchListAdapter(@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener,
@NonNull Locale locale)
{
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.locale = locale;
}
@Override
public @NonNull SearchResultViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new SearchResultViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.conversation_list_item_view, parent, false));
}
@Override
public void onBindViewHolder(@NonNull SearchResultViewHolder holder, int position) {
ThreadRecord conversationResult = getConversationResult(position);
if (conversationResult != null) {
holder.bind(conversationResult, glideRequests, eventListener, locale, searchResult.getQuery());
return;
}
Recipient contactResult = getContactResult(position);
if (contactResult != null) {
holder.bind(contactResult, glideRequests, eventListener, locale, searchResult.getQuery());
return;
}
MessageResult messageResult = getMessageResult(position);
if (messageResult != null) {
holder.bind(messageResult, glideRequests, eventListener, locale, searchResult.getQuery());
}
}
@Override
public void onViewRecycled(@NonNull SearchResultViewHolder holder) {
holder.recycle();
}
@Override
public int getItemCount() {
return searchResult.size();
}
@Override
public long getHeaderId(int position) {
if (getConversationResult(position) != null) {
return TYPE_CONVERSATIONS;
} else if (getContactResult(position) != null) {
return TYPE_CONTACTS;
} else {
return TYPE_MESSAGES;
}
}
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.contact_selection_list_divider, parent, false));
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
viewHolder.bind((int) getHeaderId(position));
}
void updateResults(@NonNull SearchResult result) {
this.searchResult = result;
notifyDataSetChanged();
}
@Nullable
private ThreadRecord getConversationResult(int position) {
if (position < searchResult.getConversations().size()) {
return searchResult.getConversations().get(position);
}
return null;
}
@Nullable
private Recipient getContactResult(int position) {
if (position >= getFirstContactIndex() && position < getFirstMessageIndex()) {
return searchResult.getContacts().get(position - getFirstContactIndex());
}
return null;
}
@Nullable
private MessageResult getMessageResult(int position) {
if (position >= getFirstMessageIndex() && position < searchResult.size()) {
return searchResult.getMessages().get(position - getFirstMessageIndex());
}
return null;
}
private int getFirstContactIndex() {
return searchResult.getConversations().size();
}
private int getFirstMessageIndex() {
return getFirstContactIndex() + searchResult.getContacts().size();
}
public interface EventListener {
void onConversationClicked(@NonNull ThreadRecord threadRecord);
void onContactClicked(@NonNull Recipient contact);
void onMessageClicked(@NonNull MessageResult message);
}
static class SearchResultViewHolder extends RecyclerView.ViewHolder {
private final ConversationListItem root;
SearchResultViewHolder(View itemView) {
super(itemView);
root = (ConversationListItem) itemView;
}
void bind(@NonNull ThreadRecord conversationResult,
@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener,
@NonNull Locale locale,
@Nullable String query)
{
root.bind(conversationResult, glideRequests, locale, Collections.emptySet(), Collections.emptySet(), false, query);
root.setOnClickListener(view -> eventListener.onConversationClicked(conversationResult));
}
void bind(@NonNull Recipient contactResult,
@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener,
@NonNull Locale locale,
@Nullable String query)
{
root.bind(contactResult, glideRequests, locale, query);
root.setOnClickListener(view -> eventListener.onContactClicked(contactResult));
}
void bind(@NonNull MessageResult messageResult,
@NonNull GlideRequests glideRequests,
@NonNull EventListener eventListener,
@NonNull Locale locale,
@Nullable String query)
{
root.bind(messageResult, glideRequests, locale, query);
root.setOnClickListener(view -> eventListener.onMessageClicked(messageResult));
}
void recycle() {
root.unbind();
root.setOnClickListener(null);
}
}
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
private TextView titleView;
public HeaderViewHolder(View itemView) {
super(itemView);
titleView = itemView.findViewById(R.id.label);
}
public void bind(int headerType) {
switch (headerType) {
case TYPE_CONVERSATIONS:
titleView.setText(R.string.SearchFragment_header_conversations);
break;
case TYPE_CONTACTS:
titleView.setText(R.string.SearchFragment_header_contacts);
break;
case TYPE_MESSAGES:
titleView.setText(R.string.SearchFragment_header_messages);
break;
}
}
}
}

View File

@ -1,126 +0,0 @@
package org.thoughtcrime.securesms.search;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.database.ContentObserver;
import android.os.Handler;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import androidx.fragment.app.Fragment;
import org.thoughtcrime.securesms.search.model.SearchResult;
import org.thoughtcrime.securesms.util.Debouncer;
import org.thoughtcrime.securesms.util.Util;
/**
* A {@link ViewModel} for handling all the business logic and interactions that take place inside
* of the {@link SearchFragment}.
*
* This class should be view- and Android-agnostic, and therefore should contain no references to
* things like {@link android.content.Context}, {@link android.view.View},
* {@link Fragment}, etc.
*/
class SearchViewModel extends ViewModel {
private final ObservingLiveData searchResult;
private final SearchRepository searchRepository;
private final Debouncer debouncer;
private String lastQuery;
private SearchViewModel(@NonNull SearchRepository searchRepository) {
this.searchResult = new ObservingLiveData();
this.searchRepository = searchRepository;
this.debouncer = new Debouncer(500);
searchResult.registerContentObserver(new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
if (!TextUtils.isEmpty(getLastQuery())) {
searchRepository.query(getLastQuery(), searchResult::postValue);
}
}
});
}
LiveData<SearchResult> getSearchResult() {
return searchResult;
}
void updateQuery(String query) {
lastQuery = query;
debouncer.publish(() -> searchRepository.query(query, result -> {
Util.runOnMain(() -> {
if (query.equals(lastQuery)) {
searchResult.setValue(result);
} else {
result.close();
}
});
}));
}
@NonNull
String getLastQuery() {
return lastQuery == null ? "" : lastQuery;
}
@Override
protected void onCleared() {
debouncer.clear();
searchResult.close();
}
/**
* Ensures that the previous {@link SearchResult} is always closed whenever we set a new one.
*/
private static class ObservingLiveData extends MutableLiveData<SearchResult> {
private ContentObserver observer;
@Override
public void setValue(SearchResult value) {
SearchResult previous = getValue();
if (previous != null) {
previous.unregisterContentObserver(observer);
previous.close();
}
value.registerContentObserver(observer);
super.setValue(value);
}
void close() {
SearchResult value = getValue();
if (value != null) {
value.unregisterContentObserver(observer);
value.close();
}
}
void registerContentObserver(@NonNull ContentObserver observer) {
this.observer = observer;
}
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
private final SearchRepository searchRepository;
public Factory(@NonNull SearchRepository searchRepository) {
this.searchRepository = searchRepository;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return modelClass.cast(new SearchViewModel(searchRepository));
}
}
}

View File

@ -42,12 +42,10 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
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.push.ContactTokenDetails;
import java.io.IOException;
@ -215,30 +213,6 @@ public class MessageSender {
!recipient.getAddress().isMmsGroup();
}
private static boolean isPushDestination(Context context, Recipient destination) {
if (destination.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
return true;
} else if (destination.resolve().getRegistered() == RecipientDatabase.RegisteredState.NOT_REGISTERED) {
return false;
} else {
try {
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context);
Optional<ContactTokenDetails> registeredUser = accountManager.getContact(destination.getAddress().serialize());
if (!registeredUser.isPresent()) {
DatabaseFactory.getRecipientDatabase(context).setRegistered(destination, RecipientDatabase.RegisteredState.NOT_REGISTERED);
return false;
} else {
DatabaseFactory.getRecipientDatabase(context).setRegistered(destination, RecipientDatabase.RegisteredState.REGISTERED);
return true;
}
} catch (IOException e1) {
Log.w(TAG, e1);
return false;
}
}
}
private static boolean isLocalSelfSend(@NonNull Context context, @NonNull Recipient recipient, boolean forceSms) {
return recipient.isLocalNumber() &&
!forceSms &&

View File

@ -15,43 +15,14 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.WebRtcCallService;
public class CommunicationActions {
public static void startVoiceCall(@NonNull Activity activity, @NonNull Recipient recipient) {
if (TelephonyUtil.isAnyPstnLineBusy(activity)) {
Toast.makeText(activity,
R.string.CommunicationActions_a_cellular_call_is_already_in_progress,
Toast.LENGTH_SHORT
).show();
return;
}
Permissions.with(activity)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.withRationaleDialog(activity.getString(R.string.ConversationActivity_to_call_s_signal_needs_access_to_your_microphone_and_camera),
R.drawable.ic_mic_white_48dp,
R.drawable.ic_videocam_white_48dp)
.withPermanentDenialDialog(activity.getString(R.string.ConversationActivity_signal_needs_the_microphone_and_camera_permissions_in_order_to_call_s, recipient.toShortString()))
.onAllGranted(() -> {
Intent intent = new Intent(activity, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, recipient.getAddress());
activity.startService(intent);
Intent activityIntent = new Intent(activity, WebRtcCallActivity.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(activityIntent);
})
.execute();
}
public static void startConversation(@NonNull Context context, @NonNull Recipient recipient, @Nullable String text) {
startConversation(context, recipient, text, null);
}

View File

@ -9,12 +9,6 @@ import java.security.NoSuchAlgorithmException;
public class FileUtils {
static {
System.loadLibrary("native-utils");
}
public static native int getFileDescriptorOwner(FileDescriptor fileDescriptor);
public static byte[] getFileDigest(FileInputStream fin) throws IOException {
try {
MessageDigest digest = MessageDigest.getInstance("SHA256");

View File

@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import network.loki.messenger.R;
/**
* Manages the state of the WebRtc items in the Android notification bar.
*
* @author Moxie Marlinspike
*
*/
public class CallNotificationBuilder {
public static final int WEBRTC_NOTIFICATION = 313388;
public static final int TYPE_INCOMING_RINGING = 1;
public static final int TYPE_OUTGOING_RINGING = 2;
public static final int TYPE_ESTABLISHED = 3;
public static final int TYPE_INCOMING_CONNECTING = 4;
public static Notification getCallInProgressNotification(Context context, int type, Recipient recipient) {
Intent contentIntent = new Intent(context, WebRtcCallActivity.class);
contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationChannels.CALLS)
.setSmallIcon(R.drawable.ic_call_secure_white_24dp)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setContentTitle(recipient.getName());
if (type == TYPE_INCOMING_CONNECTING) {
builder.setContentText(context.getString(R.string.CallNotificationBuilder_connecting));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
} else if (type == TYPE_INCOMING_RINGING) {
builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call));
builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_DENY_CALL, R.drawable.ic_close_grey600_32dp, R.string.NotificationBarManager__deny_call));
builder.addAction(getActivityNotificationAction(context, WebRtcCallActivity.ANSWER_ACTION, R.drawable.ic_phone_grey600_32dp, R.string.NotificationBarManager__answer_call));
} else if (type == TYPE_OUTGOING_RINGING) {
builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call));
builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__cancel_call));
} else {
builder.setContentText(context.getString(R.string.NotificationBarManager_signal_call_in_progress));
builder.addAction(getServiceNotificationAction(context, WebRtcCallService.ACTION_LOCAL_HANGUP, R.drawable.ic_call_end_grey600_32dp, R.string.NotificationBarManager__end_call));
}
return builder.build();
}
private static NotificationCompat.Action getServiceNotificationAction(Context context, String action, int iconResId, int titleResId) {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(action);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
return new NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent);
}
private static NotificationCompat.Action getActivityNotificationAction(@NonNull Context context, @NonNull String action,
@DrawableRes int iconResId, @StringRes int titleResId)
{
Intent intent = new Intent(context, WebRtcCallActivity.class);
intent.setAction(action);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
return new NotificationCompat.Action(iconResId, context.getString(titleResId), pendingIntent);
}
}

View File

@ -1,32 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import androidx.annotation.NonNull;
public class CameraState {
public static final CameraState UNKNOWN = new CameraState(Direction.NONE, 0);
private final Direction activeDirection;
private final int cameraCount;
public CameraState(@NonNull Direction activeDirection, int cameraCount) {
this.activeDirection = activeDirection;
this.cameraCount = cameraCount;
}
public int getCameraCount() {
return cameraCount;
}
public Direction getActiveDirection() {
return activeDirection;
}
public boolean isEnabled() {
return this.activeDirection != Direction.NONE;
}
public enum Direction {
FRONT, BACK, NONE, PENDING
}
}

View File

@ -1,78 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.telephony.TelephonyManager;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Listens for incoming PSTN calls and rejects them if a RedPhone call is already in progress.
*
* Unstable use of reflection employed to gain access to ITelephony.
*
*/
public class IncomingPstnCallReceiver extends BroadcastReceiver {
private static final String TAG = IncomingPstnCallReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Checking incoming call...");
if (intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) == null) {
Log.w(TAG, "Telephony event does not contain number...");
return;
}
if (!intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING)) {
Log.w(TAG, "Telephony event is not state ringing...");
return;
}
InCallListener listener = new InCallListener(context, new Handler());
WebRtcCallService.isCallActive(context, listener);
}
private static class InCallListener extends ResultReceiver {
private final Context context;
InCallListener(Context context, Handler handler) {
super(handler);
this.context = context.getApplicationContext();
}
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 1) {
Log.i(TAG, "Attempting to deny incoming PSTN call.");
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
try {
Method getTelephony = tm.getClass().getDeclaredMethod("getITelephony");
getTelephony.setAccessible(true);
Object telephonyService = getTelephony.invoke(tm);
Method endCall = telephonyService.getClass().getDeclaredMethod("endCall");
endCall.invoke(telephonyService);
Log.i(TAG, "Denied Incoming Call.");
} catch (NoSuchMethodException e) {
Log.w(TAG, "Unable to access ITelephony API", e);
} catch (IllegalAccessException e) {
Log.w(TAG, "Unable to access ITelephony API", e);
} catch (InvocationTargetException e) {
Log.w(TAG, "Unable to access ITelephony API", e);
}
}
}
}
}

View File

@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import org.webrtc.PeerConnectionFactory;
public class PeerConnectionFactoryOptions extends PeerConnectionFactory.Options {
public PeerConnectionFactoryOptions() {
this.networkIgnoreMask = 1 << 4;
}
}

View File

@ -1,424 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.DataChannel;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.VideoSink;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.BACK;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.FRONT;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.NONE;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.PENDING;
public class PeerConnectionWrapper {
private static final String TAG = PeerConnectionWrapper.class.getSimpleName();
private static final PeerConnection.IceServer STUN_SERVER = new PeerConnection.IceServer("stun:stun1.l.google.com:19302");
@NonNull private final PeerConnection peerConnection;
@NonNull private final AudioTrack audioTrack;
@NonNull private final AudioSource audioSource;
@NonNull private final Camera camera;
@Nullable private final VideoSource videoSource;
@Nullable private final VideoTrack videoTrack;
public PeerConnectionWrapper(@NonNull Context context,
@NonNull PeerConnectionFactory factory,
@NonNull PeerConnection.Observer observer,
@NonNull VideoSink localRenderer,
@NonNull List<PeerConnection.IceServer> turnServers,
@NonNull CameraEventListener cameraEventListener,
@NonNull EglBase eglBase,
boolean hideIp)
{
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
iceServers.add(STUN_SERVER);
iceServers.addAll(turnServers);
MediaConstraints constraints = new MediaConstraints();
MediaConstraints audioConstraints = new MediaConstraints();
PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
configuration.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
configuration.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
if (hideIp) {
configuration.iceTransportsType = PeerConnection.IceTransportsType.RELAY;
}
constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
audioConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
this.peerConnection = factory.createPeerConnection(configuration, constraints, observer);
this.peerConnection.setAudioPlayout(false);
this.peerConnection.setAudioRecording(false);
MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
this.audioSource = factory.createAudioSource(audioConstraints);
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
this.audioTrack.setEnabled(false);
mediaStream.addTrack(audioTrack);
this.camera = new Camera(context, cameraEventListener);
if (camera.capturer != null) {
this.videoSource = factory.createVideoSource(false);
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
camera.capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()), context, videoSource.getCapturerObserver());
this.videoTrack.addSink(localRenderer);
this.videoTrack.setEnabled(false);
mediaStream.addTrack(videoTrack);
} else {
this.videoSource = null;
this.videoTrack = null;
}
this.peerConnection.addStream(mediaStream);
}
public void setVideoEnabled(boolean enabled) {
if (this.videoTrack != null) {
this.videoTrack.setEnabled(enabled);
}
camera.setEnabled(enabled);
}
public void flipCamera() {
camera.flip();
}
public CameraState getCameraState() {
return new CameraState(camera.getActiveDirection(), camera.getCount());
}
public void setCommunicationMode() {
this.peerConnection.setAudioPlayout(true);
this.peerConnection.setAudioRecording(true);
}
public void setAudioEnabled(boolean enabled) {
this.audioTrack.setEnabled(enabled);
}
public DataChannel createDataChannel(String name) {
DataChannel.Init dataChannelConfiguration = new DataChannel.Init();
dataChannelConfiguration.ordered = true;
return this.peerConnection.createDataChannel(name, dataChannelConfiguration);
}
public SessionDescription createOffer(MediaConstraints mediaConstraints) throws PeerConnectionException {
final SettableFuture<SessionDescription> future = new SettableFuture<>();
peerConnection.createOffer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
future.set(sdp);
}
@Override
public void onCreateFailure(String error) {
future.setException(new PeerConnectionException(error));
}
@Override
public void onSetSuccess() {
throw new AssertionError();
}
@Override
public void onSetFailure(String error) {
throw new AssertionError();
}
}, mediaConstraints);
try {
return correctSessionDescription(future.get());
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public SessionDescription createAnswer(MediaConstraints mediaConstraints) throws PeerConnectionException {
final SettableFuture<SessionDescription> future = new SettableFuture<>();
peerConnection.createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
future.set(sdp);
}
@Override
public void onCreateFailure(String error) {
future.setException(new PeerConnectionException(error));
}
@Override
public void onSetSuccess() {
throw new AssertionError();
}
@Override
public void onSetFailure(String error) {
throw new AssertionError();
}
}, mediaConstraints);
try {
return correctSessionDescription(future.get());
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void setRemoteDescription(SessionDescription sdp) throws PeerConnectionException {
final SettableFuture<Boolean> future = new SettableFuture<>();
peerConnection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {}
@Override
public void onCreateFailure(String error) {}
@Override
public void onSetSuccess() {
future.set(true);
}
@Override
public void onSetFailure(String error) {
future.setException(new PeerConnectionException(error));
}
}, sdp);
try {
future.get();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void setLocalDescription(SessionDescription sdp) throws PeerConnectionException {
final SettableFuture<Boolean> future = new SettableFuture<>();
peerConnection.setLocalDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
throw new AssertionError();
}
@Override
public void onCreateFailure(String error) {
throw new AssertionError();
}
@Override
public void onSetSuccess() {
future.set(true);
}
@Override
public void onSetFailure(String error) {
future.setException(new PeerConnectionException(error));
}
}, sdp);
try {
future.get();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
throw new PeerConnectionException(e);
}
}
public void dispose() {
this.camera.dispose();
if (this.videoSource != null) {
this.videoSource.dispose();
}
this.audioSource.dispose();
this.peerConnection.close();
this.peerConnection.dispose();
}
public boolean addIceCandidate(IceCandidate candidate) {
return this.peerConnection.addIceCandidate(candidate);
}
private SessionDescription correctSessionDescription(SessionDescription sessionDescription) {
String updatedSdp = sessionDescription.description.replaceAll("(a=fmtp:111 ((?!cbr=).)*)\r?\n", "$1;cbr=1\r\n");
updatedSdp = updatedSdp.replaceAll(".+urn:ietf:params:rtp-hdrext:ssrc-audio-level.*\r?\n", "");
return new SessionDescription(sessionDescription.type, updatedSdp);
}
public static class PeerConnectionException extends Exception {
public PeerConnectionException(String error) {
super(error);
}
public PeerConnectionException(Throwable throwable) {
super(throwable);
}
}
private static class Camera implements CameraVideoCapturer.CameraSwitchHandler {
@Nullable
private final CameraVideoCapturer capturer;
private final CameraEventListener cameraEventListener;
private final int cameraCount;
private CameraState.Direction activeDirection;
private boolean enabled;
Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener)
{
this.cameraEventListener = cameraEventListener;
CameraEnumerator enumerator = getCameraEnumerator(context);
cameraCount = enumerator.getDeviceNames().length;
CameraVideoCapturer capturerCandidate = createVideoCapturer(enumerator, FRONT);
if (capturerCandidate != null) {
activeDirection = FRONT;
} else {
capturerCandidate = createVideoCapturer(enumerator, BACK);
if (capturerCandidate != null) {
activeDirection = BACK;
} else {
activeDirection = NONE;
}
}
capturer = capturerCandidate;
}
void flip() {
if (capturer == null || cameraCount < 2) {
Log.w(TAG, "Tried to flip the camera, but we only have " + cameraCount + " of them.");
return;
}
activeDirection = PENDING;
capturer.switchCamera(this);
}
void setEnabled(boolean enabled) {
this.enabled = enabled;
if (capturer == null) {
return;
}
try {
if (enabled) {
capturer.startCapture(1280, 720, 30);
} else {
capturer.stopCapture();
}
} catch (InterruptedException e) {
Log.w(TAG, "Got interrupted while trying to stop video capture", e);
}
}
void dispose() {
if (capturer != null) {
capturer.dispose();
}
}
int getCount() {
return cameraCount;
}
@NonNull CameraState.Direction getActiveDirection() {
return enabled ? activeDirection : NONE;
}
@Nullable CameraVideoCapturer getCapturer() {
return capturer;
}
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator,
@NonNull CameraState.Direction direction)
{
String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) ||
(direction == BACK && enumerator.isBackFacing(deviceName)))
{
return enumerator.createCapturer(deviceName, null);
}
}
return null;
}
private @NonNull CameraEnumerator getCameraEnumerator(@NonNull Context context) {
boolean camera2EnumeratorIsSupported = false;
try {
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
} catch (final Throwable throwable) {
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
}
Log.i(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
return camera2EnumeratorIsSupported ? new Camera2Enumerator(context)
: new Camera1Enumerator(true);
}
@Override
public void onCameraSwitchDone(boolean isFrontFacing) {
activeDirection = isFrontFacing ? FRONT : BACK;
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
}
@Override
public void onCameraSwitchError(String errorMessage) {
Log.e(TAG, "onCameraSwitchError: " + errorMessage);
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
}
}
public interface CameraEventListener {
void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
}
}

View File

@ -1,42 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import org.thoughtcrime.securesms.logging.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Allows multiple default uncaught exception handlers to be registered
*
* Calls all registered handlers in reverse order of registration.
* Errors in one handler do not prevent subsequent handlers from being called.
*/
public class UncaughtExceptionHandlerManager implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler originalHandler;
private final List<Thread.UncaughtExceptionHandler> handlers = new ArrayList<Thread.UncaughtExceptionHandler>();
public UncaughtExceptionHandlerManager() {
originalHandler = Thread.getDefaultUncaughtExceptionHandler();
registerHandler(originalHandler);
Thread.setDefaultUncaughtExceptionHandler(this);
}
public void registerHandler(Thread.UncaughtExceptionHandler handler) {
handlers.add(handler);
}
public void unregister() {
Thread.setDefaultUncaughtExceptionHandler(originalHandler);
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
for (int i = handlers.size() - 1; i >= 0; i--) {
try {
handlers.get(i).uncaughtException(thread, throwable);
} catch(Throwable t) {
Log.e("UncaughtExceptionHandlerManager", "Error in uncaught exception handling", t);
}
}
}
}

View File

@ -1,50 +0,0 @@
package org.thoughtcrime.securesms.webrtc;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.service.WebRtcCallService;
public class VoiceCallShare extends Activity {
private static final String TAG = VoiceCallShare.class.getSimpleName();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (getIntent().getData() != null && "content".equals(getIntent().getData().getScheme())) {
Cursor cursor = null;
try {
cursor = getContentResolver().query(getIntent().getData(), null, null, null, null);
if (cursor != null && cursor.moveToNext()) {
String destination = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.Data.DATA1));
Address address = Address.fromExternal(this, destination);
if (!TextUtils.isEmpty(destination)) {
Intent serviceIntent = new Intent(this, WebRtcCallService.class);
serviceIntent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
serviceIntent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, address);
startService(serviceIntent);
Intent activityIntent = new Intent(this, WebRtcCallActivity.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(activityIntent);
}
}
} finally {
if (cursor != null) cursor.close();
}
}
finish();
}
}

View File

@ -1,230 +0,0 @@
package org.thoughtcrime.securesms.webrtc.audio;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.ServiceUtil;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class BluetoothStateManager {
private static final String TAG = BluetoothStateManager.class.getSimpleName();
private enum ScoConnection {
DISCONNECTED,
IN_PROGRESS,
CONNECTED
}
private final Object LOCK = new Object();
private final Context context;
private final BluetoothAdapter bluetoothAdapter;
private BluetoothScoReceiver bluetoothScoReceiver;
private BluetoothConnectionReceiver bluetoothConnectionReceiver;
private final BluetoothStateListener listener;
private final AtomicBoolean destroyed;
private BluetoothHeadset bluetoothHeadset = null;
private ScoConnection scoConnection = ScoConnection.DISCONNECTED;
private boolean wantsConnection = false;
public BluetoothStateManager(@NonNull Context context, @Nullable BluetoothStateListener listener) {
this.context = context.getApplicationContext();
this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
this.bluetoothScoReceiver = new BluetoothScoReceiver();
this.bluetoothConnectionReceiver = new BluetoothConnectionReceiver();
this.listener = listener;
this.destroyed = new AtomicBoolean(false);
if (this.bluetoothAdapter == null)
return;
requestHeadsetProxyProfile();
this.context.registerReceiver(bluetoothConnectionReceiver, new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
Intent sticky = this.context.registerReceiver(bluetoothScoReceiver, new IntentFilter(getScoChangeIntent()));
if (sticky != null) {
bluetoothScoReceiver.onReceive(context, sticky);
}
handleBluetoothStateChange();
}
public void onDestroy() {
destroyed.set(true);
if (bluetoothHeadset != null && bluetoothAdapter != null) {
this.bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset);
}
if (bluetoothConnectionReceiver != null) {
context.unregisterReceiver(bluetoothConnectionReceiver);
bluetoothConnectionReceiver = null;
}
if (bluetoothScoReceiver != null) {
context.unregisterReceiver(bluetoothScoReceiver);
bluetoothScoReceiver = null;
}
this.bluetoothHeadset = null;
}
public void setWantsConnection(boolean enabled) {
synchronized (LOCK) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
this.wantsConnection = enabled;
if (wantsConnection && isBluetoothAvailable() && scoConnection == ScoConnection.DISCONNECTED) {
audioManager.startBluetoothSco();
scoConnection = ScoConnection.IN_PROGRESS;
} else if (!wantsConnection && scoConnection == ScoConnection.CONNECTED) {
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
scoConnection = ScoConnection.DISCONNECTED;
} else if (!wantsConnection && scoConnection == ScoConnection.IN_PROGRESS) {
audioManager.stopBluetoothSco();
scoConnection = ScoConnection.DISCONNECTED;
}
}
}
private void handleBluetoothStateChange() {
if (listener != null && !destroyed.get()) listener.onBluetoothStateChanged(isBluetoothAvailable());
}
private boolean isBluetoothAvailable() {
try {
synchronized (LOCK) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) return false;
if (!audioManager.isBluetoothScoAvailableOffCall()) return false;
return bluetoothHeadset != null && !bluetoothHeadset.getConnectedDevices().isEmpty();
}
} catch (Exception e) {
Log.w(TAG, e);
return false;
}
}
private String getScoChangeIntent() {
return AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED;
}
private void requestHeadsetProxyProfile() {
this.bluetoothAdapter.getProfileProxy(context, new BluetoothProfile.ServiceListener() {
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (destroyed.get()) {
Log.w(TAG, "Got bluetooth profile event after the service was destroyed. Ignoring.");
return;
}
if (profile == BluetoothProfile.HEADSET) {
synchronized (LOCK) {
bluetoothHeadset = (BluetoothHeadset) proxy;
}
Intent sticky = context.registerReceiver(null, new IntentFilter(getScoChangeIntent()));
bluetoothScoReceiver.onReceive(context, sticky);
synchronized (LOCK) {
if (wantsConnection && isBluetoothAvailable() && scoConnection == ScoConnection.DISCONNECTED) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
audioManager.startBluetoothSco();
scoConnection = ScoConnection.IN_PROGRESS;
}
}
handleBluetoothStateChange();
}
}
@Override
public void onServiceDisconnected(int profile) {
Log.i(TAG, "onServiceDisconnected");
if (profile == BluetoothProfile.HEADSET) {
bluetoothHeadset = null;
handleBluetoothStateChange();
}
}
}, BluetoothProfile.HEADSET);
}
private class BluetoothScoReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) return;
Log.i(TAG, "onReceive");
synchronized (LOCK) {
if (getScoChangeIntent().equals(intent.getAction())) {
int status = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR);
if (status == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
if (bluetoothHeadset != null) {
List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
for (BluetoothDevice device : devices) {
if (bluetoothHeadset.isAudioConnected(device)) {
int deviceClass = device.getBluetoothClass().getDeviceClass();
if (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
deviceClass == BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO ||
deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)
{
scoConnection = ScoConnection.CONNECTED;
if (wantsConnection) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
audioManager.setBluetoothScoOn(true);
}
}
}
}
}
}
}
}
handleBluetoothStateChange();
}
}
private class BluetoothConnectionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive");
handleBluetoothStateChange();
}
}
public interface BluetoothStateListener {
public void onBluetoothStateChanged(boolean isAvailable);
}
}

View File

@ -1,131 +0,0 @@
package org.thoughtcrime.securesms.webrtc.audio;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Vibrator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.ServiceUtil;
import java.io.IOException;
public class IncomingRinger {
private static final String TAG = IncomingRinger.class.getSimpleName();
private static final long[] VIBRATE_PATTERN = {0, 1000, 1000};
private final Context context;
private final Vibrator vibrator;
private MediaPlayer player;
IncomingRinger(Context context) {
this.context = context.getApplicationContext();
this.vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
public void start(@Nullable Uri uri, boolean vibrate) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
if (player != null) player.release();
if (uri != null) player = createPlayer(uri);
int ringerMode = audioManager.getRingerMode();
if (shouldVibrate(context, player, ringerMode, vibrate)) {
Log.i(TAG, "Starting vibration");
vibrator.vibrate(VIBRATE_PATTERN, 1);
}
if (player != null && ringerMode == AudioManager.RINGER_MODE_NORMAL) {
try {
if (!player.isPlaying()) {
player.prepare();
player.start();
Log.i(TAG, "Playing ringtone now...");
} else {
Log.w(TAG, "Ringtone is already playing, declining to restart.");
}
} catch (IllegalStateException | IOException e) {
Log.w(TAG, e);
player = null;
}
} else {
Log.w(TAG, "Not ringing, mode: " + ringerMode);
}
}
public void stop() {
if (player != null) {
Log.i(TAG, "Stopping ringer");
player.release();
player = null;
}
Log.i(TAG, "Cancelling vibrator");
vibrator.cancel();
}
private boolean shouldVibrate(Context context, MediaPlayer player, int ringerMode, boolean vibrate) {
if (player == null) {
return true;
}
return shouldVibrateNew(context, ringerMode, vibrate);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private boolean shouldVibrateNew(Context context, int ringerMode, boolean vibrate) {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator == null || !vibrator.hasVibrator()) {
return false;
}
if (vibrate) {
return ringerMode != AudioManager.RINGER_MODE_SILENT;
} else {
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
}
}
private boolean shouldVibrateOld(Context context, boolean vibrate) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
return vibrate && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER);
}
private MediaPlayer createPlayer(@NonNull Uri ringtoneUri) {
try {
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setOnErrorListener(new MediaPlayerErrorListener());
mediaPlayer.setDataSource(context, ringtoneUri);
mediaPlayer.setLooping(true);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
return mediaPlayer;
} catch (IOException e) {
Log.e(TAG, "Failed to create player for incoming call ringer");
return null;
}
}
private class MediaPlayerErrorListener implements MediaPlayer.OnErrorListener {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.w(TAG, "onError(" + mp + ", " + what + ", " + extra);
player = null;
return false;
}
}
}

View File

@ -1,63 +0,0 @@
package org.thoughtcrime.securesms.webrtc.audio;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.logging.Log;
import network.loki.messenger.R;
import java.io.IOException;
public class OutgoingRinger {
private static final String TAG = OutgoingRinger.class.getSimpleName();
public enum Type {
RINGING,
BUSY
}
private final Context context;
private MediaPlayer mediaPlayer;
public OutgoingRinger(@NonNull Context context) {
this.context = context;
}
public void start(Type type) {
int soundId;
if (type == Type.RINGING) soundId = R.raw.redphone_outring;
else if (type == Type.BUSY) soundId = R.raw.redphone_busy;
else throw new IllegalArgumentException("Not a valid sound type");
if( mediaPlayer != null ) {
mediaPlayer.release();
}
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
mediaPlayer.setLooping(true);
String packageName = context.getPackageName();
Uri dataUri = Uri.parse("android.resource://" + packageName + "/" + soundId);
try {
mediaPlayer.setDataSource(context, dataUri);
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IllegalArgumentException | SecurityException | IllegalStateException | IOException e) {
Log.w(TAG, e);
}
}
public void stop() {
if (mediaPlayer == null) return;
mediaPlayer.release();
mediaPlayer = null;
}
}

View File

@ -1,102 +0,0 @@
package org.thoughtcrime.securesms.webrtc.audio;
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.util.ServiceUtil;
public class SignalAudioManager {
@SuppressWarnings("unused")
private static final String TAG = SignalAudioManager.class.getSimpleName();
private final Context context;
private final IncomingRinger incomingRinger;
private final OutgoingRinger outgoingRinger;
private final SoundPool soundPool;
private final int connectedSoundId;
private final int disconnectedSoundId;
public SignalAudioManager(@NonNull Context context) {
this.context = context.getApplicationContext();
this.incomingRinger = new IncomingRinger(context);
this.outgoingRinger = new OutgoingRinger(context);
this.soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0);
this.connectedSoundId = this.soundPool.load(context, R.raw.webrtc_completed, 1);
this.disconnectedSoundId = this.soundPool.load(context, R.raw.webrtc_disconnected, 1);
}
public void initializeAudioForCall() {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE);
}
public void startIncomingRinger(@Nullable Uri ringtoneUri, boolean vibrate) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
boolean speaker = !audioManager.isWiredHeadsetOn() && !audioManager.isBluetoothScoOn();
audioManager.setMode(AudioManager.MODE_RINGTONE);
audioManager.setMicrophoneMute(false);
audioManager.setSpeakerphoneOn(speaker);
incomingRinger.start(ringtoneUri, vibrate);
}
public void startOutgoingRinger(OutgoingRinger.Type type) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
audioManager.setMicrophoneMute(false);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
outgoingRinger.start(type);
}
public void silenceIncomingRinger() {
incomingRinger.stop();
}
public void startCommunication(boolean preserveSpeakerphone) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
incomingRinger.stop();
outgoingRinger.stop();
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
if (!preserveSpeakerphone) {
audioManager.setSpeakerphoneOn(false);
}
soundPool.play(connectedSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
}
public void stop(boolean playDisconnected) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
incomingRinger.stop();
outgoingRinger.stop();
if (playDisconnected) {
soundPool.play(disconnectedSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
}
if (audioManager.isBluetoothScoOn()) {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
audioManager.setSpeakerphoneOn(false);
audioManager.setMicrophoneMute(false);
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(null);
}
}

View File

@ -1,161 +0,0 @@
/*
* Copyright (C) 2009 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.webrtc.locks;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Message;
import org.thoughtcrime.securesms.logging.Log;
/**
* This class is used to listen to the accelerometer to monitor the
* orientation of the phone. The client of this class is notified when
* the orientation changes between horizontal and vertical.
*/
public final class AccelerometerListener {
private static final String TAG = "AccelerometerListener";
private static final boolean DEBUG = true;
private static final boolean VDEBUG = false;
private SensorManager mSensorManager;
private Sensor mSensor;
// mOrientation is the orientation value most recently reported to the client.
private int mOrientation;
// mPendingOrientation is the latest orientation computed based on the sensor value.
// This is sent to the client after a rebounce delay, at which point it is copied to
// mOrientation.
private int mPendingOrientation;
private OrientationListener mListener;
// Device orientation
public static final int ORIENTATION_UNKNOWN = 0;
public static final int ORIENTATION_VERTICAL = 1;
public static final int ORIENTATION_HORIZONTAL = 2;
private static final int ORIENTATION_CHANGED = 1234;
private static final int VERTICAL_DEBOUNCE = 100;
private static final int HORIZONTAL_DEBOUNCE = 500;
private static final double VERTICAL_ANGLE = 50.0;
public interface OrientationListener {
public void orientationChanged(int orientation);
}
public AccelerometerListener(Context context, OrientationListener listener) {
mListener = listener;
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
public void enable(boolean enable) {
if (DEBUG) Log.d(TAG, "enable(" + enable + ")");
synchronized (this) {
if (enable) {
mOrientation = ORIENTATION_UNKNOWN;
mPendingOrientation = ORIENTATION_UNKNOWN;
mSensorManager.registerListener(mSensorListener, mSensor,
SensorManager.SENSOR_DELAY_NORMAL);
} else {
mSensorManager.unregisterListener(mSensorListener);
mHandler.removeMessages(ORIENTATION_CHANGED);
}
}
}
private void setOrientation(int orientation) {
synchronized (this) {
if (mPendingOrientation == orientation) {
// Pending orientation has not changed, so do nothing.
return;
}
// Cancel any pending messages.
// We will either start a new timer or cancel alltogether
// if the orientation has not changed.
mHandler.removeMessages(ORIENTATION_CHANGED);
if (mOrientation != orientation) {
// Set timer to send an event if the orientation has changed since its
// previously reported value.
mPendingOrientation = orientation;
Message m = mHandler.obtainMessage(ORIENTATION_CHANGED);
// set delay to our debounce timeout
int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE
: HORIZONTAL_DEBOUNCE);
mHandler.sendMessageDelayed(m, delay);
} else {
// no message is pending
mPendingOrientation = ORIENTATION_UNKNOWN;
}
}
}
private void onSensorEvent(double x, double y, double z) {
if (VDEBUG) Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")");
// If some values are exactly zero, then likely the sensor is not powered up yet.
// ignore these events to avoid false horizontal positives.
if (x == 0.0 || y == 0.0 || z == 0.0) return;
// magnitude of the acceleration vector projected onto XY plane
double xy = Math.sqrt(x * x + y * y);
// compute the vertical angle
double angle = Math.atan2(xy, z);
// convert to degrees
angle = angle * 180.0 / Math.PI;
int orientation = (angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL);
if (VDEBUG) Log.d(TAG, "angle: " + angle + " orientation: " + orientation);
setOrientation(orientation);
}
SensorEventListener mSensorListener = new SensorEventListener() {
public void onSensorChanged(SensorEvent event) {
onSensorEvent(event.values[0], event.values[1], event.values[2]);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// ignore
}
};
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case ORIENTATION_CHANGED:
synchronized (this) {
mOrientation = mPendingOrientation;
if (DEBUG) {
Log.d(TAG, "orientation: " +
(mOrientation == ORIENTATION_HORIZONTAL ? "horizontal"
: (mOrientation == ORIENTATION_VERTICAL ? "vertical"
: "unknown")));
}
mListener.orientationChanged(mOrientation);
}
break;
}
}
};
}

View File

@ -1,147 +0,0 @@
package org.thoughtcrime.securesms.webrtc.locks;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.os.PowerManager;
import android.provider.Settings;
import org.thoughtcrime.securesms.logging.Log;
/**
* Maintains wake lock state.
*
* @author Stuart O. Anderson
*/
public class LockManager {
private static final String TAG = LockManager.class.getSimpleName();
private final PowerManager.WakeLock fullLock;
private final PowerManager.WakeLock partialLock;
private final WifiManager.WifiLock wifiLock;
private final ProximityLock proximityLock;
private final AccelerometerListener accelerometerListener;
private final boolean wifiLockEnforced;
private int orientation = AccelerometerListener.ORIENTATION_UNKNOWN;
private boolean proximityDisabled = false;
public enum PhoneState {
IDLE,
PROCESSING, //used when the phone is active but before the user should be alerted.
INTERACTIVE,
IN_CALL,
IN_VIDEO
}
private enum LockState {
FULL,
PARTIAL,
SLEEP,
PROXIMITY
}
public LockManager(Context context) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "signal:full");
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial");
proximityLock = new ProximityLock(pm);
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi");
fullLock.setReferenceCounted(false);
partialLock.setReferenceCounted(false);
wifiLock.setReferenceCounted(false);
accelerometerListener = new AccelerometerListener(context, new AccelerometerListener.OrientationListener() {
@Override
public void orientationChanged(int newOrientation) {
orientation = newOrientation;
Log.d(TAG, "Orentation Update: " + newOrientation);
updateInCallLockState();
}
});
wifiLockEnforced = isWifiPowerActiveModeEnabled(context);
}
private boolean isWifiPowerActiveModeEnabled(Context context) {
int wifi_pwr_active_mode = Settings.Secure.getInt(context.getContentResolver(), "wifi_pwr_active_mode", -1);
Log.d(TAG, "Wifi Activity Policy: " + wifi_pwr_active_mode);
if (wifi_pwr_active_mode == 0) {
return false;
}
return true;
}
private void updateInCallLockState() {
if (orientation != AccelerometerListener.ORIENTATION_HORIZONTAL && wifiLockEnforced && !proximityDisabled) {
setLockState(LockState.PROXIMITY);
} else {
setLockState(LockState.FULL);
}
}
public void updatePhoneState(PhoneState state) {
switch(state) {
case IDLE:
setLockState(LockState.SLEEP);
accelerometerListener.enable(false);
break;
case PROCESSING:
setLockState(LockState.PARTIAL);
accelerometerListener.enable(false);
break;
case INTERACTIVE:
setLockState(LockState.FULL);
accelerometerListener.enable(false);
break;
case IN_VIDEO:
proximityDisabled = true;
accelerometerListener.enable(false);
updateInCallLockState();
break;
case IN_CALL:
proximityDisabled = false;
accelerometerListener.enable(true);
updateInCallLockState();
break;
}
}
private synchronized void setLockState(LockState newState) {
switch(newState) {
case FULL:
fullLock.acquire();
partialLock.acquire();
wifiLock.acquire();
proximityLock.release();
break;
case PARTIAL:
partialLock.acquire();
wifiLock.acquire();
fullLock.release();
proximityLock.release();
break;
case SLEEP:
fullLock.release();
partialLock.release();
wifiLock.release();
proximityLock.release();
break;
case PROXIMITY:
partialLock.acquire();
proximityLock.acquire();
wifiLock.acquire();
fullLock.release();
break;
default:
throw new IllegalArgumentException("Unhandled Mode: " + newState);
}
Log.d(TAG, "Entered Lock State: " + newState);
}
}

View File

@ -1,94 +0,0 @@
package org.thoughtcrime.securesms.webrtc.locks;
import android.os.Build;
import android.os.PowerManager;
import org.thoughtcrime.securesms.logging.Log;
import org.session.libsignal.libsignal.util.guava.Optional;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Controls access to the proximity lock.
* The proximity lock is not part of the public API.
*
* @author Stuart O. Anderson
*/
class ProximityLock {
private static final String TAG = ProximityLock.class.getSimpleName();
private final Method wakelockParameterizedRelease = getWakelockParamterizedReleaseMethod();
private final Optional<PowerManager.WakeLock> proximityLock;
private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
private static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1;
ProximityLock(PowerManager pm) {
proximityLock = getProximityLock(pm);
}
private Optional<PowerManager.WakeLock> getProximityLock(PowerManager pm) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
return Optional.fromNullable(pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "signal:proximity"));
} else {
return Optional.absent();
}
} else {
try {
return Optional.fromNullable(pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "signal:incall"));
} catch (Throwable t) {
Log.e(TAG, "Failed to create proximity lock", t);
return Optional.absent();
}
}
}
public void acquire() {
if (!proximityLock.isPresent() || proximityLock.get().isHeld()) {
return;
}
proximityLock.get().acquire();
}
public void release() {
if (!proximityLock.isPresent() || !proximityLock.get().isHeld()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
proximityLock.get().release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
} else {
boolean released = false;
if (wakelockParameterizedRelease != null) {
try {
wakelockParameterizedRelease.invoke(proximityLock.get(), WAIT_FOR_PROXIMITY_NEGATIVE);
released = true;
} catch (IllegalAccessException e) {
Log.w(TAG, e);
} catch (InvocationTargetException e) {
Log.w(TAG, e);
}
}
if (!released) {
proximityLock.get().release();
}
}
Log.d(TAG, "Released proximity lock:" + proximityLock.get().isHeld());
}
private static Method getWakelockParamterizedReleaseMethod() {
try {
return PowerManager.WakeLock.class.getDeclaredMethod("release", Integer.TYPE);
} catch (NoSuchMethodException e) {
Log.d(TAG, "Parameterized WakeLock release not available on this device.");
}
return null;
}
}

View File

@ -1,11 +0,0 @@
JNI_DIR := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := native-utils
LOCAL_C_INCLUDES := $(JNI_DIR)/utils/
LOCAL_CFLAGS += -Wall
LOCAL_SRC_FILES := $(JNI_DIR)/utils/org_thoughtcrime_securesms_util_FileUtils.cpp
include $(BUILD_SHARED_LIBRARY)

View File

@ -1,6 +0,0 @@
# Built with NDK 19.2.5345600
APP_ABI := armeabi-v7a x86 arm64-v8a x86_64
APP_PLATFORM := android-19
APP_STL := c++_static
APP_CPPFLAGS += -fexceptions
APP_OPTIM := debug

View File

@ -1,31 +0,0 @@
#include "org_thoughtcrime_securesms_util_FileUtils.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *env, jclass clazz, jobject fileDescriptor)
{
jclass fdClass = env->GetObjectClass(fileDescriptor);
if (fdClass == NULL) {
return -1;
}
jfieldID fdFieldId = env->GetFieldID(fdClass, "descriptor", "I");
if (fdFieldId == NULL) {
return -1;
}
int fd = env->GetIntField(fileDescriptor, fdFieldId);
struct stat stat_struct;
if (fstat(fd, &stat_struct) != 0) {
return -1;
}
return stat_struct.st_uid;
}

View File

@ -1,21 +0,0 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_thoughtcrime_securesms_util_FileUtils */
#ifndef _Included_org_thoughtcrime_securesms_util_FileUtils
#define _Included_org_thoughtcrime_securesms_util_FileUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_thoughtcrime_securesms_util_FileUtils
* Method: getFileDescriptorOwner
* Signature: (Ljava/io/FileDescriptor;)I
*/
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *, jclass, jobject);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,159 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/shared_contact_details_header_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:id="@+id/contact_details_avatar"
android:layout_width="125dp"
android:layout_height="125dp"
android:padding="8dp"
android:transitionName="avatar"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/contact_details_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:transitionName="name"
android:gravity="center"
android:textSize="20sp"
tools:text="Peter Parker"/>
<TextView
android:id="@+id/contact_details_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:transitionName="number"
android:layout_marginBottom="14dp"
android:gravity="center"
tools:text="(610) 555-5555"/>
<Button
android:id="@+id/contact_details_add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_gravity="center"
style="@style/Button.Primary"
android:text="@string/SharedContactDetailsActivity_add_to_contacts" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/contact_details_invite_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_gravity="center"
style="@style/Button.Borderless"
android:text="@string/SharedContactDetailsActivity_invite_to_signal"
android:visibility="gone"
tools:visibility="gone"/>
<LinearLayout
android:id="@+id/contact_details_engage_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
style="?attr/buttonBarStyle"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:id="@+id/contact_details_message_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center"
android:padding="10dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:tint="@color/signal_primary"
android:src="@drawable/message_24dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/SharedContactDetailsActivity_signal_message"
android:textColor="@color/signal_primary"/>
</LinearLayout>
<LinearLayout
android:id="@+id/contact_details_call_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center"
android:padding="10dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:tint="@color/signal_primary"
android:src="@drawable/phone_24dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/SharedContactDetailsActivity_signal_call"
android:textColor="@color/signal_primary"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</LinearLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/TextSecure.LightActionBar.DarkText"
android:background="@color/transparent"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contact_details_fields"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -215,14 +215,6 @@
</LinearLayout>
<Button
android:id="@+id/register_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@string/conversation_activity__enable_signal_messages"
android:visibility="gone" />
<Button
android:id="@+id/unblock_button"
android:layout_width="fill_parent"

View File

@ -122,14 +122,6 @@
app:quote_colorSecondary="@color/text"
tools:visibility="visible"/>
<ViewStub
android:id="@+id/shared_contact_view_stub"
android:layout="@layout/conversation_item_received_shared_contact"
android:layout_width="@dimen/media_bubble_default_dimens"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_top_padding"
android:visibility="gone"/>
<ViewStub
android:id="@+id/image_view_stub"
android:layout_width="wrap_content"

View File

@ -58,14 +58,6 @@
app:quote_colorSecondary="@color/text"
tools:visibility="visible"/>
<ViewStub
android:id="@+id/shared_contact_view_stub"
android:layout="@layout/conversation_item_sent_shared_contact"
android:layout_width="@dimen/media_bubble_default_dimens"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/medium_spacing"
android:visibility="gone"/>
<ViewStub
android:id="@+id/image_view_stub"
android:layout_width="wrap_content"

View File

@ -11,14 +11,4 @@
android:src="@drawable/ic_message_white_24dp"
android:layout_marginEnd="20dp"/>
<ImageView android:id="@+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_call_white_24dp"/>
<ImageView android:id="@+id/secure_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_call_secure_white_24dp"/>
</LinearLayout>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<org.thoughtcrime.securesms.components.webrtc.WebRtcCallScreen android:id="@+id/callScreen"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</FrameLayout>