mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 16:33:39 +00:00
Unused code cleanup.
This commit is contained in:
parent
9bf3540d22
commit
4fb4709ec2
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
// }
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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() + "]";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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 &&
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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>
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user