mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-01 01:52:15 +00:00
commit
d64d34bd25
AndroidManifest.xml
res
drawable
layout
activity_seed.xmldevice_list_fragment.xmldevice_list_item_view.xmlfragment_device_list_bottom_sheet.xmlfragment_scan_qr_code.xmlprofile_create_activity.xmlview_device_linking.xml
values
xml
src/org/thoughtcrime/securesms
ApplicationContext.javaApplicationPreferencesActivity.javaConversationListActivity.javaCreateProfileActivity.javaDeviceListFragment.javaDeviceListItem.javaPassphraseRequiredActionBarActivity.java
avatar
components
conversation
crypto
database
devicelist
jobs
loki
DeviceLinkingDelegate.ktDeviceLinkingDialog.ktDeviceLinkingDialogDelegate.ktDeviceLinkingView.ktDeviceLinkingViewDelegate.ktDeviceListBottomSheetFragment.ktDisplayNameActivity.ktJazzIdenticonContactPhoto.ktLinkedDevicesActivity.ktLokiAPIDatabase.ktLokiMessageDatabase.ktLokiPreKeyBundleDatabase.ktLokiPublicChatPoller.ktLokiUserDatabase.ktMnemonicUtilities.ktMultiDeviceUtilities.ktPushBackgroundMessageSendJob.ktRecipientAvatarModifiedEvent.ktScanQRCodeFragment.ktSeedActivity.kt
notifications
preferences/widgets
qr
recipients
registration
service
sms
util
1441
AndroidManifest.xml
1441
AndroidManifest.xml
File diff suppressed because it is too large
Load Diff
5
res/drawable/ic_edit_white_24dp.xml
Normal file
5
res/drawable/ic_edit_white_24dp.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||||
|
</vector>
|
5
res/drawable/ic_phonelink_erase_white_24dp.xml
Normal file
5
res/drawable/ic_phonelink_erase_white_24dp.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M13,8.2l-1,-1 -4,4 -4,-4 -1,1 4,4 -4,4 1,1 4,-4 4,4 1,-1 -4,-4 4,-4zM19,1H9c-1.1,0 -2,0.9 -2,2v3h2V4h10v16H9v-2H7v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||||
|
</vector>
|
@ -96,6 +96,17 @@
|
|||||||
app:labeledEditText_background="@color/loki_darkest_gray"
|
app:labeledEditText_background="@color/loki_darkest_gray"
|
||||||
app:labeledEditText_label="@string/activity_key_pair_public_key_edit_text_label"/>
|
app:labeledEditText_label="@string/activity_key_pair_public_key_edit_text_label"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/scanQRButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:background="@color/transparent"
|
||||||
|
android:elevation="0dp"
|
||||||
|
android:stateListAnimator="@null"
|
||||||
|
android:text="@string/fragment_scan_qr_code_title"
|
||||||
|
android:textColor="@color/signal_primary"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/toggleRestoreModeButton"
|
android:id="@+id/toggleRestoreModeButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -123,7 +134,7 @@
|
|||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
android:textColor="@color/signal_primary"
|
android:textColor="@color/signal_primary"
|
||||||
android:text="Link Device"
|
android:text="@string/activity_key_pair_toggle_mode_button_title_3"
|
||||||
android:elevation="0dp"
|
android:elevation="0dp"
|
||||||
android:stateListAnimator="@null" />
|
android:stateListAnimator="@null" />
|
||||||
|
|
||||||
|
@ -1,58 +1,49 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
android:layout_height="match_parent"
|
||||||
xmlns:fab="http://schemas.android.com/apk/res-auto"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical">
|
xmlns:fab="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/progress_container"
|
<ProgressBar
|
||||||
android:layout_width="fill_parent"
|
android:id="@+id/activityIndicator"
|
||||||
android:layout_height="fill_parent"
|
android:indeterminate="true"
|
||||||
android:gravity="center"
|
android:layout_width="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone" >
|
android:layout_centerInParent="true"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<ProgressBar android:id="@+id/progress"
|
<TextView
|
||||||
android:indeterminate="true"
|
android:id="@+id/emptyStateTextView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" >
|
android:layout_height="match_parent"
|
||||||
</ProgressBar>
|
android:gravity="center"
|
||||||
</LinearLayout>
|
android:textSize="20sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:text="@string/device_list_fragment__no_devices_linked"
|
||||||
|
android:paddingStart="16dip"
|
||||||
|
android:paddingEnd="16dip"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
<TextView android:id="@+id/empty"
|
<ListView
|
||||||
android:layout_width="match_parent"
|
android:id="@id/android:list"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_gravity="center|center_vertical"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center|center_vertical"
|
android:drawSelectorOnTop="false" />
|
||||||
android:textSize="20sp"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:text="@string/device_list_fragment__no_devices_linked"
|
|
||||||
android:paddingStart="16dip"
|
|
||||||
android:paddingEnd="16dip"
|
|
||||||
android:layout_weight="1"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
<ListView android:id="@id/android:list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:drawSelectorOnTop="false"
|
|
||||||
android:paddingStart="16dip"
|
|
||||||
android:paddingEnd="16dip"
|
|
||||||
tools:visibility="gone"/>
|
|
||||||
|
|
||||||
<com.melnykov.fab.FloatingActionButton
|
<com.melnykov.fab.FloatingActionButton
|
||||||
android:id="@+id/add_device"
|
android:id="@+id/addDeviceButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|right"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_margin="16dp"
|
android:layout_alignParentRight="true"
|
||||||
android:src="@drawable/ic_add_white_original_24dp"
|
android:layout_margin="16dp"
|
||||||
android:focusable="true"
|
android:src="@drawable/ic_add_white_original_24dp"
|
||||||
android:contentDescription="@string/device_list_fragment__link_new_device"
|
android:focusable="true"
|
||||||
fab:fab_colorNormal="?fab_color"
|
android:contentDescription="@string/device_list_fragment__link_new_device"
|
||||||
fab:fab_colorPressed="@color/textsecure_primary_dark"
|
fab:fab_colorNormal="?fab_color"
|
||||||
fab:fab_colorRipple="@color/textsecure_primary_dark" />
|
fab:fab_colorPressed="@color/textsecure_primary_dark"
|
||||||
|
fab:fab_colorRipple="@color/textsecure_primary_dark" />
|
||||||
|
|
||||||
</LinearLayout>
|
</RelativeLayout>
|
@ -1,33 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.thoughtcrime.securesms.DeviceListItem xmlns:android="http://schemas.android.com/apk/res/android"
|
<org.thoughtcrime.securesms.DeviceListItem
|
||||||
android:orientation="vertical"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:orientation="vertical"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_height="64dp"
|
||||||
android:layout_marginEnd="16dp">
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<TextView android:id="@+id/name"
|
<TextView
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:id="@+id/name"
|
||||||
android:textColor="?attr/conversation_list_item_contact_color"
|
android:layout_width="wrap_content"
|
||||||
android:singleLine="true"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_marginTop="8dp"
|
android:singleLine="true"
|
||||||
android:layout_width="wrap_content"
|
android:text="Name"
|
||||||
android:layout_height="wrap_content" />
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?attr/conversation_list_item_contact_color"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
<TextView android:id="@+id/created"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/shortId"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="?attr/conversation_list_item_subject_color"
|
android:layout_marginTop="2dp"
|
||||||
android:fontFamily="sans-serif-light" />
|
android:ellipsize="marquee"
|
||||||
|
android:singleLine="true"
|
||||||
<TextView android:id="@+id/active"
|
android:text="shortId"
|
||||||
android:layout_width="match_parent"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:layout_height="wrap_content"
|
android:textColor="#A2A2A2"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textSize="14sp" />
|
||||||
android:textColor="?attr/conversation_list_item_subject_color"
|
|
||||||
android:fontFamily="sans-serif-light"
|
|
||||||
android:layout_marginBottom="8dp" />
|
|
||||||
|
|
||||||
</org.thoughtcrime.securesms.DeviceListItem>
|
</org.thoughtcrime.securesms.DeviceListItem>
|
22
res/layout/fragment_device_list_bottom_sheet.xml
Normal file
22
res/layout/fragment_device_list_bottom_sheet.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:behavior_hideable="true"
|
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/editDisplayNameText"
|
||||||
|
style="@style/ActionItem"
|
||||||
|
android:drawableStart="@drawable/ic_edit_white_24dp"
|
||||||
|
android:text="@string/fragment_device_list_edit_device_name_title"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/unlinkDeviceText"
|
||||||
|
style="@style/ActionItem"
|
||||||
|
android:drawableStart="@drawable/ic_phonelink_erase_white_24dp"
|
||||||
|
android:text="@string/fragment_device_list_unlink_device_title" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -24,13 +24,14 @@
|
|||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/descriptionTextView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:padding="32dp"
|
android:padding="32dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="@color/loki_darkest_gray"
|
android:background="@color/loki_darkest_gray"
|
||||||
android:text="@string/fragment_scan_qr_code_explanation"
|
android:text="@string/fragment_scan_qr_code_explanation_new_conversation"
|
||||||
android:textColor="?android:textColorPrimary" />
|
android:textColor="?android:textColorPrimary" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
android:id="@+id/avatar_background"
|
android:id="@+id/avatar_background"
|
||||||
android:layout_width="80dp"
|
android:layout_width="80dp"
|
||||||
android:layout_height="80dp"
|
android:layout_height="80dp"
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:src="@drawable/circle_tintable"
|
android:src="@drawable/circle_tintable"
|
||||||
@ -56,7 +55,6 @@
|
|||||||
android:id="@+id/avatar_placeholder"
|
android:id="@+id/avatar_placeholder"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:src="@drawable/ic_profile_default"
|
android:src="@drawable/ic_profile_default"
|
||||||
@ -71,7 +69,6 @@
|
|||||||
android:id="@+id/avatar"
|
android:id="@+id/avatar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/avatar_background"
|
app:layout_constraintBottom_toBottomOf="@+id/avatar_background"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/avatar_background"
|
app:layout_constraintEnd_toEndOf="@+id/avatar_background"
|
||||||
app:layout_constraintStart_toStartOf="@+id/avatar_background"
|
app:layout_constraintStart_toStartOf="@+id/avatar_background"
|
||||||
@ -81,7 +78,6 @@
|
|||||||
android:id="@+id/camera_icon"
|
android:id="@+id/camera_icon"
|
||||||
android:layout_width="60dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_marginStart="35dp"
|
android:layout_marginStart="35dp"
|
||||||
android:layout_marginTop="35dp"
|
android:layout_marginTop="35dp"
|
||||||
android:cropToPadding="false"
|
android:cropToPadding="false"
|
||||||
@ -94,7 +90,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginStart="49dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@ -105,7 +101,7 @@
|
|||||||
app:layout_constraintBottom_toTopOf="@+id/description_text"
|
app:layout_constraintBottom_toTopOf="@+id/description_text"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/emoji_toggle"
|
app:layout_constraintEnd_toStartOf="@+id/emoji_toggle"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toStartOf="@+id/avatar_background"
|
app:layout_constraintStart_toEndOf="@+id/avatar_background"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiToggle
|
<org.thoughtcrime.securesms.components.emoji.EmojiToggle
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
@ -15,6 +16,13 @@
|
|||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:progressTint="@color/white" />
|
android:progressTint="@color/white" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/qrCodeImageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/white" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/titleTextView"
|
android:id="@+id/titleTextView"
|
||||||
style="@style/Signal.Text.Headline"
|
style="@style/Signal.Text.Headline"
|
||||||
|
@ -115,4 +115,10 @@
|
|||||||
|
|
||||||
<dimen name="recording_voice_lock_target">-150dp</dimen>
|
<dimen name="recording_voice_lock_target">-150dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="default_margin">16dp</dimen>
|
||||||
|
<dimen name="drawable_padding">24dp</dimen>
|
||||||
|
<dimen name="text_size">16sp</dimen>
|
||||||
|
<dimen name="normal_padding">16dp</dimen>
|
||||||
|
<dimen name="action_item_height">56dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -293,12 +293,14 @@
|
|||||||
|
|
||||||
<!-- DeviceListActivity -->
|
<!-- DeviceListActivity -->
|
||||||
<string name="DeviceListActivity_unlink_s">Unlink \'%s\'?</string>
|
<string name="DeviceListActivity_unlink_s">Unlink \'%s\'?</string>
|
||||||
<string name="DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive">By unlinking this device, it will no longer be able to send or receive messages.</string>
|
<string name="DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive">This device will no longer be able to send or receive messages.</string>
|
||||||
<string name="DeviceListActivity_network_connection_failed">Network connection failed</string>
|
<string name="DeviceListActivity_network_connection_failed">Network connection failed</string>
|
||||||
<string name="DeviceListActivity_try_again">Try again</string>
|
<string name="DeviceListActivity_try_again">Try again</string>
|
||||||
<string name="DeviceListActivity_unlinking_device">Unlinking device...</string>
|
<string name="DeviceListActivity_unlinking_device">Unlinking device...</string>
|
||||||
<string name="DeviceListActivity_unlinking_device_no_ellipsis">Unlinking device</string>
|
<string name="DeviceListActivity_unlinking_device_no_ellipsis">Unlinking device</string>
|
||||||
<string name="DeviceListActivity_network_failed">Network failed!</string>
|
<string name="DeviceListActivity_network_failed">Network failed!</string>
|
||||||
|
<string name="DeviceListActivity_unlinked_device">Successfully unlinked device</string>
|
||||||
|
<string name="DeviceListActivity_edit_device_name">Edit device name</string>
|
||||||
|
|
||||||
<!-- DeviceListItem -->
|
<!-- DeviceListItem -->
|
||||||
<string name="DeviceListItem_unnamed_device">Unnamed device</string>
|
<string name="DeviceListItem_unnamed_device">Unnamed device</string>
|
||||||
@ -946,7 +948,7 @@
|
|||||||
<string name="device_link_fragment__link_device">Link device</string>
|
<string name="device_link_fragment__link_device">Link device</string>
|
||||||
|
|
||||||
<!-- device_list_fragment -->
|
<!-- device_list_fragment -->
|
||||||
<string name="device_list_fragment__no_devices_linked">No devices linked</string>
|
<string name="device_list_fragment__no_devices_linked">You don\'t have any linked devices yet</string>
|
||||||
<string name="device_list_fragment__link_new_device">Link new device</string>
|
<string name="device_list_fragment__link_new_device">Link new device</string>
|
||||||
|
|
||||||
<!-- experience_upgrade_activity -->
|
<!-- experience_upgrade_activity -->
|
||||||
@ -1157,7 +1159,7 @@
|
|||||||
<string name="AndroidManifest__log_submit">Submit debug log</string>
|
<string name="AndroidManifest__log_submit">Submit debug log</string>
|
||||||
<string name="AndroidManifest__media_preview">Media preview</string>
|
<string name="AndroidManifest__media_preview">Media preview</string>
|
||||||
<string name="AndroidManifest__message_details">Message details</string>
|
<string name="AndroidManifest__message_details">Message details</string>
|
||||||
<string name="AndroidManifest__linked_devices">Linked devices</string>
|
<string name="AndroidManifest__linked_devices">Linked Devices</string>
|
||||||
<string name="AndroidManifest__invite_friends">Invite friends</string>
|
<string name="AndroidManifest__invite_friends">Invite friends</string>
|
||||||
<string name="AndroidManifest_archived_conversations">Archived conversations</string>
|
<string name="AndroidManifest_archived_conversations">Archived conversations</string>
|
||||||
<string name="AndroidManifest_remove_photo">Remove photo</string>
|
<string name="AndroidManifest_remove_photo">Remove photo</string>
|
||||||
@ -1572,11 +1574,11 @@
|
|||||||
<!-- Conversation list activity -->
|
<!-- Conversation list activity -->
|
||||||
<string name="activity_conversation_list_empty_state_message">Looks like you don\'t have any conversations yet. Get started by messaging a friend.</string>
|
<string name="activity_conversation_list_empty_state_message">Looks like you don\'t have any conversations yet. Get started by messaging a friend.</string>
|
||||||
<!-- Settings activity -->
|
<!-- Settings activity -->
|
||||||
<string name="activity_settings_secondary_device_tag">Secondary device</string>
|
<string name="activity_settings_linked_device_tag">Linked device (%s)</string>
|
||||||
<string name="activity_settings_public_key_copied_message">Copied to clipboard</string>
|
<string name="activity_settings_public_key_copied_message">Copied to clipboard</string>
|
||||||
<string name="activity_settings_share_public_key_button_title">Share Public Key</string>
|
<string name="activity_settings_share_public_key_button_title">Share Public Key</string>
|
||||||
<string name="activity_settings_show_qr_code_button_title">Show QR Code</string>
|
<string name="activity_settings_show_qr_code_button_title">Show QR Code</string>
|
||||||
<string name="activity_settings_link_device_button_title">Link Device</string>
|
<string name="activity_settings_linked_devices_button_title">Linked Devices</string>
|
||||||
<string name="activity_settings_show_seed_button_title">Show Seed</string>
|
<string name="activity_settings_show_seed_button_title">Show Seed</string>
|
||||||
<string name="activity_settings_seed_dialog_title">Your Seed</string>
|
<string name="activity_settings_seed_dialog_title">Your Seed</string>
|
||||||
<string name="activity_settings_seed_dialog_copy_button_title">Copy</string>
|
<string name="activity_settings_seed_dialog_copy_button_title">Copy</string>
|
||||||
@ -1636,11 +1638,18 @@
|
|||||||
<string name="view_device_linking_cancel_button_title">Cancel</string>
|
<string name="view_device_linking_cancel_button_title">Cancel</string>
|
||||||
<!-- Scan QR code fragment -->
|
<!-- Scan QR code fragment -->
|
||||||
<string name="fragment_scan_qr_code_title">Scan QR Code</string>
|
<string name="fragment_scan_qr_code_title">Scan QR Code</string>
|
||||||
<string name="fragment_scan_qr_code_explanation">Scan the QR code of the person you\'d like to securely message. They can find their QR code by going into Loki Messenger\'s in-app settings and clicking \"Show QR Code\".</string>
|
<string name="fragment_scan_qr_code_explanation_new_conversation">Scan the QR code of the person you\'d like to securely message. They can find their QR code by going into Loki Messenger\'s in-app settings and clicking \"Show QR Code\".</string>
|
||||||
|
<string name="fragment_scan_qr_code_explanation_link_device">Link to an existing device by going into its in-app settings and clicking \"Link Device\".</string>
|
||||||
<string name="fragment_scan_qr_code_camera_permission_dialog_message">Loki Messenger needs camera access to scan QR codes.</string>
|
<string name="fragment_scan_qr_code_camera_permission_dialog_message">Loki Messenger needs camera access to scan QR codes.</string>
|
||||||
<!-- Conversation activity -->
|
<!-- Conversation activity -->
|
||||||
<string name="activity_conversation_copy_public_key_button_title">Copy public key</string>
|
<string name="activity_conversation_copy_public_key_button_title">Copy public key</string>
|
||||||
<!-- Conversation list activity -->
|
<!-- Conversation list activity -->
|
||||||
<string name="activity_conversation_list_add_public_chat_button_title">Add Public Chat</string>
|
<string name="activity_conversation_list_add_public_chat_button_title">Add Public Chat</string>
|
||||||
|
<!-- Device list bottom sheet fragment -->
|
||||||
|
<string name="fragment_device_list_edit_device_name_title">Edit device name</string>
|
||||||
|
<string name="fragment_device_list_unlink_device_title">Unlink device</string>
|
||||||
|
<!-- Device unlink dialog -->
|
||||||
|
<string name="dialog_device_unlink_title">Device unlinked</string>
|
||||||
|
<string name="dialog_device_unlink_message">This device has been successfully unlinked</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -242,4 +242,15 @@
|
|||||||
<item name="colorControlActivated">@color/white</item>
|
<item name="colorControlActivated">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ActionItem">
|
||||||
|
<item name="android:textSize">@dimen/text_size</item>
|
||||||
|
<item name="android:drawablePadding">@dimen/drawable_padding</item>
|
||||||
|
<item name="android:layout_width">match_parent</item>
|
||||||
|
<item name="android:layout_height">@dimen/action_item_height</item>
|
||||||
|
<item name="android:padding">@dimen/normal_padding</item>
|
||||||
|
<item name="android:gravity">center_vertical</item>
|
||||||
|
<item name="android:selectable">true</item>
|
||||||
|
<item name="android:foreground">?attr/selectableItemBackground</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
<item name="android:windowBackground">@color/loki_darkest_gray</item>
|
<item name="android:windowBackground">@color/loki_darkest_gray</item>
|
||||||
<item name="alertDialogTheme">@style/AppCompatAlertDialogStyleLight</item>
|
<item name="alertDialogTheme">@style/AppCompatAlertDialogStyleLight</item>
|
||||||
<item name="android:alertDialogTheme">@style/AppCompatDialogStyleLight</item>
|
<item name="android:alertDialogTheme">@style/AppCompatDialogStyleLight</item>
|
||||||
|
<item name="bottomSheetDialogTheme">@style/Theme.MaterialComponents.Light.BottomSheetDialog</item>
|
||||||
<!--<item name="android:windowContentOverlay">@drawable/compat_actionbar_shadow_background</item>-->
|
<!--<item name="android:windowContentOverlay">@drawable/compat_actionbar_shadow_background</item>-->
|
||||||
|
|
||||||
<item name="attachment_type_selector_background">@color/white</item>
|
<item name="attachment_type_selector_background">@color/white</item>
|
||||||
@ -317,6 +318,7 @@
|
|||||||
<item name="android:windowBackground">@color/loki_darkest_gray</item>
|
<item name="android:windowBackground">@color/loki_darkest_gray</item>
|
||||||
<item name="alertDialogTheme">@style/AppCompatAlertDialogStyleDark</item>
|
<item name="alertDialogTheme">@style/AppCompatAlertDialogStyleDark</item>
|
||||||
<item name="android:alertDialogTheme">@style/AppCompatDialogStyleDark</item>
|
<item name="android:alertDialogTheme">@style/AppCompatDialogStyleDark</item>
|
||||||
|
<item name="bottomSheetDialogTheme">@style/Theme.MaterialComponents.BottomSheetDialog</item>
|
||||||
|
|
||||||
<item name="attachment_type_selector_background">@color/gray95</item>
|
<item name="attachment_type_selector_background">@color/gray95</item>
|
||||||
<item name="attachment_document_icon_small">@drawable/ic_document_small_dark</item>
|
<item name="attachment_document_icon_small">@drawable/ic_document_small_dark</item>
|
||||||
|
@ -33,6 +33,10 @@
|
|||||||
android:title="@string/preferences__advanced"
|
android:title="@string/preferences__advanced"
|
||||||
android:icon="@drawable/ic_advanced_24dp"/> -->
|
android:icon="@drawable/ic_advanced_24dp"/> -->
|
||||||
|
|
||||||
|
<Preference android:key="preference_category_linked_devices"
|
||||||
|
android:title="@string/activity_settings_linked_devices_button_title"
|
||||||
|
android:icon="@drawable/icon_link"/>
|
||||||
|
|
||||||
<Preference android:key="preference_category_public_key"
|
<Preference android:key="preference_category_public_key"
|
||||||
android:title="@string/activity_settings_share_public_key_button_title"
|
android:title="@string/activity_settings_share_public_key_button_title"
|
||||||
android:icon="@drawable/icon_share"/>
|
android:icon="@drawable/icon_share"/>
|
||||||
@ -41,10 +45,6 @@
|
|||||||
android:title="@string/activity_settings_show_qr_code_button_title"
|
android:title="@string/activity_settings_show_qr_code_button_title"
|
||||||
android:icon="@drawable/icon_qr_code"/>
|
android:icon="@drawable/icon_qr_code"/>
|
||||||
|
|
||||||
<Preference android:key="preference_category_link_device"
|
|
||||||
android:title="Link Device"
|
|
||||||
android:icon="@drawable/icon_link"/>
|
|
||||||
|
|
||||||
<Preference android:key="preference_category_seed"
|
<Preference android:key="preference_category_seed"
|
||||||
android:title="@string/activity_settings_show_seed_button_title"
|
android:title="@string/activity_settings_show_seed_button_title"
|
||||||
android:icon="@drawable/icon_seedling"/>
|
android:icon="@drawable/icon_seedling"/>
|
||||||
|
@ -20,10 +20,13 @@ import android.annotation.SuppressLint;
|
|||||||
import android.arch.lifecycle.DefaultLifecycleObserver;
|
import android.arch.lifecycle.DefaultLifecycleObserver;
|
||||||
import android.arch.lifecycle.LifecycleOwner;
|
import android.arch.lifecycle.LifecycleOwner;
|
||||||
import android.arch.lifecycle.ProcessLifecycleOwner;
|
import android.arch.lifecycle.ProcessLifecycleOwner;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.multidex.MultiDexApplication;
|
import android.support.multidex.MultiDexApplication;
|
||||||
@ -38,6 +41,9 @@ import org.signal.aesgcmprovider.AesGcmProvider;
|
|||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||||
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
|
||||||
@ -65,10 +71,12 @@ import org.thoughtcrime.securesms.loki.LokiAPIDatabase;
|
|||||||
import org.thoughtcrime.securesms.loki.LokiPublicChatManager;
|
import org.thoughtcrime.securesms.loki.LokiPublicChatManager;
|
||||||
import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller;
|
import org.thoughtcrime.securesms.loki.LokiRSSFeedPoller;
|
||||||
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
||||||
|
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
|
||||||
@ -88,11 +96,11 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
|||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
|
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
|
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
|
||||||
import org.whispersystems.signalservice.loki.api.LokiLongPoller;
|
import org.whispersystems.signalservice.loki.api.LokiLongPoller;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
|
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
|
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPublicChat;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiRSSFeed;
|
import org.whispersystems.signalservice.loki.api.LokiRSSFeed;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
||||||
@ -154,8 +162,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
startKovenant();
|
|
||||||
Log.i(TAG, "onCreate()");
|
Log.i(TAG, "onCreate()");
|
||||||
|
checkNeedsDatabaseReset();
|
||||||
|
startKovenant();
|
||||||
initializeSecurityProvider();
|
initializeSecurityProvider();
|
||||||
initializeLogging();
|
initializeLogging();
|
||||||
initializeCrashHandling();
|
initializeCrashHandling();
|
||||||
@ -196,7 +205,11 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
// Loki - Update device mappings
|
// Loki - Update device mappings
|
||||||
if (setUpStorageAPIIfNeeded()) {
|
if (setUpStorageAPIIfNeeded()) {
|
||||||
LokiStorageAPI.Companion.getShared().updateUserDeviceMappings();
|
LokiStorageAPI.Companion.getShared().updateUserDeviceMappings();
|
||||||
|
if (TextSecurePreferences.needsRevocationCheck(this)) {
|
||||||
|
checkNeedsRevocation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
updatePublicChatProfileAvatarIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -587,5 +600,54 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
if (lokiNewsFeedPoller != null) lokiNewsFeedPoller.startIfNeeded();
|
if (lokiNewsFeedPoller != null) lokiNewsFeedPoller.startIfNeeded();
|
||||||
if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded();
|
if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updatePublicChatProfileAvatarIfNeeded() {
|
||||||
|
AsyncTask.execute(() -> {
|
||||||
|
LokiPublicChatAPI publicChatAPI = getLokiPublicChatAPI();
|
||||||
|
if (publicChatAPI != null) {
|
||||||
|
byte[] profileKey = ProfileKeyUtil.getProfileKey(this);
|
||||||
|
String url = TextSecurePreferences.getProfileAvatarUrl(this);
|
||||||
|
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
|
||||||
|
if (ourMasterDevice != null) {
|
||||||
|
Recipient masterDevice = Recipient.from(this, Address.fromSerialized(ourMasterDevice), false).resolve();
|
||||||
|
profileKey = masterDevice.getProfileKey();
|
||||||
|
url = masterDevice.getProfileAvatar();
|
||||||
|
}
|
||||||
|
Set<String> servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers();
|
||||||
|
for (String server : servers) {
|
||||||
|
publicChatAPI.setProfilePicture(server, profileKey, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
public void checkNeedsRevocation() {
|
||||||
|
MultiDeviceUtilities.checkForRevocation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkNeedsDatabaseReset() {
|
||||||
|
if (TextSecurePreferences.resetDatabase(this)) {
|
||||||
|
boolean wasUnlinked = TextSecurePreferences.databaseResetFromUnpair(this);
|
||||||
|
TextSecurePreferences.clearAll(this);
|
||||||
|
TextSecurePreferences.setDatabaseResetFromUnpair(this, wasUnlinked); // Loki - Re-set the preference so we can use it in the starting screen to determine whether device was unlinked or not
|
||||||
|
MasterSecretUtil.clear(this);
|
||||||
|
if (this.deleteDatabase("signal.db")) {
|
||||||
|
Log.d("Loki", "Deleted database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearData() {
|
||||||
|
TextSecurePreferences.setResetDatabase(this, true);
|
||||||
|
new Handler().postDelayed(this::restartApplication, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartApplication() {
|
||||||
|
Intent intent = new Intent(this, ConversationListActivity.class);
|
||||||
|
ComponentName componentName = intent.getComponent();
|
||||||
|
Intent mainIntent = Intent.makeRestartActivityTask(componentName);
|
||||||
|
this.startActivity(mainIntent);
|
||||||
|
Runtime.getRuntime().exit(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Build.VERSION;
|
import android.os.Build.VERSION;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -40,13 +39,8 @@ import android.support.v7.app.AlertDialog;
|
|||||||
import android.support.v7.preference.Preference;
|
import android.support.v7.preference.Preference;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.loki.LinkedDevicesActivity;
|
||||||
import org.thoughtcrime.securesms.loki.DeviceLinkingDialog;
|
|
||||||
import org.thoughtcrime.securesms.loki.DeviceLinkingDialogDelegate;
|
|
||||||
import org.thoughtcrime.securesms.loki.DeviceLinkingView;
|
|
||||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
|
||||||
import org.thoughtcrime.securesms.loki.QRCodeDialog;
|
import org.thoughtcrime.securesms.loki.QRCodeDialog;
|
||||||
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
||||||
@ -57,7 +51,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
|
|||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
||||||
import org.whispersystems.signalservice.loki.utilities.SerializationKt;
|
import org.whispersystems.signalservice.loki.utilities.SerializationKt;
|
||||||
@ -89,7 +82,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
// private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
// private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
|
||||||
private static final String PREFERENCE_CATEGORY_PUBLIC_KEY = "preference_category_public_key";
|
private static final String PREFERENCE_CATEGORY_PUBLIC_KEY = "preference_category_public_key";
|
||||||
private static final String PREFERENCE_CATEGORY_QR_CODE = "preference_category_qr_code";
|
private static final String PREFERENCE_CATEGORY_QR_CODE = "preference_category_qr_code";
|
||||||
private static final String PREFERENCE_CATEGORY_LINK_DEVICE = "preference_category_link_device";
|
private static final String PREFERENCE_CATEGORY_LINKED_DEVICES = "preference_category_linked_devices";
|
||||||
private static final String PREFERENCE_CATEGORY_SEED = "preference_category_seed";
|
private static final String PREFERENCE_CATEGORY_SEED = "preference_category_seed";
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||||
@ -172,15 +165,15 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_SMS_MMS));
|
||||||
*/
|
*/
|
||||||
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_NOTIFICATIONS));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_NOTIFICATIONS));
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
|
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_APP_PROTECTION));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APP_PROTECTION));
|
||||||
/*
|
/*
|
||||||
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
|
||||||
*/
|
*/
|
||||||
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
this.findPreference(PREFERENCE_CATEGORY_CHATS)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_CHATS));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
|
||||||
/*
|
/*
|
||||||
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
|
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
|
||||||
@ -188,29 +181,18 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
|
||||||
*/
|
*/
|
||||||
this.findPreference(PREFERENCE_CATEGORY_PUBLIC_KEY)
|
this.findPreference(PREFERENCE_CATEGORY_PUBLIC_KEY)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_PUBLIC_KEY));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_PUBLIC_KEY));
|
||||||
this.findPreference(PREFERENCE_CATEGORY_QR_CODE)
|
this.findPreference(PREFERENCE_CATEGORY_QR_CODE)
|
||||||
.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_QR_CODE));
|
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_QR_CODE));
|
||||||
|
|
||||||
Preference linkDevicePreference = this.findPreference(PREFERENCE_CATEGORY_LINK_DEVICE);
|
Preference linkDevicesPreference = this.findPreference(PREFERENCE_CATEGORY_LINKED_DEVICES);
|
||||||
linkDevicePreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), PREFERENCE_CATEGORY_LINK_DEVICE));
|
linkDevicesPreference.setVisible(isMasterDevice);
|
||||||
|
linkDevicesPreference.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_LINKED_DEVICES));
|
||||||
// Disable if we hit the cap of 1 linked device
|
|
||||||
if (isMasterDevice) {
|
|
||||||
Context context = getContext();
|
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
|
||||||
boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(userHexEncodedPublicKey).size() <= 1;
|
|
||||||
linkDevicePreference.setEnabled(isDeviceLinkingEnabled);
|
|
||||||
linkDevicePreference.getIcon().setAlpha(isDeviceLinkingEnabled ? 255 : 124);
|
|
||||||
} else {
|
|
||||||
// Hide if this is a slave device
|
|
||||||
linkDevicePreference.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Preference seedPreference = this.findPreference(PREFERENCE_CATEGORY_SEED);
|
Preference seedPreference = this.findPreference(PREFERENCE_CATEGORY_SEED);
|
||||||
// Hide if this is a slave device
|
// Hide if this is a slave device
|
||||||
seedPreference.setVisible(isMasterDevice);
|
seedPreference.setVisible(isMasterDevice);
|
||||||
seedPreference.setOnPreferenceClickListener(new CategoryClickListener(getContext(), (PREFERENCE_CATEGORY_SEED)));
|
seedPreference.setOnPreferenceClickListener(new CategoryClickListener((PREFERENCE_CATEGORY_SEED)));
|
||||||
|
|
||||||
if (VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
if (VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
tintIcons(getActivity());
|
tintIcons(getActivity());
|
||||||
@ -299,16 +281,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
// this.findPreference(PREFERENCE_CATEGORY_ADVANCED).setIcon(advanced);
|
// this.findPreference(PREFERENCE_CATEGORY_ADVANCED).setIcon(advanced);
|
||||||
this.findPreference(PREFERENCE_CATEGORY_PUBLIC_KEY).setIcon(publicKey);
|
this.findPreference(PREFERENCE_CATEGORY_PUBLIC_KEY).setIcon(publicKey);
|
||||||
this.findPreference(PREFERENCE_CATEGORY_QR_CODE).setIcon(qrCode);
|
this.findPreference(PREFERENCE_CATEGORY_QR_CODE).setIcon(qrCode);
|
||||||
this.findPreference(PREFERENCE_CATEGORY_LINK_DEVICE).setIcon(linkDevice);
|
this.findPreference(PREFERENCE_CATEGORY_LINKED_DEVICES).setIcon(linkDevice);
|
||||||
this.findPreference(PREFERENCE_CATEGORY_SEED).setIcon(seed);
|
this.findPreference(PREFERENCE_CATEGORY_SEED).setIcon(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CategoryClickListener implements Preference.OnPreferenceClickListener, DeviceLinkingDialogDelegate {
|
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
|
||||||
private String category;
|
private String category;
|
||||||
private Context context;
|
|
||||||
|
|
||||||
CategoryClickListener(Context context,String category) {
|
CategoryClickListener(String category) {
|
||||||
this.context = context;
|
|
||||||
this.category = category;
|
this.category = category;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,8 +340,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
case PREFERENCE_CATEGORY_QR_CODE:
|
case PREFERENCE_CATEGORY_QR_CODE:
|
||||||
QRCodeDialog.INSTANCE.show(getContext());
|
QRCodeDialog.INSTANCE.show(getContext());
|
||||||
break;
|
break;
|
||||||
case PREFERENCE_CATEGORY_LINK_DEVICE:
|
case PREFERENCE_CATEGORY_LINKED_DEVICES:
|
||||||
DeviceLinkingDialog.Companion.show(getContext(), DeviceLinkingView.Mode.Master, this);
|
Intent intent = new Intent(getActivity(), LinkedDevicesActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
break;
|
break;
|
||||||
case PREFERENCE_CATEGORY_SEED:
|
case PREFERENCE_CATEGORY_SEED:
|
||||||
Analytics.Companion.getShared().track("Seed Modal Shown");
|
Analytics.Companion.getShared().track("Seed Modal Shown");
|
||||||
@ -404,12 +385,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void sendPairingAuthorizedMessage(@NotNull PairingAuthorisation pairingAuthorisation) {
|
|
||||||
AsyncTask.execute(() -> MultiDeviceUtilities.signAndSendPairingAuthorisationMessage(context, pairingAuthorisation));
|
|
||||||
}
|
|
||||||
@Override public void handleDeviceLinkAuthorized(@NotNull PairingAuthorisation pairingAuthorisation) {}
|
|
||||||
@Override public void handleDeviceLinkingDialogDismissed() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
||||||
|
@ -22,6 +22,7 @@ import android.content.ActivityNotFoundException;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -38,8 +39,14 @@ import android.view.ViewTreeObserver;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.Toast;
|
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.RatingManager;
|
||||||
import org.thoughtcrime.securesms.components.SearchToolbar;
|
import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
@ -48,6 +55,8 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
|||||||
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
|
||||||
import org.thoughtcrime.securesms.loki.AddPublicChatActivity;
|
import org.thoughtcrime.securesms.loki.AddPublicChatActivity;
|
||||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
||||||
|
import org.thoughtcrime.securesms.loki.RecipientAvatarModifiedEvent;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
@ -129,6 +138,18 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
MenuInflater inflater = this.getMenuInflater();
|
MenuInflater inflater = this.getMenuInflater();
|
||||||
@ -197,45 +218,23 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
profilePictureImageView.setClipToOutline(true);
|
||||||
|
|
||||||
// Display the correct identicon if we're a secondary device
|
// Display the correct identicon if we're a secondary device
|
||||||
String currentUser = TextSecurePreferences.getLocalNumber(this);
|
|
||||||
String recipientAddress = recipient.getAddress().serialize();
|
|
||||||
String primaryAddress = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
|
String primaryAddress = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
|
||||||
String profileAddress = (recipientAddress.equalsIgnoreCase(currentUser) && primaryAddress != null) ? primaryAddress : recipientAddress;
|
String profileAddress = (recipient.isLocalNumber() && primaryAddress != null) ? primaryAddress : recipient.getAddress().serialize();
|
||||||
|
Recipient primaryRecipient = Recipient.from(this, Address.fromSerialized(profileAddress), false);
|
||||||
|
|
||||||
profilePictureImageView.setClipToOutline(true);
|
Drawable fallback = primaryRecipient.getFallbackContactPhotoDrawable(this, false);
|
||||||
profilePictureImageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreDraw() {
|
|
||||||
int width = profilePictureImageView.getWidth();
|
|
||||||
int height = profilePictureImageView.getHeight();
|
|
||||||
if (width == 0 || height == 0) return true;
|
|
||||||
profilePictureImageView.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
||||||
JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, profileAddress.toLowerCase());
|
|
||||||
profilePictureImageView.setImageDrawable(identicon);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
String name = Optional.fromNullable(recipient.getName()).or(Optional.fromNullable(TextSecurePreferences.getProfileName(this))).or("");
|
|
||||||
MaterialColor fallbackColor = recipient.getColor();
|
|
||||||
|
|
||||||
if (fallbackColor == ContactColors.UNKNOWN_COLOR && !TextUtils.isEmpty(name)) {
|
|
||||||
fallbackColor = ContactColors.generateFor(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawable fallback = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(this, fallbackColor.toAvatarColor(this));
|
|
||||||
|
|
||||||
GlideApp.with(this)
|
GlideApp.with(this)
|
||||||
.load(new ProfileContactPhoto(recipient.getAddress(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this))))
|
.load(primaryRecipient.getContactPhoto())
|
||||||
|
.fallback(fallback)
|
||||||
.error(fallback)
|
.error(fallback)
|
||||||
.circleCrop()
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
.into(icon);
|
.circleCrop()
|
||||||
*/
|
.into(profilePictureImageView);
|
||||||
|
|
||||||
|
|
||||||
profilePictureImageView.setOnClickListener(v -> handleDisplaySettings());
|
profilePictureImageView.setOnClickListener(v -> handleDisplaySettings());
|
||||||
}
|
}
|
||||||
@ -336,4 +335,12 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
private void addNewPublicChat() {
|
private void addNewPublicChat() {
|
||||||
startActivity(new Intent(this, AddPublicChatActivity.class));
|
startActivity(new Intent(this, AddPublicChatActivity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
public void onAvatarModified(RecipientAvatarModifiedEvent event) {
|
||||||
|
Recipient recipient = event.getRecipient();
|
||||||
|
if (recipient.isLocalNumber() || recipient.isOurMasterDevice()) {
|
||||||
|
initializeProfileIcon(recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
|||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
@ -58,13 +57,16 @@ import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
|||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
||||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiDotNetAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@ -96,6 +98,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
private View reveal;
|
private View reveal;
|
||||||
|
|
||||||
private Intent nextIntent;
|
private Intent nextIntent;
|
||||||
|
private byte[] originalAvatarBytes;
|
||||||
private byte[] avatarBytes;
|
private byte[] avatarBytes;
|
||||||
private File captureFile;
|
private File captureFile;
|
||||||
|
|
||||||
@ -301,6 +304,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(byte[] result) {
|
protected void onPostExecute(byte[] result) {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
originalAvatarBytes = result;
|
||||||
avatarBytes = result;
|
avatarBytes = result;
|
||||||
GlideApp.with(CreateProfileActivity.this)
|
GlideApp.with(CreateProfileActivity.this)
|
||||||
.load(result)
|
.load(result)
|
||||||
@ -314,6 +318,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(byte[] result) {
|
public void onSuccess(byte[] result) {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
originalAvatarBytes = result;
|
||||||
avatarBytes = result;
|
avatarBytes = result;
|
||||||
GlideApp.with(CreateProfileActivity.this)
|
GlideApp.with(CreateProfileActivity.this)
|
||||||
.load(result)
|
.load(result)
|
||||||
@ -380,7 +385,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
Context context = CreateProfileActivity.this;
|
Context context = CreateProfileActivity.this;
|
||||||
byte[] profileKey = ProfileKeyUtil.getProfileKey(CreateProfileActivity.this);
|
|
||||||
|
|
||||||
Analytics.Companion.getShared().track("Display Name Updated");
|
Analytics.Companion.getShared().track("Display Name Updated");
|
||||||
|
|
||||||
@ -393,31 +397,44 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Original code
|
// Loki - Only update avatar if there was a change
|
||||||
// ========
|
if (!Arrays.equals(originalAvatarBytes, avatarBytes)) {
|
||||||
// try {
|
try {
|
||||||
// accountManager.setProfileName(profileKey, name);
|
// Loki - Original profile photo code
|
||||||
// TextSecurePreferences.setProfileName(context, name);
|
// ========
|
||||||
// } catch (IOException e) {
|
// accountManager.setProfileAvatar(profileKey, avatar);
|
||||||
// Log.w(TAG, e);
|
// ========
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// ========
|
|
||||||
|
|
||||||
try {
|
// Try upload photo with a new profile key
|
||||||
// Loki - Original code
|
String newProfileKey = ProfileKeyUtil.generateEncodedProfileKey(context);
|
||||||
// ========
|
byte[] profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(newProfileKey);
|
||||||
// accountManager.setProfileAvatar(profileKey, avatar);
|
|
||||||
// ========
|
//Loki - Upload the profile photo here
|
||||||
AvatarHelper.setAvatar(CreateProfileActivity.this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), avatarBytes);
|
if (avatar != null) {
|
||||||
TextSecurePreferences.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt());
|
Log.d("Loki", "Start uploading profile photo");
|
||||||
} catch (IOException e) {
|
LokiStorageAPI storageAPI = LokiStorageAPI.shared;
|
||||||
Log.w(TAG, e);
|
LokiDotNetAPI.UploadResult result = storageAPI.uploadProfilePicture(storageAPI.getServer(), profileKey, avatar);
|
||||||
return false;
|
Log.d("Loki", "Profile photo uploaded, the url is " + result.getUrl());
|
||||||
|
TextSecurePreferences.setProfileAvatarUrl(context, result.getUrl());
|
||||||
|
} else {
|
||||||
|
TextSecurePreferences.setProfileAvatarUrl(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarHelper.setAvatar(context, Address.fromSerialized(TextSecurePreferences.getLocalNumber(context)), avatarBytes);
|
||||||
|
TextSecurePreferences.setProfileAvatarId(context, new SecureRandom().nextInt());
|
||||||
|
|
||||||
|
// Upload was successful with this new profile key, we should set it so the other users know to re-fetch profiles
|
||||||
|
ProfileKeyUtil.setEncodedProfileKey(context, newProfileKey);
|
||||||
|
|
||||||
|
// Update profile key on the public chat server
|
||||||
|
ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d("Loki", "Failed to upload profile photo: " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceProfileKeyUpdateJob());
|
// ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceProfileKeyUpdateJob());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.ListFragment;
|
import android.support.v4.app.ListFragment;
|
||||||
@ -16,29 +15,32 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.melnykov.fab.FloatingActionButton;
|
import com.melnykov.fab.FloatingActionButton;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.devicelist.Device;
|
import org.thoughtcrime.securesms.devicelist.Device;
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
|
import org.thoughtcrime.securesms.loki.DeviceListBottomSheetFragment;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.loki.MnemonicUtilities;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.whispersystems.libsignal.util.guava.Function;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import kotlin.Pair;
|
||||||
|
import kotlin.Unit;
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
|
import static org.thoughtcrime.securesms.loki.GeneralUtilitiesKt.toPx;
|
||||||
|
|
||||||
public class DeviceListFragment extends ListFragment
|
public class DeviceListFragment extends ListFragment
|
||||||
implements LoaderManager.LoaderCallbacks<List<Device>>,
|
implements LoaderManager.LoaderCallbacks<List<Device>>,
|
||||||
ListView.OnItemClickListener, InjectableType, Button.OnClickListener
|
ListView.OnItemClickListener, InjectableType, Button.OnClickListener
|
||||||
@ -46,14 +48,14 @@ public class DeviceListFragment extends ListFragment
|
|||||||
|
|
||||||
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
private static final String TAG = DeviceListFragment.class.getSimpleName();
|
||||||
|
|
||||||
@Inject
|
private File languageFileDirectory;
|
||||||
SignalServiceAccountManager accountManager;
|
|
||||||
|
|
||||||
private Locale locale;
|
private Locale locale;
|
||||||
private View empty;
|
private View empty;
|
||||||
private View progressContainer;
|
private View progressContainer;
|
||||||
private FloatingActionButton addDeviceButton;
|
private FloatingActionButton addDeviceButton;
|
||||||
private Button.OnClickListener addDeviceButtonListener;
|
private Button.OnClickListener addDeviceButtonListener;
|
||||||
|
private Function<String, Void> handleDisconnectDevice;
|
||||||
|
private Function<Pair<String, String>, Void> handleDeviceNameChange;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@ -71,10 +73,11 @@ public class DeviceListFragment extends ListFragment
|
|||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||||
View view = inflater.inflate(R.layout.device_list_fragment, container, false);
|
View view = inflater.inflate(R.layout.device_list_fragment, container, false);
|
||||||
|
|
||||||
this.empty = view.findViewById(R.id.empty);
|
this.empty = view.findViewById(R.id.emptyStateTextView);
|
||||||
this.progressContainer = view.findViewById(R.id.progress_container);
|
this.progressContainer = view.findViewById(R.id.activityIndicator);
|
||||||
this.addDeviceButton = ViewUtil.findById(view, R.id.add_device);
|
this.addDeviceButton = ViewUtil.findById(view, R.id.addDeviceButton);
|
||||||
this.addDeviceButton.setOnClickListener(this);
|
this.addDeviceButton.setOnClickListener(this);
|
||||||
|
updateAddDeviceButtonVisibility();
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@ -82,6 +85,7 @@ public class DeviceListFragment extends ListFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle bundle) {
|
public void onActivityCreated(Bundle bundle) {
|
||||||
super.onActivityCreated(bundle);
|
super.onActivityCreated(bundle);
|
||||||
|
this.languageFileDirectory = MnemonicUtilities.getLanguageFileDirectory(getContext());
|
||||||
getLoaderManager().initLoader(0, null, this);
|
getLoaderManager().initLoader(0, null, this);
|
||||||
getListView().setOnItemClickListener(this);
|
getListView().setOnItemClickListener(this);
|
||||||
}
|
}
|
||||||
@ -90,12 +94,20 @@ public class DeviceListFragment extends ListFragment
|
|||||||
this.addDeviceButtonListener = listener;
|
this.addDeviceButtonListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setHandleDisconnectDevice(Function<String, Void> handler) {
|
||||||
|
this.handleDisconnectDevice = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHandleDeviceNameChange(Function<Pair<String, String>, Void> handler) {
|
||||||
|
this.handleDeviceNameChange = handler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Loader<List<Device>> onCreateLoader(int id, Bundle args) {
|
public @NonNull Loader<List<Device>> onCreateLoader(int id, Bundle args) {
|
||||||
empty.setVisibility(View.GONE);
|
empty.setVisibility(View.GONE);
|
||||||
progressContainer.setVisibility(View.VISIBLE);
|
progressContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
return new DeviceListLoader(getActivity(), accountManager);
|
return new DeviceListLoader(getActivity(), languageFileDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -124,20 +136,63 @@ public class DeviceListFragment extends ListFragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
final boolean hasDeviceName = ((DeviceListItem)view).hasDeviceName(); // Tells us whether the name is set to shortId or the device name
|
||||||
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
final String deviceName = ((DeviceListItem)view).getDeviceName();
|
||||||
final long deviceId = ((DeviceListItem)view).getDeviceId();
|
final String deviceId = ((DeviceListItem)view).getDeviceId();
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
DeviceListBottomSheetFragment bottomSheet = new DeviceListBottomSheetFragment();
|
||||||
builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
bottomSheet.setOnEditTapped(() -> {
|
||||||
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
bottomSheet.dismiss();
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
EditText deviceNameEditText = new EditText(getContext());
|
||||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
LinearLayout deviceNameEditTextContainer = new LinearLayout(getContext());
|
||||||
@Override
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
layoutParams.setMarginStart(toPx(18, getResources()));
|
||||||
handleDisconnectDevice(deviceId);
|
layoutParams.setMarginEnd(toPx(18, getResources()));
|
||||||
}
|
deviceNameEditText.setLayoutParams(layoutParams);
|
||||||
|
deviceNameEditTextContainer.addView(deviceNameEditText);
|
||||||
|
deviceNameEditText.setText(hasDeviceName ? deviceName : "");
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setTitle(R.string.DeviceListActivity_edit_device_name);
|
||||||
|
builder.setView(deviceNameEditTextContainer);
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (handleDeviceNameChange != null) { handleDeviceNameChange.apply(new Pair<>(deviceId, deviceNameEditText.getText().toString().trim())); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
builder.show();
|
bottomSheet.setOnUnlinkTapped(() -> {
|
||||||
|
bottomSheet.dismiss();
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
|
||||||
|
builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (handleDisconnectDevice != null) { handleDisconnectDevice.apply(deviceId); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
bottomSheet.show(getFragmentManager(), bottomSheet.getTag());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
updateAddDeviceButtonVisibility();
|
||||||
|
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAddDeviceButtonVisibility() {
|
||||||
|
if (addDeviceButton != null) {
|
||||||
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||||
|
boolean isDeviceLinkingEnabled = DatabaseFactory.getLokiAPIDatabase(getContext()).getPairingAuthorisations(userHexEncodedPublicKey).isEmpty();
|
||||||
|
addDeviceButton.setVisibility(isDeviceLinkingEnabled ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLoaderFailed() {
|
private void handleLoaderFailed() {
|
||||||
@ -167,34 +222,6 @@ public class DeviceListFragment extends ListFragment
|
|||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDisconnectDevice(final long deviceId) {
|
|
||||||
new ProgressDialogAsyncTask<Void, Void, Void>(getActivity(),
|
|
||||||
R.string.DeviceListActivity_unlinking_device_no_ellipsis,
|
|
||||||
R.string.DeviceListActivity_unlinking_device)
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
try {
|
|
||||||
accountManager.removeDevice(deviceId);
|
|
||||||
|
|
||||||
ApplicationContext.getInstance(getContext())
|
|
||||||
.getJobManager()
|
|
||||||
.add(new RefreshUnidentifiedDeliveryAbilityJob());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void result) {
|
|
||||||
super.onPostExecute(result);
|
|
||||||
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
|
||||||
}
|
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (addDeviceButtonListener != null) addDeviceButtonListener.onClick(v);
|
if (addDeviceButtonListener != null) addDeviceButtonListener.onClick(v);
|
||||||
|
@ -15,10 +15,9 @@ import network.loki.messenger.R;
|
|||||||
|
|
||||||
public class DeviceListItem extends LinearLayout {
|
public class DeviceListItem extends LinearLayout {
|
||||||
|
|
||||||
private long deviceId;
|
private String deviceId;
|
||||||
private TextView name;
|
private TextView name;
|
||||||
private TextView created;
|
private TextView shortId;
|
||||||
private TextView lastActive;
|
|
||||||
|
|
||||||
public DeviceListItem(Context context) {
|
public DeviceListItem(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -31,29 +30,19 @@ public class DeviceListItem extends LinearLayout {
|
|||||||
@Override
|
@Override
|
||||||
public void onFinishInflate() {
|
public void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
this.name = (TextView) findViewById(R.id.name);
|
this.name = (TextView) findViewById(R.id.name);
|
||||||
this.created = (TextView) findViewById(R.id.created);
|
this.shortId = (TextView) findViewById(R.id.shortId);
|
||||||
this.lastActive = (TextView) findViewById(R.id.active);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(Device deviceInfo, Locale locale) {
|
public void set(Device deviceInfo, Locale locale) {
|
||||||
if (TextUtils.isEmpty(deviceInfo.getName())) this.name.setText(R.string.DeviceListItem_unnamed_device);
|
|
||||||
else this.name.setText(deviceInfo.getName());
|
|
||||||
|
|
||||||
this.created.setText(getContext().getString(R.string.DeviceListItem_linked_s,
|
|
||||||
DateUtils.getDayPrecisionTimeSpanString(getContext(),
|
|
||||||
locale,
|
|
||||||
deviceInfo.getCreated())));
|
|
||||||
|
|
||||||
this.lastActive.setText(getContext().getString(R.string.DeviceListItem_last_active_s,
|
|
||||||
DateUtils.getDayPrecisionTimeSpanString(getContext(),
|
|
||||||
locale,
|
|
||||||
deviceInfo.getLastSeen())));
|
|
||||||
|
|
||||||
this.deviceId = deviceInfo.getId();
|
this.deviceId = deviceInfo.getId();
|
||||||
|
boolean hasName = !TextUtils.isEmpty(deviceInfo.getName());
|
||||||
|
this.name.setText(hasName ? deviceInfo.getName() : deviceInfo.getShortId());
|
||||||
|
this.shortId.setText(deviceInfo.getShortId());
|
||||||
|
this.shortId.setVisibility(hasName ? VISIBLE : GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDeviceId() {
|
public String getDeviceId() {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,4 +50,8 @@ public class DeviceListItem extends LinearLayout {
|
|||||||
return name.getText().toString();
|
return name.getText().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasDeviceName() {
|
||||||
|
return shortId.getVisibility() == VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.IdRes;
|
import android.support.annotation.IdRes;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
@ -5,6 +5,7 @@ import android.app.Activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
@ -43,7 +44,7 @@ public final class AvatarSelection {
|
|||||||
CropImage.activity(inputFile)
|
CropImage.activity(inputFile)
|
||||||
.setGuidelines(CropImageView.Guidelines.ON)
|
.setGuidelines(CropImageView.Guidelines.ON)
|
||||||
.setAspectRatio(1, 1)
|
.setAspectRatio(1, 1)
|
||||||
.setCropShape(CropImageView.CropShape.OVAL)
|
.setCropShape(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? CropImageView.CropShape.RECTANGLE : CropImageView.CropShape.OVAL)
|
||||||
.setOutputUri(outputFile)
|
.setOutputUri(outputFile)
|
||||||
.setAllowRotation(true)
|
.setAllowRotation(true)
|
||||||
.setAllowFlipping(true)
|
.setAllowFlipping(true)
|
||||||
|
@ -11,22 +11,23 @@ import android.provider.ContactsContract;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.AppCompatImageView;
|
import android.support.v7.widget.AppCompatImageView;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -52,7 +53,9 @@ public class AvatarImageView extends AppCompatImageView {
|
|||||||
private boolean inverted;
|
private boolean inverted;
|
||||||
private Paint outlinePaint;
|
private Paint outlinePaint;
|
||||||
private OnClickListener listener;
|
private OnClickListener listener;
|
||||||
private Recipient recipient;
|
|
||||||
|
private @Nullable RecipientContactPhoto recipientContactPhoto;
|
||||||
|
private @NonNull Drawable unknownRecipientDrawable;
|
||||||
|
|
||||||
public AvatarImageView(Context context) {
|
public AvatarImageView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -75,23 +78,27 @@ public class AvatarImageView extends AppCompatImageView {
|
|||||||
|
|
||||||
outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
|
outlinePaint = ThemeUtil.isDarkTheme(getContext()) ? DARK_THEME_OUTLINE_PAINT : LIGHT_THEME_OUTLINE_PAINT;
|
||||||
setOutlineProvider(new ViewOutlineProvider() {
|
setOutlineProvider(new ViewOutlineProvider() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getOutline(View view, Outline outline) {
|
public void getOutline(View view, Outline outline) {
|
||||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setClipToOutline(true);
|
setClipToOutline(true);
|
||||||
|
|
||||||
|
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_default).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
super.dispatchDraw(canvas);
|
super.onDraw(canvas);
|
||||||
|
|
||||||
float cx = canvas.getWidth() / 2;
|
float width = getWidth() - getPaddingRight() - getPaddingLeft();
|
||||||
float cy = canvas.getHeight() / 2;
|
float height = getHeight() - getPaddingBottom() - getPaddingTop();
|
||||||
float radius = (canvas.getWidth() / 2) - (outlinePaint.getStrokeWidth() / 2);
|
float cx = width / 2f;
|
||||||
|
float cy = height / 2f;
|
||||||
|
float radius = Math.min(cx, cy) - (outlinePaint.getStrokeWidth() / 2f);
|
||||||
|
|
||||||
|
canvas.translate(getPaddingLeft(), getPaddingTop());
|
||||||
canvas.drawCircle(cx, cy, radius, outlinePaint);
|
canvas.drawCircle(cx, cy, radius, outlinePaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,39 +108,46 @@ public class AvatarImageView extends AppCompatImageView {
|
|||||||
super.setOnClickListener(listener);
|
super.setOnClickListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
||||||
super.onSizeChanged(w, h, oldw, oldh);
|
|
||||||
updateImage(w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(String hexEncodedPublicKey) {
|
public void update(String hexEncodedPublicKey) {
|
||||||
Address address = Address.fromSerialized(hexEncodedPublicKey);
|
Address address = Address.fromSerialized(hexEncodedPublicKey);
|
||||||
if (recipient == null || !address.equals(recipient.getAddress())) {
|
Recipient recipient = Recipient.from(getContext(), address, false);
|
||||||
this.recipient = Recipient.from(getContext(), address, false);
|
updateAvatar(recipient);
|
||||||
updateImage();
|
}
|
||||||
}
|
|
||||||
|
private void updateAvatar(Recipient recipient) {
|
||||||
|
setAvatar(GlideApp.with(getContext()), recipient, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
||||||
if (this.recipient == null || !this.recipient.equals(recipient)) {
|
|
||||||
this.recipient = recipient;
|
|
||||||
updateImage();
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
requestManager.load(recipient.getContactPhoto())
|
if (recipient.isLocalNumber()) {
|
||||||
.fallback(recipient.getFallbackContactPhotoDrawable(getContext(), inverted))
|
setImageDrawable(new ResourceContactPhoto(R.drawable.ic_note_to_self).asDrawable(getContext(), recipient.getColor().toAvatarColor(getContext()), inverted));
|
||||||
.error(recipient.getFallbackContactPhotoDrawable(getContext(), inverted))
|
} else {
|
||||||
|
RecipientContactPhoto photo = new RecipientContactPhoto(recipient);
|
||||||
|
if (!photo.equals(recipientContactPhoto)) {
|
||||||
|
requestManager.clear(this);
|
||||||
|
recipientContactPhoto = photo;
|
||||||
|
|
||||||
|
Drawable fallbackContactPhotoDrawable = photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted);
|
||||||
|
|
||||||
|
if (photo.contactPhoto != null) {
|
||||||
|
requestManager.load(photo.contactPhoto)
|
||||||
|
.fallback(fallbackContactPhotoDrawable)
|
||||||
|
.error(fallbackContactPhotoDrawable)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
.circleCrop()
|
.circleCrop()
|
||||||
.into(this);
|
.into(this);
|
||||||
setAvatarClickHandler(recipient, quickContactEnabled);
|
} else {
|
||||||
|
setImageDrawable(fallbackContactPhotoDrawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setImageDrawable(new ResourceContactPhoto(R.drawable.ic_profile_default).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted));
|
recipientContactPhoto = null;
|
||||||
|
requestManager.clear(this);
|
||||||
|
setImageDrawable(unknownRecipientDrawable);
|
||||||
super.setOnClickListener(listener);
|
super.setOnClickListener(listener);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear(@NonNull GlideRequests glideRequests) {
|
public void clear(@NonNull GlideRequests glideRequests) {
|
||||||
@ -154,32 +168,25 @@ public class AvatarImageView extends AppCompatImageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateImage() { updateImage(getWidth(), getHeight()); }
|
private static class RecipientContactPhoto {
|
||||||
|
|
||||||
private void updateImage(int w, int h) {
|
private final @NonNull Recipient recipient;
|
||||||
if (w == 0 || h == 0 || recipient == null) { return; }
|
private final @Nullable ContactPhoto contactPhoto;
|
||||||
|
private final boolean ready;
|
||||||
|
|
||||||
Drawable image;
|
RecipientContactPhoto(@NonNull Recipient recipient) {
|
||||||
Context context = this.getContext();
|
this.recipient = recipient;
|
||||||
|
this.ready = !recipient.isResolving();
|
||||||
if (recipient.isGroupRecipient()) {
|
this.contactPhoto = recipient.getContactPhoto();
|
||||||
String name = Optional.fromNullable(recipient.getName()).or(Optional.fromNullable(TextSecurePreferences.getProfileName(context))).or("");
|
|
||||||
MaterialColor fallbackColor = recipient.getColor();
|
|
||||||
|
|
||||||
if (fallbackColor == ContactColors.UNKNOWN_COLOR && !TextUtils.isEmpty(name)) {
|
|
||||||
fallbackColor = ContactColors.generateFor(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
image = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(context, fallbackColor.toAvatarColor(context));
|
|
||||||
} else {
|
|
||||||
// Default to primary device image
|
|
||||||
String ourPublicKey = TextSecurePreferences.getLocalNumber(context);
|
|
||||||
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
|
||||||
String recipientAddress = recipient.getAddress().serialize();
|
|
||||||
String profileAddress = (ourPrimaryDevice != null && ourPublicKey.equals(recipientAddress)) ? ourPrimaryDevice : recipientAddress;
|
|
||||||
image = new JazzIdenticonDrawable(w, h, profileAddress.toLowerCase());
|
|
||||||
}
|
}
|
||||||
setImageDrawable(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public boolean equals(@Nullable RecipientContactPhoto other) {
|
||||||
|
if (other == null) return false;
|
||||||
|
|
||||||
|
return other.recipient.equals(recipient) &&
|
||||||
|
other.recipient.getColor().equals(recipient.getColor()) &&
|
||||||
|
other.ready == ready &&
|
||||||
|
Objects.equals(other.contactPhoto, contactPhoto);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3052,12 +3052,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
|
long originalThreadID = lokiMessageDatabase.getOriginalThreadID(friendRequest.id);
|
||||||
long threadId = originalThreadID < 0 ? this.threadId : originalThreadID;
|
long threadId = originalThreadID < 0 ? this.threadId : originalThreadID;
|
||||||
|
|
||||||
Address contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId).getAddress();
|
Recipient contact = DatabaseFactory.getThreadDatabase(this).getRecipientForThreadId(threadId);
|
||||||
String contactPubKey = contact.toString();
|
Address address = contact.getAddress();
|
||||||
|
String contactPubKey = address.serialize();
|
||||||
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS);
|
DatabaseFactory.getLokiThreadDatabase(this).setFriendRequestStatus(threadId, LokiThreadFriendRequestStatus.FRIENDS);
|
||||||
lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
|
lokiMessageDatabase.setFriendRequestStatus(friendRequest.id, LokiMessageFriendRequestStatus.REQUEST_ACCEPTED);
|
||||||
|
DatabaseFactory.getRecipientDatabase(this).setProfileSharing(contact, true);
|
||||||
MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey);
|
MessageSender.sendBackgroundMessageToAllDevices(this, contactPubKey);
|
||||||
MessageSender.syncContact(this, contact);
|
MessageSender.syncContact(this, address);
|
||||||
updateInputPanel();
|
updateInputPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +198,10 @@ public class MasterSecretUtil {
|
|||||||
return preferences.getBoolean("passphrase_initialized", false);
|
return preferences.getBoolean("passphrase_initialized", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void clear(Context context) {
|
||||||
|
context.getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
|
||||||
|
}
|
||||||
|
|
||||||
private static void save(Context context, String key, int value) {
|
private static void save(Context context, String key, int value) {
|
||||||
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
||||||
.edit()
|
.edit()
|
||||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.crypto;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@ -31,8 +32,24 @@ public class ProfileKeyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized @NonNull byte[] getProfileKeyFromEncodedString(String encodedProfileKey) {
|
||||||
|
try {
|
||||||
|
return Base64.decode(encodedProfileKey);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static synchronized @NonNull byte[] rotateProfileKey(@NonNull Context context) {
|
public static synchronized @NonNull byte[] rotateProfileKey(@NonNull Context context) {
|
||||||
TextSecurePreferences.setProfileKey(context, null);
|
TextSecurePreferences.setProfileKey(context, null);
|
||||||
return getProfileKey(context);
|
return getProfileKey(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized @NonNull String generateEncodedProfileKey(@NonNull Context context) {
|
||||||
|
return Util.getSecret(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void setEncodedProfileKey(@NonNull Context context, @Nullable String key) {
|
||||||
|
TextSecurePreferences.setProfileKey(context, key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
private static final int lokiV3 = 24;
|
private static final int lokiV3 = 24;
|
||||||
private static final int lokiV4 = 25;
|
private static final int lokiV4 = 25;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = lokiV3; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
private static final int DATABASE_VERSION = lokiV4; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
@ -7,10 +7,15 @@ import android.text.TextUtils;
|
|||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
|
import org.thoughtcrime.securesms.database.Database;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.devicelist.Device;
|
import org.thoughtcrime.securesms.devicelist.Device;
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.loki.MnemonicUtilities;
|
||||||
import org.thoughtcrime.securesms.util.AsyncLoader;
|
import org.thoughtcrime.securesms.util.AsyncLoader;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libsignal.InvalidKeyException;
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
import org.whispersystems.libsignal.ecc.Curve;
|
import org.whispersystems.libsignal.ecc.Curve;
|
||||||
import org.whispersystems.libsignal.ecc.ECPrivateKey;
|
import org.whispersystems.libsignal.ecc.ECPrivateKey;
|
||||||
@ -19,7 +24,10 @@ import org.whispersystems.libsignal.util.ByteUtil;
|
|||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
|
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||||
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
@ -33,93 +41,43 @@ import javax.crypto.spec.IvParameterSpec;
|
|||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.*;
|
import static org.thoughtcrime.securesms.devicelist.DeviceNameProtos.*;
|
||||||
|
import static org.whispersystems.signalservice.loki.utilities.TrimmingKt.removing05PrefixIfNeeded;
|
||||||
|
|
||||||
public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
public class DeviceListLoader extends AsyncLoader<List<Device>> {
|
||||||
|
|
||||||
private static final String TAG = DeviceListLoader.class.getSimpleName();
|
private static final String TAG = DeviceListLoader.class.getSimpleName();
|
||||||
|
private MnemonicCodec mnemonicCodec;
|
||||||
|
|
||||||
private final SignalServiceAccountManager accountManager;
|
public DeviceListLoader(Context context, File languageFileDirectory) {
|
||||||
|
|
||||||
public DeviceListLoader(Context context, SignalServiceAccountManager accountManager) {
|
|
||||||
super(context);
|
super(context);
|
||||||
this.accountManager = accountManager;
|
this.mnemonicCodec = new MnemonicCodec(languageFileDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Device> loadInBackground() {
|
public List<Device> loadInBackground() {
|
||||||
try {
|
try {
|
||||||
List<Device> devices = Stream.of(accountManager.getDevices())
|
String ourPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||||
.filter(d -> d.getId() != SignalServiceAddress.DEFAULT_DEVICE_ID)
|
List<String> secondaryDevicePublicKeys = LokiStorageAPI.shared.getSecondaryDevicePublicKeys(ourPublicKey).get();
|
||||||
.map(this::mapToDevice)
|
List<Device> devices = Stream.of(secondaryDevicePublicKeys).map(this::mapToDevice).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
Collections.sort(devices, new DeviceComparator());
|
Collections.sort(devices, new DeviceComparator());
|
||||||
|
|
||||||
return devices;
|
return devices;
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Device mapToDevice(@NonNull DeviceInfo deviceInfo) {
|
private Device mapToDevice(@NonNull String hexEncodedPublicKey) {
|
||||||
try {
|
String shortId = MnemonicUtilities.getFirst3Words(mnemonicCodec, hexEncodedPublicKey);
|
||||||
if (TextUtils.isEmpty(deviceInfo.getName()) || deviceInfo.getName().length() < 4) {
|
String name = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(hexEncodedPublicKey);
|
||||||
throw new IOException("Invalid DeviceInfo name.");
|
return new Device(hexEncodedPublicKey, shortId, name);
|
||||||
}
|
|
||||||
|
|
||||||
DeviceName deviceName = DeviceName.parseFrom(Base64.decode(deviceInfo.getName()));
|
|
||||||
|
|
||||||
if (!deviceName.hasCiphertext() || !deviceName.hasEphemeralPublic() || !deviceName.hasSyntheticIv()) {
|
|
||||||
throw new IOException("Got a DeviceName that wasn't properly populated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] syntheticIv = deviceName.getSyntheticIv().toByteArray();
|
|
||||||
byte[] cipherText = deviceName.getCiphertext().toByteArray();
|
|
||||||
ECPrivateKey identityKey = IdentityKeyUtil.getIdentityKeyPair(getContext()).getPrivateKey();
|
|
||||||
ECPublicKey ephemeralPublic = Curve.decodePoint(deviceName.getEphemeralPublic().toByteArray(), 0);
|
|
||||||
byte[] masterSecret = Curve.calculateAgreement(ephemeralPublic, identityKey);
|
|
||||||
|
|
||||||
Mac mac = Mac.getInstance("HmacSHA256");
|
|
||||||
mac.init(new SecretKeySpec(masterSecret, "HmacSHA256"));
|
|
||||||
byte[] cipherKeyPart1 = mac.doFinal("cipher".getBytes());
|
|
||||||
|
|
||||||
mac.init(new SecretKeySpec(cipherKeyPart1, "HmacSHA256"));
|
|
||||||
byte[] cipherKey = mac.doFinal(syntheticIv);
|
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(new byte[16]));
|
|
||||||
final byte[] plaintext = cipher.doFinal(cipherText);
|
|
||||||
|
|
||||||
mac.init(new SecretKeySpec(masterSecret, "HmacSHA256"));
|
|
||||||
byte[] verificationPart1 = mac.doFinal("auth".getBytes());
|
|
||||||
|
|
||||||
mac.init(new SecretKeySpec(verificationPart1, "HmacSHA256"));
|
|
||||||
byte[] verificationPart2 = mac.doFinal(plaintext);
|
|
||||||
byte[] ourSyntheticIv = ByteUtil.trim(verificationPart2, 16);
|
|
||||||
|
|
||||||
if (!MessageDigest.isEqual(ourSyntheticIv, syntheticIv)) {
|
|
||||||
throw new GeneralSecurityException("The computed syntheticIv didn't match the actual syntheticIv.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Device(deviceInfo.getId(), new String(plaintext), deviceInfo.getCreated(), deviceInfo.getLastSeen());
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, "Failed while reading the protobuf.", e);
|
|
||||||
} catch (GeneralSecurityException | InvalidKeyException e) {
|
|
||||||
Log.w(TAG, "Failed during decryption.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Device(deviceInfo.getId(), deviceInfo.getName(), deviceInfo.getCreated(), deviceInfo.getLastSeen());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DeviceComparator implements Comparator<Device> {
|
private static class DeviceComparator implements Comparator<Device> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(Device lhs, Device rhs) {
|
public int compare(Device lhs, Device rhs) {
|
||||||
if (lhs.getCreated() < rhs.getCreated()) return -1;
|
return lhs.getName().compareTo(rhs.getName());
|
||||||
else if (lhs.getCreated() != rhs.getCreated()) return 1;
|
|
||||||
else return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,31 +2,19 @@ package org.thoughtcrime.securesms.devicelist;
|
|||||||
|
|
||||||
public class Device {
|
public class Device {
|
||||||
|
|
||||||
private final long id;
|
private final String id;
|
||||||
|
private final String shortId;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final long created;
|
|
||||||
private final long lastSeen;
|
|
||||||
|
|
||||||
public Device(long id, String name, long created, long lastSeen) {
|
public Device(String id, String shortId, String name) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.shortId = shortId;
|
||||||
this.created = created;
|
this.name = name;
|
||||||
this.lastSeen = lastSeen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
public String getShortId() { return shortId; }
|
||||||
public String getName() {
|
public String getName() { return name; }
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCreated() {
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLastSeen() {
|
|
||||||
return lastSeen;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@ -14,7 +15,6 @@ import android.util.Pair;
|
|||||||
|
|
||||||
import com.annimon.stream.Collectors;
|
import com.annimon.stream.Collectors;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
import com.google.android.gms.common.util.IOUtils;
|
|
||||||
|
|
||||||
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
|
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
|
||||||
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
|
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
|
||||||
@ -130,6 +130,7 @@ import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
|||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession;
|
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
||||||
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
|
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
|
||||||
@ -139,7 +140,6 @@ import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestSt
|
|||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus;
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadSessionResetStatus;
|
||||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@ -147,11 +147,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
import nl.komponents.kovenant.Promise;
|
||||||
|
|
||||||
public class PushDecryptJob extends BaseJob implements InjectableType {
|
public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||||
|
|
||||||
@ -288,21 +290,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
// Loki - Handle friend request acceptance if needed
|
// Loki - Handle friend request acceptance if needed
|
||||||
acceptFriendRequestIfNeeded(envelope, content);
|
acceptFriendRequestIfNeeded(envelope, content);
|
||||||
|
|
||||||
// Loki - Store pre key bundle if needed
|
// Loki - Store pre key bundle
|
||||||
|
// We shouldn't store it if it's a pairing message
|
||||||
|
if (!content.getPairingAuthorisation().isPresent()) {
|
||||||
|
storePreKeyBundleIfNeeded(envelope, content);
|
||||||
|
}
|
||||||
|
|
||||||
if (content.lokiServiceMessage.isPresent()) {
|
if (content.lokiServiceMessage.isPresent()) {
|
||||||
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||||
if (lokiMessage.getPreKeyBundleMessage() != null) {
|
|
||||||
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
|
|
||||||
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
|
|
||||||
|
|
||||||
// Only store the pre key bundle if we don't have one in our database
|
|
||||||
if (registrationID > 0 && !lokiPreKeyBundleDatabase.hasPreKeyBundle(envelope.getSource())) {
|
|
||||||
Log.d("Loki", "Received a pre key bundle from: " + envelope.getSource() + ".");
|
|
||||||
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
|
|
||||||
lokiPreKeyBundleDatabase.setPreKeyBundle(envelope.getSource(), preKeyBundle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lokiMessage.getAddressMessage() != null) {
|
if (lokiMessage.getAddressMessage() != null) {
|
||||||
// TODO: Loki - Handle address message
|
// TODO: Loki - Handle address message
|
||||||
}
|
}
|
||||||
@ -311,43 +306,58 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
// Loki - Store the sender display name if needed
|
// Loki - Store the sender display name if needed
|
||||||
Optional<String> rawSenderDisplayName = content.senderDisplayName;
|
Optional<String> rawSenderDisplayName = content.senderDisplayName;
|
||||||
if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) {
|
if (rawSenderDisplayName.isPresent() && rawSenderDisplayName.get().length() > 0) {
|
||||||
setDisplayName(envelope.getSource(), rawSenderDisplayName.get());
|
// If we got a name from our primary device then we set our profile name to match it
|
||||||
|
|
||||||
// If we got a name from our primary device then we also set that
|
|
||||||
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||||
if (ourPrimaryDevice != null && envelope.getSource().equals(ourPrimaryDevice)) {
|
if (ourPrimaryDevice != null && envelope.getSource().equals(ourPrimaryDevice)) {
|
||||||
TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get());
|
TextSecurePreferences.setProfileName(context, rawSenderDisplayName.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we receive a message from our device then don't set the display name in the database (as we probably have a alias set for them)
|
||||||
|
MultiDeviceUtilities.isOneOfOurDevices(context, Address.fromSerialized(content.getSender())).success(isOneOfOurDevice -> {
|
||||||
|
if (!isOneOfOurDevice) { setDisplayName(envelope.getSource(), rawSenderDisplayName.get()); }
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Deleting the display name
|
|
||||||
if (content.getPairingAuthorisation().isPresent()) {
|
if (content.getPairingAuthorisation().isPresent()) {
|
||||||
handlePairingMessage(content.getPairingAuthorisation().get(), envelope, content);
|
handlePairingMessage(content.getPairingAuthorisation().get(), envelope, content);
|
||||||
} else if (content.getDataMessage().isPresent()) {
|
} else if (content.getDataMessage().isPresent()) {
|
||||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||||
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
||||||
|
|
||||||
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
if (!envelope.isFriendRequest() && message.isUnpairingRequest()) {
|
||||||
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
// Make sure we got the request from our primary device
|
||||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId);
|
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||||
else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
if (ourPrimaryDevice != null && ourPrimaryDevice.equals(content.getSender())) {
|
||||||
else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId, Optional.absent());
|
TextSecurePreferences.setDatabaseResetFromUnpair(context, true);
|
||||||
|
MultiDeviceUtilities.checkForRevocation(context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
||||||
|
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
||||||
|
else if (message.isExpirationUpdate())
|
||||||
|
handleExpirationUpdate(content, message, smsMessageId);
|
||||||
|
else if (isMediaMessage)
|
||||||
|
handleMediaMessage(content, message, smsMessageId, Optional.absent());
|
||||||
|
else if (message.getBody().isPresent())
|
||||||
|
handleTextMessage(content, message, smsMessageId, Optional.absent());
|
||||||
|
|
||||||
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) {
|
if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) {
|
||||||
handleUnknownGroupMessage(content, message.getGroupInfo().get());
|
handleUnknownGroupMessage(content, message.getGroupInfo().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
|
||||||
|
handleProfileKey(content, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loki - This doesn't get invoked for group chats
|
||||||
|
if (content.isNeedsReceipt()) {
|
||||||
|
handleNeedsDeliveryReceipt(content, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loki - Handle friend request logic if needed
|
||||||
|
updateFriendRequestStatusIfNeeded(envelope, content, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
|
|
||||||
handleProfileKey(content, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loki - This doesn't get invoked for group chats
|
|
||||||
if (content.isNeedsReceipt()) {
|
|
||||||
handleNeedsDeliveryReceipt(content, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loki - Handle friend request logic if needed
|
|
||||||
updateFriendRequestStatusIfNeeded(envelope, content, message);
|
|
||||||
} else if (content.getSyncMessage().isPresent()) {
|
} else if (content.getSyncMessage().isPresent()) {
|
||||||
TextSecurePreferences.setMultiDevice(context, true);
|
TextSecurePreferences.setMultiDevice(context, true);
|
||||||
|
|
||||||
@ -716,6 +726,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
|
handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||||
|
boolean isSenderMasterDevice = ourMasterDevice != null && ourMasterDevice.equals(content.getSender());
|
||||||
if (message.getMessage().getProfileKey().isPresent()) {
|
if (message.getMessage().getProfileKey().isPresent()) {
|
||||||
Recipient recipient = null;
|
Recipient recipient = null;
|
||||||
|
|
||||||
@ -726,6 +738,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) {
|
if (recipient != null && !recipient.isSystemContact() && !recipient.isProfileSharing()) {
|
||||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loki - If we received a sync message from our master device then we need to extract the avatar url
|
||||||
|
if (isSenderMasterDevice) {
|
||||||
|
handleProfileKey(content, message.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loki - Update display name from master device
|
||||||
|
if (isSenderMasterDevice && content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
|
||||||
|
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadId != null) {
|
if (threadId != null) {
|
||||||
@ -859,18 +881,23 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
database.endTransaction();
|
database.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loki - Store message server ID
|
|
||||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
|
||||||
|
|
||||||
// Loki - Update mapping of message to original thread id
|
|
||||||
if (insertResult.isPresent()) {
|
if (insertResult.isPresent()) {
|
||||||
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
||||||
|
|
||||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
|
||||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
|
||||||
lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loki - Run db updates in the background, we should look into fixing this in the future
|
||||||
|
AsyncTask.execute(() -> {
|
||||||
|
// Loki - Store message server ID
|
||||||
|
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||||
|
|
||||||
|
// Loki - Update mapping of message to original thread id
|
||||||
|
if (insertResult.isPresent()) {
|
||||||
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
|
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||||
|
lokiMessageDatabase.setOriginalThreadID(insertResult.get().getMessageId(), originalThreadId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
|
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
|
||||||
@ -1014,35 +1041,37 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
// Insert the message into the database
|
// Insert the message into the database
|
||||||
Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
|
Optional<InsertResult> insertResult = database.insertMessageInbox(textMessage);
|
||||||
|
|
||||||
Long messageId = null;
|
|
||||||
if (insertResult.isPresent()) {
|
if (insertResult.isPresent()) {
|
||||||
threadId = insertResult.get().getThreadId();
|
threadId = insertResult.get().getThreadId();
|
||||||
messageId = insertResult.get().getMessageId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get());
|
if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get());
|
||||||
|
|
||||||
// Loki - Cache the user hex encoded public key (for mentions)
|
|
||||||
if (threadId != null) {
|
|
||||||
LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(threadId, context);
|
|
||||||
LokiAPI.Companion.cache(textMessage.getSender().serialize(), threadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loki - Store message server ID
|
|
||||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
|
||||||
|
|
||||||
// Loki - Update mapping of message to original thread id
|
|
||||||
if (messageId != null) {
|
|
||||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
|
||||||
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
|
||||||
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
|
||||||
lokiMessageDatabase.setOriginalThreadID(messageId, originalThreadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isGroupMessage = message.getGroupInfo().isPresent();
|
boolean isGroupMessage = message.getGroupInfo().isPresent();
|
||||||
if (threadId != null && !isGroupMessage) {
|
if (threadId != null && !isGroupMessage) {
|
||||||
MessageNotifier.updateNotification(context, threadId);
|
MessageNotifier.updateNotification(context, threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loki - Run db updates in background, we should look into fixing this in the future
|
||||||
|
AsyncTask.execute(() -> {
|
||||||
|
if (insertResult.isPresent()) {
|
||||||
|
InsertResult result = insertResult.get();
|
||||||
|
// Loki - Cache the user hex encoded public key (for mentions)
|
||||||
|
LokiAPIUtilities.INSTANCE.populateUserHexEncodedPublicKeyCacheIfNeeded(result.getThreadId(), context);
|
||||||
|
LokiAPI.Companion.cache(textMessage.getSender().serialize(), result.getThreadId());
|
||||||
|
|
||||||
|
// Loki - Store message server ID
|
||||||
|
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||||
|
|
||||||
|
// Loki - Update mapping of message to original thread id
|
||||||
|
if (result.getMessageId() > -1) {
|
||||||
|
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||||
|
LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context);
|
||||||
|
long originalThreadId = threadDatabase.getThreadIdFor(originalRecipient);
|
||||||
|
lokiMessageDatabase.setOriginalThreadID(result.getMessageId(), originalThreadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1066,19 +1095,26 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
return authorisation.verify();
|
return authorisation.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleProfileAvatar(SignalServiceContent content, String url) {
|
||||||
|
Recipient primaryDevice = getPrimaryDeviceRecipient(content.getSender());
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(primaryDevice, url));
|
||||||
|
}
|
||||||
|
|
||||||
private void handlePairingMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
private void handlePairingMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||||
if (authorisation.getType() == PairingAuthorisation.Type.REQUEST) {
|
if (authorisation.getType() == PairingAuthorisation.Type.REQUEST) {
|
||||||
handlePairingRequestMessage(authorisation);
|
handlePairingRequestMessage(authorisation, envelope, content);
|
||||||
} else if (authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
|
} else if (authorisation.getSecondaryDevicePublicKey().equals(userHexEncodedPublicKey)) {
|
||||||
handlePairingAuthorisationMessage(authorisation, envelope, content);
|
handlePairingAuthorisationMessage(authorisation, envelope, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePairingRequestMessage(@NonNull PairingAuthorisation authorisation) {
|
private void handlePairingRequestMessage(@NonNull PairingAuthorisation authorisation, @NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
||||||
boolean isValid = isValidPairingMessage(authorisation);
|
boolean isValid = isValidPairingMessage(authorisation);
|
||||||
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
|
DeviceLinkingSession linkingSession = DeviceLinkingSession.Companion.getShared();
|
||||||
if (isValid && linkingSession.isListeningForLinkingRequests()) {
|
if (isValid && linkingSession.isListeningForLinkingRequests()) {
|
||||||
|
// Loki - If we successfully received a request then we should store the PreKeyBundle
|
||||||
|
storePreKeyBundleIfNeeded(envelope, content);
|
||||||
linkingSession.processLinkingRequest(authorisation);
|
linkingSession.processLinkingRequest(authorisation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1101,6 +1137,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
if (authorisation.getType() != PairingAuthorisation.Type.GRANT) { return; }
|
if (authorisation.getType() != PairingAuthorisation.Type.GRANT) { return; }
|
||||||
Log.d("Loki", "Received pairing authorisation message from: " + authorisation.getPrimaryDevicePublicKey() + ".");
|
Log.d("Loki", "Received pairing authorisation message from: " + authorisation.getPrimaryDevicePublicKey() + ".");
|
||||||
|
// Save PreKeyBundle if for whatever reason we got one
|
||||||
|
storePreKeyBundleIfNeeded(envelope, content);
|
||||||
// Process
|
// Process
|
||||||
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(authorisation);
|
DeviceLinkingSession.Companion.getShared().processLinkingAuthorization(authorisation);
|
||||||
// Store the primary device's public key
|
// Store the primary device's public key
|
||||||
@ -1118,7 +1156,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
|
if (content.senderDisplayName.isPresent() && content.senderDisplayName.get().length() > 0) {
|
||||||
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
|
TextSecurePreferences.setProfileName(context, content.senderDisplayName.get());
|
||||||
}
|
}
|
||||||
|
// Profile avatar updates
|
||||||
|
if (content.getDataMessage().isPresent()) {
|
||||||
|
handleProfileKey(content, content.getDataMessage().get());
|
||||||
|
}
|
||||||
// Contact sync
|
// Contact sync
|
||||||
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
|
if (content.getSyncMessage().isPresent() && content.getSyncMessage().get().getContacts().isPresent()) {
|
||||||
handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get());
|
handleSynchronizeContactMessage(content.getSyncMessage().get().getContacts().get());
|
||||||
@ -1138,6 +1179,23 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void storePreKeyBundleIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
||||||
|
if (content.lokiServiceMessage.isPresent()) {
|
||||||
|
LokiServiceMessage lokiMessage = content.lokiServiceMessage.get();
|
||||||
|
if (lokiMessage.getPreKeyBundleMessage() != null) {
|
||||||
|
int registrationID = TextSecurePreferences.getLocalRegistrationId(context);
|
||||||
|
LokiPreKeyBundleDatabase lokiPreKeyBundleDatabase = DatabaseFactory.getLokiPreKeyBundleDatabase(context);
|
||||||
|
|
||||||
|
// Store the latest PreKeyBundle
|
||||||
|
if (registrationID > 0) {
|
||||||
|
Log.d("Loki", "Received a pre key bundle from: " + envelope.getSource() + ".");
|
||||||
|
PreKeyBundle preKeyBundle = lokiMessage.getPreKeyBundleMessage().getPreKeyBundle(registrationID);
|
||||||
|
lokiPreKeyBundleDatabase.setPreKeyBundle(envelope.getSource(), preKeyBundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void acceptFriendRequestIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
private void acceptFriendRequestIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content) {
|
||||||
// If we get anything other than a friend request, we can assume that we have a session with the other user
|
// If we get anything other than a friend request, we can assume that we have a session with the other user
|
||||||
if (envelope.isFriendRequest() || isGroupChatMessage(content)) { return; }
|
if (envelope.isFriendRequest() || isGroupChatMessage(content)) { return; }
|
||||||
@ -1159,6 +1217,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
if (syncContact) {
|
if (syncContact) {
|
||||||
MessageSender.syncContact(context, contactID.getAddress());
|
MessageSender.syncContact(context, contactID.getAddress());
|
||||||
}
|
}
|
||||||
|
// Allow profile sharing with contact
|
||||||
|
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(contactID, true);
|
||||||
// Update the last message if needed
|
// Update the last message if needed
|
||||||
LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> {
|
LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).success(primaryDevice -> {
|
||||||
Util.runOnMain(() -> {
|
Util.runOnMain(() -> {
|
||||||
@ -1172,7 +1232,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
|
private void updateFriendRequestStatusIfNeeded(@NonNull SignalServiceEnvelope envelope, @NonNull SignalServiceContent content, @NonNull SignalServiceDataMessage message) {
|
||||||
if (!envelope.isFriendRequest() || message.isGroupUpdate()) { return; }
|
if (!envelope.isFriendRequest() || message.isGroupUpdate()) { return; }
|
||||||
// This handles the case where another user sends us a regular message without authorisation
|
// This handles the case where another user sends us a regular message without authorisation
|
||||||
boolean shouldBecomeFriends = PromiseUtil.get(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), false);
|
Promise<Boolean, Exception> promise = PromiseUtil.timeout(MultiDeviceUtilities.shouldAutomaticallyBecomeFriendsWithDevice(content.getSender(), context), 8000);
|
||||||
|
boolean shouldBecomeFriends = PromiseUtil.get(promise, false);
|
||||||
if (shouldBecomeFriends) {
|
if (shouldBecomeFriends) {
|
||||||
// Become friends AND update the message they sent
|
// Become friends AND update the message they sent
|
||||||
becomeFriendsWithContact(content.getSender(), true);
|
becomeFriendsWithContact(content.getSender(), true);
|
||||||
@ -1369,14 +1430,25 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
private void handleProfileKey(@NonNull SignalServiceContent content,
|
private void handleProfileKey(@NonNull SignalServiceContent content,
|
||||||
@NonNull SignalServiceDataMessage message)
|
@NonNull SignalServiceDataMessage message)
|
||||||
{
|
{
|
||||||
|
if (!message.getProfileKey().isPresent()) { return; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
If we get a profile key then we don't need to map it to the primary device.
|
||||||
|
For now a profile key is mapped one-to-one to avoid secondary devices setting the incorrect avatar for a primary device.
|
||||||
|
*/
|
||||||
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
|
RecipientDatabase database = DatabaseFactory.getRecipientDatabase(context);
|
||||||
Address sourceAddress = Address.fromSerialized(content.getSender());
|
Recipient recipient = Recipient.from(context, Address.fromSerialized(content.getSender()), false);
|
||||||
Recipient recipient = Recipient.from(context, sourceAddress, false);
|
|
||||||
|
|
||||||
if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
|
if (recipient.getProfileKey() == null || !MessageDigest.isEqual(recipient.getProfileKey(), message.getProfileKey().get())) {
|
||||||
database.setProfileKey(recipient, message.getProfileKey().get());
|
database.setProfileKey(recipient, message.getProfileKey().get());
|
||||||
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN);
|
database.setUnidentifiedAccessMode(recipient, RecipientDatabase.UnidentifiedAccessMode.UNKNOWN);
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileJob(recipient));
|
String url = content.senderProfileAvatarUrl.or("");
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new RetrieveProfileAvatarJob(recipient, url));
|
||||||
|
|
||||||
|
// Loki - If the recipient is our master device then we need to go and update our avatar mappings on the public chats
|
||||||
|
if (recipient.isOurMasterDevice()) {
|
||||||
|
ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1631,7 +1703,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
*/
|
*/
|
||||||
private Recipient getPrimaryDeviceRecipient(String pubKey) {
|
private Recipient getPrimaryDeviceRecipient(String pubKey) {
|
||||||
try {
|
try {
|
||||||
String primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey).get();
|
String primaryDevice = PromiseUtil.timeout(LokiStorageAPI.shared.getPrimaryDevicePublicKey(pubKey), 5000).get();
|
||||||
String publicKey = (primaryDevice != null) ? primaryDevice : pubKey;
|
String publicKey = (primaryDevice != null) ? primaryDevice : pubKey;
|
||||||
// If the public key matches our primary device then we need to forward the message to ourselves (Note to self)
|
// If the public key matches our primary device then we need to forward the message to ourselves (Note to self)
|
||||||
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
String ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||||
@ -1696,7 +1768,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
|||||||
} else if (content.getSyncMessage().isPresent()) {
|
} else if (content.getSyncMessage().isPresent()) {
|
||||||
try {
|
try {
|
||||||
// We should ignore a sync message if the sender is not one of our devices
|
// We should ignore a sync message if the sender is not one of our devices
|
||||||
boolean isOurDevice = MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()).get();
|
boolean isOurDevice = PromiseUtil.timeout(MultiDeviceUtilities.isOneOfOurDevices(context, sender.getAddress()), 5000).get();
|
||||||
if (!isOurDevice) {
|
if (!isOurDevice) {
|
||||||
Log.w(TAG, "Got a sync message from a device that is not ours!.");
|
Log.w(TAG, "Got a sync message from a device that is not ours!.");
|
||||||
}
|
}
|
||||||
|
@ -25,22 +25,26 @@ public abstract class PushReceivedJob extends BaseJob {
|
|||||||
|
|
||||||
public void processEnvelope(@NonNull SignalServiceEnvelope envelope) {
|
public void processEnvelope(@NonNull SignalServiceEnvelope envelope) {
|
||||||
synchronized (RECEIVE_LOCK) {
|
synchronized (RECEIVE_LOCK) {
|
||||||
if (envelope.hasSource()) {
|
try {
|
||||||
Address source = Address.fromExternal(context, envelope.getSource());
|
if (envelope.hasSource()) {
|
||||||
Recipient recipient = Recipient.from(context, source, false);
|
Address source = Address.fromExternal(context, envelope.getSource());
|
||||||
|
Recipient recipient = Recipient.from(context, source, false);
|
||||||
|
|
||||||
if (!isActiveNumber(recipient)) {
|
if (!isActiveNumber(recipient)) {
|
||||||
DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED);
|
DatabaseFactory.getRecipientDatabase(context).setRegistered(recipient, RecipientDatabase.RegisteredState.REGISTERED);
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(recipient, false));
|
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(recipient, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (envelope.isReceipt()) {
|
if (envelope.isReceipt()) {
|
||||||
handleReceipt(envelope);
|
handleReceipt(envelope);
|
||||||
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender() || envelope.isFriendRequest()) {
|
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender() || envelope.isFriendRequest()) {
|
||||||
handleMessage(envelope);
|
handleMessage(envelope);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
|
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d("Loki", "Failed to process envelope: " + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
|
|||||||
.setQueue("RetrieveProfileAvatarJob" + recipient.getAddress().serialize())
|
.setQueue("RetrieveProfileAvatarJob" + recipient.getAddress().serialize())
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
.setLifespan(TimeUnit.HOURS.toMillis(1))
|
.setLifespan(TimeUnit.HOURS.toMillis(1))
|
||||||
.setMaxInstances(1)
|
.setMaxAttempts(2)
|
||||||
.build(),
|
.build(),
|
||||||
recipient,
|
recipient,
|
||||||
profileAvatar);
|
profileAvatar);
|
||||||
@ -99,8 +99,8 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
|
|||||||
File downloadDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
File downloadDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
InputStream avatarStream = receiver.retrieveProfileAvatar(profileAvatar, downloadDestination, profileKey, MAX_PROFILE_SIZE_BYTES);
|
InputStream avatarStream = receiver.retrieveProfileAvatar(profileAvatar, downloadDestination, profileKey, MAX_PROFILE_SIZE_BYTES);
|
||||||
File decryptDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
File decryptDestination = File.createTempFile("avatar", "jpg", context.getCacheDir());
|
||||||
|
|
||||||
Util.copy(avatarStream, new FileOutputStream(decryptDestination));
|
Util.copy(avatarStream, new FileOutputStream(decryptDestination));
|
||||||
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getAddress()));
|
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getAddress()));
|
||||||
|
29
src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt
Normal file
29
src/org/thoughtcrime/securesms/loki/DeviceLinkingDelegate.kt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||||
|
|
||||||
|
// Loki - TODO: Remove this yucky delegate pattern for device linking dialog once we have the redesign
|
||||||
|
interface DeviceLinkingDelegate {
|
||||||
|
companion object {
|
||||||
|
fun combine(vararg delegates: DeviceLinkingDelegate?): DeviceLinkingDelegate {
|
||||||
|
val validDelegates = delegates.filterNotNull()
|
||||||
|
return object : DeviceLinkingDelegate {
|
||||||
|
override fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {
|
||||||
|
for (delegate in validDelegates) { delegate.handleDeviceLinkAuthorized(pairingAuthorisation) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleDeviceLinkingDialogDismissed() {
|
||||||
|
for (delegate in validDelegates) { delegate.handleDeviceLinkingDialogDismissed() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {
|
||||||
|
for (delegate in validDelegates) { delegate.sendPairingAuthorizedMessage(pairingAuthorisation) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {}
|
||||||
|
fun handleDeviceLinkingDialogDismissed() {}
|
||||||
|
fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {}
|
||||||
|
}
|
@ -8,13 +8,12 @@ import org.whispersystems.signalservice.loki.api.DeviceLinkingSession
|
|||||||
import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener
|
import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||||
|
|
||||||
class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDialogDelegate?) : DeviceLinkingViewDelegate, DeviceLinkingSessionListener {
|
class DeviceLinkingDialog private constructor(private val context: Context, private val mode: DeviceLinkingView.Mode, private val delegate: DeviceLinkingDelegate?) : DeviceLinkingDelegate, DeviceLinkingSessionListener {
|
||||||
private lateinit var view: DeviceLinkingView
|
private lateinit var view: DeviceLinkingView
|
||||||
private lateinit var dialog: AlertDialog
|
private lateinit var dialog: AlertDialog
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDelegate?): DeviceLinkingDialog {
|
||||||
fun show(context: Context, mode: DeviceLinkingView.Mode, delegate: DeviceLinkingDialogDelegate?): DeviceLinkingDialog {
|
|
||||||
val dialog = DeviceLinkingDialog(context, mode, delegate)
|
val dialog = DeviceLinkingDialog(context, mode, delegate)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
return dialog
|
return dialog
|
||||||
@ -22,8 +21,10 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun show() {
|
private fun show() {
|
||||||
view = DeviceLinkingView(context, mode, this)
|
val delegate = DeviceLinkingDelegate.combine(this, this.delegate)
|
||||||
|
view = DeviceLinkingView(context, mode, delegate)
|
||||||
dialog = AlertDialog.Builder(context).setView(view).show()
|
dialog = AlertDialog.Builder(context).setView(view).show()
|
||||||
|
dialog.setCanceledOnTouchOutside(false)
|
||||||
view.dismiss = { dismiss() }
|
view.dismiss = { dismiss() }
|
||||||
DeviceLinkingSession.shared.startListeningForLinkingRequests()
|
DeviceLinkingSession.shared.startListeningForLinkingRequests()
|
||||||
DeviceLinkingSession.shared.addListener(this)
|
DeviceLinkingSession.shared.addListener(this)
|
||||||
@ -35,20 +36,11 @@ class DeviceLinkingDialog private constructor(private val context: Context, priv
|
|||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) {
|
|
||||||
delegate?.handleDeviceLinkAuthorized(pairingAuthorisation)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handleDeviceLinkingDialogDismissed() {
|
override fun handleDeviceLinkingDialogDismissed() {
|
||||||
if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) {
|
if (mode == DeviceLinkingView.Mode.Master && view.pairingAuthorisation != null) {
|
||||||
val authorisation = view.pairingAuthorisation!!
|
val authorisation = view.pairingAuthorisation!!
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePublicKey)
|
DatabaseFactory.getLokiPreKeyBundleDatabase(context).removePreKeyBundle(authorisation.secondaryDevicePublicKey)
|
||||||
}
|
}
|
||||||
delegate?.handleDeviceLinkingDialogDismissed()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {
|
|
||||||
delegate?.sendPairingAuthorizedMessage(pairingAuthorisation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestUserAuthorization(authorisation: PairingAuthorisation) {
|
override fun requestUserAuthorization(authorisation: PairingAuthorisation) {
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki
|
|
||||||
|
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
|
||||||
|
|
||||||
interface DeviceLinkingDialogDelegate {
|
|
||||||
|
|
||||||
fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { }
|
|
||||||
fun handleDeviceLinkingDialogDismissed() { }
|
|
||||||
fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { }
|
|
||||||
}
|
|
@ -5,19 +5,23 @@ import android.graphics.Color
|
|||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import kotlinx.android.synthetic.main.view_device_linking.view.*
|
import kotlinx.android.synthetic.main.view_device_linking.view.*
|
||||||
|
import kotlinx.android.synthetic.main.view_device_linking.view.cancelButton
|
||||||
|
import kotlinx.android.synthetic.main.view_device_linking.view.explanationTextView
|
||||||
|
import kotlinx.android.synthetic.main.view_device_linking.view.titleTextView
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.qr.QrCode
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||||
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode, private var delegate: DeviceLinkingViewDelegate) : LinearLayout(context, attrs, defStyleAttr) {
|
class DeviceLinkingView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, private val mode: Mode, private var delegate: DeviceLinkingDelegate) : LinearLayout(context, attrs, defStyleAttr) {
|
||||||
private lateinit var languageFileDirectory: File
|
private val languageFileDirectory: File = MnemonicUtilities.getLanguageFileDirectory(context)
|
||||||
var dismiss: (() -> Unit)? = null
|
var dismiss: (() -> Unit)? = null
|
||||||
var pairingAuthorisation: PairingAuthorisation? = null
|
var pairingAuthorisation: PairingAuthorisation? = null
|
||||||
private set
|
private set
|
||||||
@ -27,36 +31,14 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
constructor(context: Context, mode: Mode, delegate: DeviceLinkingViewDelegate) : this(context, null, 0, mode, delegate)
|
constructor(context: Context, mode: Mode, delegate: DeviceLinkingDelegate) : this(context, null, 0, mode, delegate)
|
||||||
private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master, object : DeviceLinkingViewDelegate { }) // Just pass in a dummy mode
|
private constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0, Mode.Master, object : DeviceLinkingDelegate { }) // Just pass in a dummy mode
|
||||||
private constructor(context: Context) : this(context, null)
|
private constructor(context: Context) : this(context, null)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setUpLanguageFileDirectory()
|
|
||||||
setUpViewHierarchy()
|
setUpViewHierarchy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpLanguageFileDirectory() {
|
|
||||||
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
|
|
||||||
val directory = File(context.applicationInfo.dataDir)
|
|
||||||
for (language in languages) {
|
|
||||||
val fileName = "$language.txt"
|
|
||||||
if (directory.list().contains(fileName)) { continue }
|
|
||||||
val inputStream = context.assets.open("mnemonic/$fileName")
|
|
||||||
val file = File(directory, fileName)
|
|
||||||
val outputStream = FileOutputStream(file)
|
|
||||||
val buffer = ByteArray(1024)
|
|
||||||
while (true) {
|
|
||||||
val count = inputStream.read(buffer)
|
|
||||||
if (count < 0) { break }
|
|
||||||
outputStream.write(buffer, 0, count)
|
|
||||||
}
|
|
||||||
inputStream.close()
|
|
||||||
outputStream.close()
|
|
||||||
}
|
|
||||||
languageFileDirectory = directory
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUpViewHierarchy() {
|
private fun setUpViewHierarchy() {
|
||||||
inflate(context, R.layout.view_device_linking, this)
|
inflate(context, R.layout.view_device_linking, this)
|
||||||
spinner.indeterminateDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
|
spinner.indeterminateDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
|
||||||
@ -72,11 +54,24 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
|
|||||||
explanationTextView.text = resources.getString(explanationID)
|
explanationTextView.text = resources.getString(explanationID)
|
||||||
mnemonicTextView.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
|
mnemonicTextView.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
|
||||||
if (mode == Mode.Slave) {
|
if (mode == Mode.Slave) {
|
||||||
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context).removing05PrefixIfNeeded()
|
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
|
mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), hexEncodedPublicKey)
|
||||||
}
|
}
|
||||||
authorizeButton.visibility = View.GONE
|
authorizeButton.visibility = View.GONE
|
||||||
authorizeButton.setOnClickListener { authorizePairing() }
|
authorizeButton.setOnClickListener { authorizePairing() }
|
||||||
|
|
||||||
|
// QR Code
|
||||||
|
spinner.visibility = if (mode == Mode.Master) View.GONE else View.VISIBLE
|
||||||
|
qrCodeImageView.visibility = if (mode == Mode.Master) View.VISIBLE else View.GONE
|
||||||
|
if (mode == Mode.Master) {
|
||||||
|
val hexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
ServiceUtil.getWindowManager(context).defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
val size = displayMetrics.widthPixels - 2 * toPx(96, resources)
|
||||||
|
val qrCode = QrCode.create(hexEncodedPublicKey, size)
|
||||||
|
qrCodeImageView.setImageBitmap(qrCode)
|
||||||
|
}
|
||||||
|
|
||||||
cancelButton.setOnClickListener { cancel() }
|
cancelButton.setOnClickListener { cancel() }
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
@ -86,14 +81,14 @@ class DeviceLinkingView private constructor(context: Context, attrs: AttributeSe
|
|||||||
if (mode != Mode.Master || pairingAuthorisation.type != PairingAuthorisation.Type.REQUEST || this.pairingAuthorisation != null) { return }
|
if (mode != Mode.Master || pairingAuthorisation.type != PairingAuthorisation.Type.REQUEST || this.pairingAuthorisation != null) { return }
|
||||||
this.pairingAuthorisation = pairingAuthorisation
|
this.pairingAuthorisation = pairingAuthorisation
|
||||||
spinner.visibility = View.GONE
|
spinner.visibility = View.GONE
|
||||||
|
qrCodeImageView.visibility = View.GONE
|
||||||
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
|
val titleTextViewLayoutParams = titleTextView.layoutParams as LayoutParams
|
||||||
titleTextViewLayoutParams.topMargin = toPx(16, resources)
|
titleTextViewLayoutParams.topMargin = toPx(16, resources)
|
||||||
titleTextView.layoutParams = titleTextViewLayoutParams
|
titleTextView.layoutParams = titleTextViewLayoutParams
|
||||||
titleTextView.text = resources.getString(R.string.view_device_linking_title_3)
|
titleTextView.text = resources.getString(R.string.view_device_linking_title_3)
|
||||||
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_2)
|
explanationTextView.text = resources.getString(R.string.view_device_linking_explanation_2)
|
||||||
mnemonicTextView.visibility = View.VISIBLE
|
mnemonicTextView.visibility = View.VISIBLE
|
||||||
val hexEncodedPublicKey = pairingAuthorisation.secondaryDevicePublicKey.removing05PrefixIfNeeded()
|
mnemonicTextView.text = MnemonicUtilities.getFirst3Words(MnemonicCodec(languageFileDirectory), pairingAuthorisation.secondaryDevicePublicKey)
|
||||||
mnemonicTextView.text = MnemonicCodec(languageFileDirectory).encode(hexEncodedPublicKey).split(" ").slice(0 until 3).joinToString(" ")
|
|
||||||
authorizeButton.visibility = View.VISIBLE
|
authorizeButton.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki
|
|
||||||
|
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
|
||||||
|
|
||||||
interface DeviceLinkingViewDelegate {
|
|
||||||
|
|
||||||
fun handleDeviceLinkAuthorized(pairingAuthorisation: PairingAuthorisation) { }
|
|
||||||
fun handleDeviceLinkingDialogDismissed() { }
|
|
||||||
fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) { }
|
|
||||||
}
|
|
@ -0,0 +1,24 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.design.widget.BottomSheetDialogFragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import kotlinx.android.synthetic.main.fragment_device_list_bottom_sheet.*
|
||||||
|
import network.loki.messenger.R
|
||||||
|
|
||||||
|
public class DeviceListBottomSheetFragment : BottomSheetDialogFragment() {
|
||||||
|
var onEditTapped: (() -> Unit)? = null
|
||||||
|
var onUnlinkTapped: (() -> Unit)? = null
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_device_list_bottom_sheet, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
editDisplayNameText.setOnClickListener { onEditTapped?.invoke() }
|
||||||
|
unlinkDeviceText.setOnClickListener { onUnlinkTapped?.invoke() }
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,7 @@ class DisplayNameActivity : BaseActionBarActivity() {
|
|||||||
application.startRSSFeedPollersIfNeeded()
|
application.startRSSFeedPollersIfNeeded()
|
||||||
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||||
servers.forEach { publicChatAPI.setDisplayName(name, it) }
|
servers.forEach { publicChatAPI.setDisplayName(name, it) }
|
||||||
|
application.updatePublicChatProfileAvatarIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.support.v7.content.res.AppCompatResources
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
|
||||||
|
|
||||||
|
class JazzIdenticonContactPhoto(val hexEncodedPublicKey: String) : FallbackContactPhoto {
|
||||||
|
override fun asDrawable(context: Context, color: Int): Drawable {
|
||||||
|
return asDrawable(context, color, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun asDrawable(context: Context, color: Int, inverted: Boolean): Drawable {
|
||||||
|
val targetSize = context.resources.getDimensionPixelSize(R.dimen.contact_photo_target_size)
|
||||||
|
return JazzIdenticonDrawable(targetSize, targetSize, hexEncodedPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun asCallCard(context: Context): Drawable? {
|
||||||
|
return AppCompatResources.getDrawable(context, R.drawable.ic_person_large)
|
||||||
|
}
|
||||||
|
}
|
85
src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt
Normal file
85
src/org/thoughtcrime/securesms/loki/LinkedDevicesActivity.kt
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.Toast
|
||||||
|
import org.thoughtcrime.securesms.*
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicTheme
|
||||||
|
import org.thoughtcrime.securesms.util.DynamicLanguage
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import nl.komponents.kovenant.then
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.sms.MessageSender
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.thoughtcrime.securesms.util.Util
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||||
|
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||||
|
|
||||||
|
class LinkedDevicesActivity : PassphraseRequiredActionBarActivity(), DeviceLinkingDelegate {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = DeviceActivity::class.java.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dynamicTheme = DynamicTheme()
|
||||||
|
private val dynamicLanguage = DynamicLanguage()
|
||||||
|
private lateinit var deviceListFragment: DeviceListFragment
|
||||||
|
|
||||||
|
public override fun onPreCreate() {
|
||||||
|
dynamicTheme.onCreate(this)
|
||||||
|
dynamicLanguage.onCreate(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||||
|
super.onCreate(savedInstanceState, ready)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
supportActionBar?.setTitle(R.string.AndroidManifest__linked_devices)
|
||||||
|
this.deviceListFragment = DeviceListFragment()
|
||||||
|
this.deviceListFragment.setAddDeviceButtonListener {
|
||||||
|
DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Master, this)
|
||||||
|
}
|
||||||
|
this.deviceListFragment.setHandleDisconnectDevice { devicePublicKey ->
|
||||||
|
// Purge the device pairing from our database
|
||||||
|
val ourPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||||
|
val database = DatabaseFactory.getLokiAPIDatabase(this)
|
||||||
|
database.removePairingAuthorisation(ourPublicKey, devicePublicKey)
|
||||||
|
// Update mapping on the file server
|
||||||
|
LokiStorageAPI.shared.updateUserDeviceMappings().success {
|
||||||
|
// Send an unpair request to let the device know that it has been revoked
|
||||||
|
MessageSender.sendUnpairRequest(this, devicePublicKey)
|
||||||
|
}
|
||||||
|
// Refresh the list
|
||||||
|
this.deviceListFragment.refresh()
|
||||||
|
Toast.makeText(this, R.string.DeviceListActivity_unlinked_device, Toast.LENGTH_LONG).show()
|
||||||
|
return@setHandleDisconnectDevice null
|
||||||
|
}
|
||||||
|
this.deviceListFragment.setHandleDeviceNameChange { pair ->
|
||||||
|
DatabaseFactory.getLokiUserDatabase(this).setDisplayName(pair.first, pair.second)
|
||||||
|
this.deviceListFragment.refresh()
|
||||||
|
return@setHandleDeviceNameChange null
|
||||||
|
}
|
||||||
|
initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.currentLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
dynamicTheme.onResume(this)
|
||||||
|
dynamicLanguage.onResume(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == android.R.id.home) {
|
||||||
|
finish()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendPairingAuthorizedMessage(pairingAuthorisation: PairingAuthorisation) {
|
||||||
|
AsyncTask.execute {
|
||||||
|
signAndSendPairingAuthorisationMessage(this, pairingAuthorisation)
|
||||||
|
Util.runOnMain { this.deviceListFragment.refresh() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -189,6 +189,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
database.delete(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey ))
|
database.delete(pairingAuthorisationCache, "$primaryDevicePublicKey = ? OR $secondaryDevicePublicKey = ?", arrayOf( hexEncodedPublicKey, hexEncodedPublicKey ))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removePairingAuthorisation(primaryDevicePublicKey: String, secondaryDevicePublicKey: String) {
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
database.delete(pairingAuthorisationCache, "${Companion.primaryDevicePublicKey} = ? OR ${Companion.secondaryDevicePublicKey} = ?", arrayOf( primaryDevicePublicKey, secondaryDevicePublicKey ))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Convenience
|
// region Convenience
|
||||||
|
@ -19,7 +19,7 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
|||||||
private val friendRequestStatus = "friend_request_status"
|
private val friendRequestStatus = "friend_request_status"
|
||||||
private val threadID = "thread_id"
|
private val threadID = "thread_id"
|
||||||
@JvmStatic val createMessageFriendRequestTableCommand = "CREATE TABLE $messageFriendRequestTableName ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);"
|
@JvmStatic val createMessageFriendRequestTableCommand = "CREATE TABLE $messageFriendRequestTableName ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||||
@JvmStatic val createMessageToThreadMappingTableCommand = "CREATE TABLE $messageThreadMappingTableName ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);"
|
@JvmStatic val createMessageToThreadMappingTableCommand = "CREATE TABLE IF NOT EXISTS $messageThreadMappingTableName ($messageID INTEGER PRIMARY KEY, $threadID INTEGER);"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getQuoteServerID(quoteID: Long, quoteeHexEncodedPublicKey: String): Long? {
|
override fun getQuoteServerID(quoteID: Long, quoteeHexEncodedPublicKey: String): Long? {
|
||||||
|
@ -36,11 +36,6 @@ class LokiPreKeyBundleDatabase(context: Context, helper: SQLCipherOpenHelper) :
|
|||||||
"$signedPreKeySignature TEXT," + "$identityKey TEXT NOT NULL," + "$deviceID INTEGER," + "$registrationID INTEGER" + ");"
|
"$signedPreKeySignature TEXT," + "$identityKey TEXT NOT NULL," + "$deviceID INTEGER," + "$registrationID INTEGER" + ");"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetAllPreKeyBundleInfo() {
|
|
||||||
TextSecurePreferences.removeLocalRegistrationId(context)
|
|
||||||
TextSecurePreferences.setSignedPreKeyRegistered(context, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? {
|
fun generatePreKeyBundle(hexEncodedPublicKey: String): PreKeyBundle? {
|
||||||
var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
|
var registrationID = TextSecurePreferences.getLocalRegistrationId(context)
|
||||||
if (registrationID == 0) {
|
if (registrationID == 0) {
|
||||||
|
@ -6,9 +6,14 @@ import android.util.Log
|
|||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.then
|
import nl.komponents.kovenant.then
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob
|
import org.thoughtcrime.securesms.jobs.PushDecryptJob
|
||||||
|
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.libsignal.util.guava.Optional
|
import org.whispersystems.libsignal.util.guava.Optional
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||||
@ -21,7 +26,9 @@ import org.whispersystems.signalservice.loki.api.LokiPublicChat
|
|||||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI
|
||||||
import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage
|
import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage
|
||||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||||
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||||
import org.whispersystems.signalservice.loki.utilities.successBackground
|
import org.whispersystems.signalservice.loki.utilities.successBackground
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class LokiPublicChatPoller(private val context: Context, private val group: LokiPublicChat) {
|
class LokiPublicChatPoller(private val context: Context, private val group: LokiPublicChat) {
|
||||||
@ -155,6 +162,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
|
val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})"
|
||||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
|
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey
|
val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey
|
||||||
val serviceDataMessage = getDataMessage(message)
|
val serviceDataMessage = getDataMessage(message)
|
||||||
val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false)
|
val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false)
|
||||||
@ -163,6 +171,25 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
} else {
|
} else {
|
||||||
PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update profile avatar if needed
|
||||||
|
val senderRecipient = Recipient.from(context, Address.fromSerialized(senderPublicKey), false)
|
||||||
|
if (message.avatar != null && message.avatar!!.url.isNotEmpty()) {
|
||||||
|
val profileKey = message.avatar!!.profileKey
|
||||||
|
val url = message.avatar!!.url
|
||||||
|
if (senderRecipient.profileKey == null || !MessageDigest.isEqual(senderRecipient.profileKey, profileKey)) {
|
||||||
|
val database = DatabaseFactory.getRecipientDatabase(context)
|
||||||
|
database.setProfileKey(senderRecipient, profileKey)
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderRecipient, url))
|
||||||
|
}
|
||||||
|
} else if (senderRecipient.profileAvatar.orEmpty().isNotEmpty()) {
|
||||||
|
// Unset the avatar if we had an avatar before and we're not friends with the person
|
||||||
|
val threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(senderRecipient)
|
||||||
|
val friendRequestStatus = DatabaseFactory.getLokiThreadDatabase(context).getFriendRequestStatus(threadId)
|
||||||
|
if (friendRequestStatus != LokiThreadFriendRequestStatus.FRIENDS) {
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderRecipient, ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fun processOutgoingMessage(message: LokiPublicChatMessage) {
|
fun processOutgoingMessage(message: LokiPublicChatMessage) {
|
||||||
val messageServerID = message.serverID ?: return
|
val messageServerID = message.serverID ?: return
|
||||||
@ -178,6 +205,19 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki
|
|||||||
} else {
|
} else {
|
||||||
PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript)
|
PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loki - If we got a message from our master device then make sure our mappings stay in sync
|
||||||
|
val recipient = Recipient.from(context, Address.fromSerialized(message.hexEncodedPublicKey), false)
|
||||||
|
if (recipient.isOurMasterDevice && message.avatar != null) {
|
||||||
|
val profileKey = message.avatar!!.profileKey
|
||||||
|
val url = message.avatar!!.url
|
||||||
|
if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, profileKey)) {
|
||||||
|
val database = DatabaseFactory.getRecipientDatabase(context)
|
||||||
|
database.setProfileKey(recipient, profileKey)
|
||||||
|
database.setProfileAvatar(recipient, url)
|
||||||
|
ApplicationContext.getInstance(context).updatePublicChatProfileAvatarIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var userDevices = setOf<String>()
|
var userDevices = setOf<String>()
|
||||||
var uniqueDevices = setOf<String>()
|
var uniqueDevices = setOf<String>()
|
||||||
|
@ -16,6 +16,7 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database
|
|||||||
companion object {
|
companion object {
|
||||||
// Shared
|
// Shared
|
||||||
private val displayName = "display_name"
|
private val displayName = "display_name"
|
||||||
|
private val profileAvatarUrl = "profile_avatar_url"
|
||||||
// Display name cache
|
// Display name cache
|
||||||
private val displayNameTable = "loki_user_display_name_database"
|
private val displayNameTable = "loki_user_display_name_database"
|
||||||
private val hexEncodedPublicKey = "hex_encoded_public_key"
|
private val hexEncodedPublicKey = "hex_encoded_public_key"
|
||||||
@ -66,4 +67,12 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database
|
|||||||
Log.d("Loki", "Couldn't save server display name due to exception: $e.")
|
Log.d("Loki", "Couldn't save server display name due to exception: $e.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getProfileAvatarUrl(hexEncodedPublicKey: String): String? {
|
||||||
|
return if (hexEncodedPublicKey == TextSecurePreferences.getLocalNumber(context)) {
|
||||||
|
TextSecurePreferences.getProfileAvatarUrl(context)
|
||||||
|
} else {
|
||||||
|
Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).resolve().profileAvatar
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
37
src/org/thoughtcrime/securesms/loki/MnemonicUtilities.kt
Normal file
37
src/org/thoughtcrime/securesms/loki/MnemonicUtilities.kt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
|
||||||
|
import org.whispersystems.signalservice.loki.utilities.removing05PrefixIfNeeded
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
object MnemonicUtilities {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
public fun getLanguageFileDirectory(context: Context): File {
|
||||||
|
val languages = listOf( "english", "japanese", "portuguese", "spanish" )
|
||||||
|
val directory = File(context.applicationInfo.dataDir)
|
||||||
|
for (language in languages) {
|
||||||
|
val fileName = "$language.txt"
|
||||||
|
if (directory.list().contains(fileName)) { continue }
|
||||||
|
val inputStream = context.assets.open("mnemonic/$fileName")
|
||||||
|
val file = File(directory, fileName)
|
||||||
|
val outputStream = FileOutputStream(file)
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
while (true) {
|
||||||
|
val count = inputStream.read(buffer)
|
||||||
|
if (count < 0) { break }
|
||||||
|
outputStream.write(buffer, 0, count)
|
||||||
|
}
|
||||||
|
inputStream.close()
|
||||||
|
outputStream.close()
|
||||||
|
}
|
||||||
|
return directory
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
public fun getFirst3Words(codec: MnemonicCodec, hexEncodedPublicKey: String): String {
|
||||||
|
return codec.encode(hexEncodedPublicKey.removing05PrefixIfNeeded()).split(" ").slice(0 until 3).joinToString(" ")
|
||||||
|
}
|
||||||
|
}
|
@ -6,9 +6,13 @@ import nl.komponents.kovenant.Promise
|
|||||||
import nl.komponents.kovenant.all
|
import nl.komponents.kovenant.all
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
|
import nl.komponents.kovenant.then
|
||||||
import nl.komponents.kovenant.toFailVoid
|
import nl.komponents.kovenant.toFailVoid
|
||||||
|
import nl.komponents.kovenant.ui.successUi
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.crypto.PreKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.logging.Log
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
@ -22,11 +26,32 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
|||||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
|
||||||
|
import org.whispersystems.signalservice.loki.utilities.Analytics
|
||||||
import org.whispersystems.signalservice.loki.utilities.recover
|
import org.whispersystems.signalservice.loki.utilities.recover
|
||||||
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
|
fun checkForRevocation(context: Context) {
|
||||||
|
val primaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context) ?: return
|
||||||
|
val ourDevice = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
|
||||||
|
LokiStorageAPI.shared.fetchDeviceMappings(primaryDevice).bind { mappings ->
|
||||||
|
val ourMapping = mappings.find { it.secondaryDevicePublicKey == ourDevice }
|
||||||
|
if (ourMapping != null) throw Error("Device has not been revoked")
|
||||||
|
// remove pairing authorisations for our device
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).removePairingAuthorisations(ourDevice)
|
||||||
|
LokiStorageAPI.shared.updateUserDeviceMappings()
|
||||||
|
}.successUi {
|
||||||
|
Analytics.shared.track("Secondary Device Unlinked")
|
||||||
|
TextSecurePreferences.setNeedsRevocationCheck(context, false)
|
||||||
|
ApplicationContext.getInstance(context).clearData()
|
||||||
|
}.fail { error ->
|
||||||
|
TextSecurePreferences.setNeedsRevocationCheck(context, true)
|
||||||
|
Log.d("Loki", "Revocation check failed: ${error.message ?: error}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
|
fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
|
||||||
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
||||||
return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
|
return LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
|
||||||
@ -91,12 +116,16 @@ fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Conte
|
|||||||
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
|
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
|
||||||
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||||
val address = SignalServiceAddress(contactHexEncodedPublicKey)
|
val address = SignalServiceAddress(contactHexEncodedPublicKey)
|
||||||
val message = SignalServiceDataMessage.newBuilder().withBody(null).withPairingAuthorisation(authorisation)
|
val message = SignalServiceDataMessage.newBuilder().withPairingAuthorisation(authorisation)
|
||||||
// A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message.
|
// A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message.
|
||||||
if (authorisation.type == PairingAuthorisation.Type.REQUEST) {
|
if (authorisation.type == PairingAuthorisation.Type.REQUEST) {
|
||||||
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
|
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
|
||||||
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
|
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
|
||||||
|
} else {
|
||||||
|
// Send over our profile key so that our linked device can get our profile picture
|
||||||
|
message.withProfileKey(ProfileKeyUtil.getProfileKey(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.")
|
Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.")
|
||||||
val result = messageSender.sendMessage(0, address, Optional.absent<UnidentifiedAccessPair>(), message.build())
|
val result = messageSender.sendMessage(0, address, Optional.absent<UnidentifiedAccessPair>(), message.build())
|
||||||
|
@ -11,39 +11,58 @@ import org.whispersystems.libsignal.util.guava.Optional
|
|||||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||||
|
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
data class BackgroundMessage private constructor(val recipient: String, val body: String?, val friendRequest: Boolean, val unpairingRequest: Boolean) {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun create(recipient: String) = BackgroundMessage(recipient, null, false, false)
|
||||||
|
@JvmStatic
|
||||||
|
fun createFriendRequest(recipient: String, messageBody: String) = BackgroundMessage(recipient, messageBody, true, false)
|
||||||
|
@JvmStatic
|
||||||
|
fun createUnpairingRequest(recipient: String) = BackgroundMessage(recipient, null, false, true)
|
||||||
|
|
||||||
|
internal fun parse(serialized: String): BackgroundMessage {
|
||||||
|
val node = JsonUtil.fromJson(serialized)
|
||||||
|
val recipient = node.get("recipient").asText()
|
||||||
|
val body = if (node.hasNonNull("body")) node.get("body").asText() else null
|
||||||
|
val friendRequest = node.get("friendRequest").asBoolean(false)
|
||||||
|
val unpairingRequest = node.get("unpairingRequest").asBoolean(false)
|
||||||
|
return BackgroundMessage(recipient, body, friendRequest, unpairingRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serialize(): String {
|
||||||
|
val map = mapOf("recipient" to recipient, "body" to body, "friendRequest" to friendRequest, "unpairingRequest" to unpairingRequest)
|
||||||
|
return JsonUtil.toJson(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PushBackgroundMessageSendJob private constructor(
|
class PushBackgroundMessageSendJob private constructor(
|
||||||
parameters: Parameters,
|
parameters: Parameters,
|
||||||
private val recipient: String,
|
private val message: BackgroundMessage
|
||||||
private val messageBody: String?,
|
|
||||||
private val friendRequest: Boolean
|
|
||||||
) : BaseJob(parameters) {
|
) : BaseJob(parameters) {
|
||||||
companion object {
|
companion object {
|
||||||
const val KEY = "PushBackgroundMessageSendJob"
|
const val KEY = "PushBackgroundMessageSendJob"
|
||||||
|
|
||||||
private val TAG = PushBackgroundMessageSendJob::class.java.simpleName
|
private val TAG = PushBackgroundMessageSendJob::class.java.simpleName
|
||||||
|
|
||||||
private val KEY_RECIPIENT = "recipient"
|
private val KEY_MESSAGE = "message"
|
||||||
private val KEY_MESSAGE_BODY = "message_body"
|
|
||||||
private val KEY_FRIEND_REQUEST = "asFriendRequest"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(recipient: String): this(recipient, null, false)
|
constructor(message: BackgroundMessage) : this(Parameters.Builder()
|
||||||
constructor(recipient: String, messageBody: String?, friendRequest: Boolean) : this(Parameters.Builder()
|
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
.setQueue(KEY)
|
.setQueue(KEY)
|
||||||
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
.setMaxAttempts(1)
|
.setMaxAttempts(1)
|
||||||
.build(),
|
.build(),
|
||||||
recipient, messageBody, friendRequest)
|
message)
|
||||||
|
|
||||||
override fun serialize(): Data {
|
override fun serialize(): Data {
|
||||||
return Data.Builder()
|
return Data.Builder()
|
||||||
.putString(KEY_RECIPIENT, recipient)
|
.putString(KEY_MESSAGE, message.serialize())
|
||||||
.putString(KEY_MESSAGE_BODY, messageBody)
|
|
||||||
.putBoolean(KEY_FRIEND_REQUEST, friendRequest)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,22 +71,24 @@ class PushBackgroundMessageSendJob private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override fun onRun() {
|
public override fun onRun() {
|
||||||
val message = SignalServiceDataMessage.newBuilder()
|
val dataMessage = SignalServiceDataMessage.newBuilder()
|
||||||
.withTimestamp(System.currentTimeMillis())
|
.withTimestamp(System.currentTimeMillis())
|
||||||
.withBody(messageBody)
|
.withBody(message.body)
|
||||||
|
|
||||||
if (friendRequest) {
|
if (message.friendRequest) {
|
||||||
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(recipient)
|
val bundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(message.recipient)
|
||||||
message.withPreKeyBundle(bundle)
|
dataMessage.withPreKeyBundle(bundle)
|
||||||
.asFriendRequest(true)
|
.asFriendRequest(true)
|
||||||
|
} else if (message.unpairingRequest) {
|
||||||
|
dataMessage.asUnpairingRequest(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
|
||||||
val address = SignalServiceAddress(recipient)
|
val address = SignalServiceAddress(message.recipient)
|
||||||
try {
|
try {
|
||||||
messageSender.sendMessage(-1, address, Optional.absent<UnidentifiedAccessPair>(), message.build()) // The message ID doesn't matter
|
messageSender.sendMessage(-1, address, Optional.absent<UnidentifiedAccessPair>(), dataMessage.build()) // The message ID doesn't matter
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to send background message to: $recipient.")
|
Log.d("Loki", "Failed to send background message to: ${message.recipient}.")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,10 +103,8 @@ class PushBackgroundMessageSendJob private constructor(
|
|||||||
class Factory : Job.Factory<PushBackgroundMessageSendJob> {
|
class Factory : Job.Factory<PushBackgroundMessageSendJob> {
|
||||||
override fun create(parameters: Parameters, data: Data): PushBackgroundMessageSendJob {
|
override fun create(parameters: Parameters, data: Data): PushBackgroundMessageSendJob {
|
||||||
try {
|
try {
|
||||||
val recipient = data.getString(KEY_RECIPIENT)
|
val messageJSON = data.getString(KEY_MESSAGE)
|
||||||
val messageBody = if (data.hasString(KEY_MESSAGE_BODY)) data.getString(KEY_MESSAGE_BODY) else null
|
return PushBackgroundMessageSendJob(parameters, BackgroundMessage.parse(messageJSON))
|
||||||
val friendRequest = data.getBooleanOrDefault(KEY_FRIEND_REQUEST, false)
|
|
||||||
return PushBackgroundMessageSendJob(parameters, recipient, messageBody, friendRequest)
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
throw AssertionError(e)
|
throw AssertionError(e)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
|
data class RecipientAvatarModifiedEvent(val recipient: Recipient)
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import android.support.v4.app.Fragment
|
||||||
|
import android.support.v7.app.AppCompatActivity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -14,8 +15,15 @@ import org.thoughtcrime.securesms.qr.ScanningThread
|
|||||||
|
|
||||||
class ScanQRCodeFragment : Fragment() {
|
class ScanQRCodeFragment : Fragment() {
|
||||||
private val scanningThread = ScanningThread()
|
private val scanningThread = ScanningThread()
|
||||||
|
private var viewCreated = false
|
||||||
var scanListener: ScanListener? = null
|
var scanListener: ScanListener? = null
|
||||||
set(value) { field = value; scanningThread.setScanListener(scanListener) }
|
set(value) { field = value; scanningThread.setScanListener(scanListener) }
|
||||||
|
var mode: Mode = Mode.NewConversation
|
||||||
|
set(value) { field = value; updateDescription(); }
|
||||||
|
|
||||||
|
// region Types
|
||||||
|
enum class Mode { NewConversation, LinkDevice }
|
||||||
|
// endregion
|
||||||
|
|
||||||
override fun onCreateView(layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
|
override fun onCreateView(layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
|
||||||
return layoutInflater.inflate(R.layout.fragment_scan_qr_code, viewGroup, false)
|
return layoutInflater.inflate(R.layout.fragment_scan_qr_code, viewGroup, false)
|
||||||
@ -23,10 +31,12 @@ class ScanQRCodeFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, bundle: Bundle?) {
|
override fun onViewCreated(view: View, bundle: Bundle?) {
|
||||||
super.onViewCreated(view, bundle)
|
super.onViewCreated(view, bundle)
|
||||||
|
viewCreated = true
|
||||||
when (resources.configuration.orientation) {
|
when (resources.configuration.orientation) {
|
||||||
Configuration.ORIENTATION_LANDSCAPE -> overlayView.orientation = LinearLayout.HORIZONTAL
|
Configuration.ORIENTATION_LANDSCAPE -> overlayView.orientation = LinearLayout.HORIZONTAL
|
||||||
else -> overlayView.orientation = LinearLayout.VERTICAL
|
else -> overlayView.orientation = LinearLayout.VERTICAL
|
||||||
}
|
}
|
||||||
|
updateDescription()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -35,8 +45,10 @@ class ScanQRCodeFragment : Fragment() {
|
|||||||
this.cameraView.onResume()
|
this.cameraView.onResume()
|
||||||
this.cameraView.setPreviewCallback(scanningThread)
|
this.cameraView.setPreviewCallback(scanningThread)
|
||||||
this.scanningThread.start()
|
this.scanningThread.start()
|
||||||
val activity = activity as NewConversationActivity
|
if (activity is AppCompatActivity) {
|
||||||
activity.supportActionBar!!.setTitle(R.string.fragment_scan_qr_code_title)
|
val activity = activity as AppCompatActivity
|
||||||
|
activity.supportActionBar?.setTitle(R.string.fragment_scan_qr_code_title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@ -55,4 +67,13 @@ class ScanQRCodeFragment : Fragment() {
|
|||||||
cameraView.onResume()
|
cameraView.onResume()
|
||||||
cameraView.setPreviewCallback(scanningThread)
|
cameraView.setPreviewCallback(scanningThread)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateDescription() {
|
||||||
|
if (!viewCreated) { return }
|
||||||
|
val text = when (mode) {
|
||||||
|
Mode.NewConversation -> R.string.fragment_scan_qr_code_explanation_new_conversation
|
||||||
|
Mode.LinkDevice -> R.string.fragment_scan_qr_code_explanation_link_device
|
||||||
|
}
|
||||||
|
descriptionTextView.setText(text)
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,11 +1,13 @@
|
|||||||
package org.thoughtcrime.securesms.loki
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.FragmentManager
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -19,6 +21,8 @@ import org.thoughtcrime.securesms.database.Address
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.IdentityDatabase
|
import org.thoughtcrime.securesms.database.IdentityDatabase
|
||||||
import org.thoughtcrime.securesms.logging.Log
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
|
import org.thoughtcrime.securesms.qr.ScanListener
|
||||||
import org.thoughtcrime.securesms.util.Hex
|
import org.thoughtcrime.securesms.util.Hex
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.curve25519.Curve25519
|
import org.whispersystems.curve25519.Curve25519
|
||||||
@ -32,7 +36,7 @@ import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
class SeedActivity : BaseActionBarActivity(), DeviceLinkingDelegate, ScanListener {
|
||||||
private lateinit var languageFileDirectory: File
|
private lateinit var languageFileDirectory: File
|
||||||
private var mode = Mode.Register
|
private var mode = Mode.Register
|
||||||
set(newValue) { field = newValue; updateUI() }
|
set(newValue) { field = newValue; updateUI() }
|
||||||
@ -57,6 +61,23 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
|||||||
toggleRestoreModeButton.setOnClickListener { mode = Mode.Restore }
|
toggleRestoreModeButton.setOnClickListener { mode = Mode.Restore }
|
||||||
toggleLinkModeButton.setOnClickListener { mode = Mode.Link }
|
toggleLinkModeButton.setOnClickListener { mode = Mode.Link }
|
||||||
mainButton.setOnClickListener { handleMainButtonTapped() }
|
mainButton.setOnClickListener { handleMainButtonTapped() }
|
||||||
|
scanQRButton.setOnClickListener {
|
||||||
|
Permissions.with(this)
|
||||||
|
.request(Manifest.permission.CAMERA)
|
||||||
|
.ifNecessary()
|
||||||
|
.withPermanentDenialDialog(getString(R.string.fragment_scan_qr_code_camera_permission_dialog_message))
|
||||||
|
.onAllGranted {
|
||||||
|
val fragment = ScanQRCodeFragment()
|
||||||
|
fragment.mode = ScanQRCodeFragment.Mode.LinkDevice
|
||||||
|
fragment.scanListener = this
|
||||||
|
supportFragmentManager.beginTransaction().replace(android.R.id.content, fragment).addToBackStack("QR").commitAllowingStateLoss()
|
||||||
|
publicKeyEditText.clearFocus()
|
||||||
|
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(publicKeyEditText.windowToken, 0)
|
||||||
|
}
|
||||||
|
.onAnyDenied { Toast.makeText(this, R.string.fragment_scan_qr_code_camera_permission_dialog_message, Toast.LENGTH_SHORT).show() }
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
Analytics.shared.track("Seed Screen Viewed")
|
Analytics.shared.track("Seed Screen Viewed")
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
@ -106,6 +127,7 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
|||||||
mnemonicEditText.visibility = restoreModeVisibility
|
mnemonicEditText.visibility = restoreModeVisibility
|
||||||
linkExplanationTextView.visibility = linkModeVisibility
|
linkExplanationTextView.visibility = linkModeVisibility
|
||||||
publicKeyEditText.visibility = linkModeVisibility
|
publicKeyEditText.visibility = linkModeVisibility
|
||||||
|
scanQRButton.visibility = linkModeVisibility
|
||||||
toggleRegisterModeButton.visibility = if (mode != Mode.Register) View.VISIBLE else View.GONE
|
toggleRegisterModeButton.visibility = if (mode != Mode.Register) View.VISIBLE else View.GONE
|
||||||
toggleRestoreModeButton.visibility = if (mode != Mode.Restore) View.VISIBLE else View.GONE
|
toggleRestoreModeButton.visibility = if (mode != Mode.Restore) View.VISIBLE else View.GONE
|
||||||
toggleLinkModeButton.visibility = if (mode != Mode.Link) View.VISIBLE else View.GONE
|
toggleLinkModeButton.visibility = if (mode != Mode.Link) View.VISIBLE else View.GONE
|
||||||
@ -230,10 +252,19 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
|||||||
|
|
||||||
private fun resetForRegistration() {
|
private fun resetForRegistration() {
|
||||||
IdentityKeyUtil.delete(this, IdentityKeyUtil.lokiSeedKey)
|
IdentityKeyUtil.delete(this, IdentityKeyUtil.lokiSeedKey)
|
||||||
DatabaseFactory.getLokiPreKeyBundleDatabase(this).resetAllPreKeyBundleInfo()
|
|
||||||
TextSecurePreferences.removeLocalNumber(this)
|
TextSecurePreferences.removeLocalNumber(this)
|
||||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, false)
|
TextSecurePreferences.setHasSeenWelcomeScreen(this, false)
|
||||||
TextSecurePreferences.setPromptedPushRegistration(this, false)
|
TextSecurePreferences.setPromptedPushRegistration(this, false)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
override fun onQrDataFound(data: String?) {
|
||||||
|
runOnUiThread {
|
||||||
|
if (data != null && PublicKeyValidation.isValid(data.trim())) {
|
||||||
|
publicKeyEditText.setText(data.trim())
|
||||||
|
supportFragmentManager.popBackStackImmediate("QR", FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||||
|
handleMainButtonTapped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
}
|
}
|
@ -4,6 +4,13 @@ import android.app.Notification;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffXfermode;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@ -237,11 +244,34 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
|||||||
Bitmap recipientPhotoBitmap = BitmapUtil.createFromDrawable(drawable, largeIconTargetSize, largeIconTargetSize);
|
Bitmap recipientPhotoBitmap = BitmapUtil.createFromDrawable(drawable, largeIconTargetSize, largeIconTargetSize);
|
||||||
|
|
||||||
if (recipientPhotoBitmap != null) {
|
if (recipientPhotoBitmap != null) {
|
||||||
setLargeIcon(recipientPhotoBitmap);
|
setLargeIcon(getCircleBitmap(recipientPhotoBitmap));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Bitmap getCircleBitmap(Bitmap bitmap) {
|
||||||
|
final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
|
||||||
|
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
final Canvas canvas = new Canvas(output);
|
||||||
|
|
||||||
|
final int color = Color.RED;
|
||||||
|
final Paint paint = new Paint();
|
||||||
|
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
||||||
|
final RectF rectF = new RectF(rect);
|
||||||
|
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
canvas.drawARGB(0, 0, 0, 0);
|
||||||
|
paint.setColor(color);
|
||||||
|
canvas.drawOval(rectF, paint);
|
||||||
|
|
||||||
|
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
||||||
|
canvas.drawBitmap(bitmap, rect, rect, paint);
|
||||||
|
|
||||||
|
bitmap.recycle();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasBigPictureSlide(@Nullable SlideDeck slideDeck) {
|
private boolean hasBigPictureSlide(@Nullable SlideDeck slideDeck) {
|
||||||
if (slideDeck == null) {
|
if (slideDeck == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -5,6 +5,7 @@ import android.content.ClipData;
|
|||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.annotation.RequiresApi;
|
import android.support.annotation.RequiresApi;
|
||||||
import android.support.v7.preference.Preference;
|
import android.support.v7.preference.Preference;
|
||||||
@ -13,14 +14,20 @@ import android.text.TextUtils;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
import org.thoughtcrime.securesms.loki.MnemonicUtilities;
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -31,6 +38,7 @@ public class ProfilePreference extends Preference {
|
|||||||
private TextView profileNameView;
|
private TextView profileNameView;
|
||||||
private TextView profileNumberView;
|
private TextView profileNumberView;
|
||||||
private TextView profileTagView;
|
private TextView profileTagView;
|
||||||
|
private String ourDeviceWords;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ProfilePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@ -73,13 +81,14 @@ public class ProfilePreference extends Preference {
|
|||||||
public void refresh() {
|
public void refresh() {
|
||||||
if (profileNumberView == null) return;
|
if (profileNumberView == null) return;
|
||||||
|
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
Context context = getContext();
|
||||||
String primaryDevicePublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(getContext());
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||||
|
String primaryDevicePublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||||
String publicKey = primaryDevicePublicKey != null ? primaryDevicePublicKey : userHexEncodedPublicKey;
|
String publicKey = primaryDevicePublicKey != null ? primaryDevicePublicKey : userHexEncodedPublicKey;
|
||||||
final Address localAddress = Address.fromSerialized(publicKey);
|
final Address localAddress = Address.fromSerialized(publicKey);
|
||||||
final String profileName = TextSecurePreferences.getProfileName(getContext());
|
final Recipient recipient = Recipient.from(context, localAddress, false);
|
||||||
|
final String profileName = TextSecurePreferences.getProfileName(context);
|
||||||
|
|
||||||
Context context = getContext();
|
|
||||||
containerView.setOnLongClickListener(v -> {
|
containerView.setOnLongClickListener(v -> {
|
||||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
ClipData clip = ClipData.newPlainText("Public Key", publicKey);
|
ClipData clip = ClipData.newPlainText("Public Key", publicKey);
|
||||||
@ -96,28 +105,16 @@ public class ProfilePreference extends Preference {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
avatarView.setClipToOutline(true);
|
avatarView.setClipToOutline(true);
|
||||||
avatarView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
|
||||||
|
|
||||||
@Override
|
Drawable fallback = recipient.getFallbackContactPhotoDrawable(context, false);
|
||||||
public boolean onPreDraw() {
|
|
||||||
int width = avatarView.getWidth();
|
|
||||||
int height = avatarView.getHeight();
|
|
||||||
if (width == 0 || height == 0) return true;
|
|
||||||
avatarView.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
||||||
JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, publicKey.toLowerCase());
|
|
||||||
avatarView.setImageDrawable(identicon);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
GlideApp.with(getContext().getApplicationContext())
|
GlideApp.with(getContext().getApplicationContext())
|
||||||
.load(new ProfileContactPhoto(localAddress, String.valueOf(TextSecurePreferences.getProfileAvatarId(getContext()))))
|
.load(recipient.getContactPhoto())
|
||||||
.error(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(getContext(), getContext().getResources().getColor(R.color.grey_400)))
|
.fallback(fallback)
|
||||||
|
.error(fallback)
|
||||||
.circleCrop()
|
.circleCrop()
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
.into(avatarView);
|
.into(avatarView);
|
||||||
*/
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(profileName)) {
|
if (!TextUtils.isEmpty(profileName)) {
|
||||||
profileNameView.setText(profileName);
|
profileNameView.setText(profileName);
|
||||||
@ -127,6 +124,12 @@ public class ProfilePreference extends Preference {
|
|||||||
profileNumberView.setText(localAddress.toPhoneString());
|
profileNumberView.setText(localAddress.toPhoneString());
|
||||||
|
|
||||||
profileTagView.setVisibility(primaryDevicePublicKey == null ? View.GONE : View.VISIBLE);
|
profileTagView.setVisibility(primaryDevicePublicKey == null ? View.GONE : View.VISIBLE);
|
||||||
profileTagView.setText(R.string.activity_settings_secondary_device_tag);
|
if (primaryDevicePublicKey != null && ourDeviceWords == null) {
|
||||||
|
MnemonicCodec codec = new MnemonicCodec(MnemonicUtilities.getLanguageFileDirectory(context));
|
||||||
|
ourDeviceWords = MnemonicUtilities.getFirst3Words(codec, userHexEncodedPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
String tag = context.getResources().getString(R.string.activity_settings_linked_device_tag);
|
||||||
|
profileTagView.setText(String.format(tag, ourDeviceWords != null ? ourDeviceWords : "-"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,15 @@ import android.graphics.Color;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.EncodeHintType;
|
||||||
import com.google.zxing.WriterException;
|
import com.google.zxing.WriterException;
|
||||||
import com.google.zxing.common.BitMatrix;
|
import com.google.zxing.common.BitMatrix;
|
||||||
import com.google.zxing.qrcode.QRCodeWriter;
|
import com.google.zxing.qrcode.QRCodeWriter;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class QrCode {
|
public class QrCode {
|
||||||
|
|
||||||
public static final String TAG = QrCode.class.getSimpleName();
|
public static final String TAG = QrCode.class.getSimpleName();
|
||||||
@ -18,10 +21,12 @@ public class QrCode {
|
|||||||
public static @NonNull Bitmap create(String data) {
|
public static @NonNull Bitmap create(String data) {
|
||||||
return create(data, 1024);
|
return create(data, 1024);
|
||||||
}
|
}
|
||||||
|
public static @NonNull Bitmap create(String data, int size) { return create(data, size, 2); }
|
||||||
public static @NonNull Bitmap create(String data, int size) {
|
public static @NonNull Bitmap create(String data, int size, int margin) {
|
||||||
try {
|
try {
|
||||||
BitMatrix result = new QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, size, size);
|
HashMap<EncodeHintType, Integer> hintMap = new HashMap<>();
|
||||||
|
hintMap.put(EncodeHintType.MARGIN, margin);
|
||||||
|
BitMatrix result = new QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, size, size, hintMap);
|
||||||
Bitmap bitmap = Bitmap.createBitmap(result.getWidth(), result.getHeight(), Bitmap.Config.ARGB_8888);
|
Bitmap bitmap = Bitmap.createBitmap(result.getWidth(), result.getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
|
||||||
for (int y = 0; y < result.getHeight(); y++) {
|
for (int y = 0; y < result.getHeight(); y++) {
|
||||||
|
@ -26,6 +26,7 @@ import android.text.TextUtils;
|
|||||||
|
|
||||||
import com.annimon.stream.function.Consumer;
|
import com.annimon.stream.function.Consumer;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||||
@ -44,10 +45,13 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
|
|||||||
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
|
import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
|
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.loki.JazzIdenticonContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.loki.RecipientAvatarModifiedEvent;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
|
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
|
||||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
@ -275,6 +279,11 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
return isLocalNumber;
|
return isLocalNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isOurMasterDevice() {
|
||||||
|
String ourMasterDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||||
|
return ourMasterDevice != null && ourMasterDevice.equals(getAddress().serialize());
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized @Nullable Uri getContactUri() {
|
public synchronized @Nullable Uri getContactUri() {
|
||||||
return this.contactUri;
|
return this.contactUri;
|
||||||
}
|
}
|
||||||
@ -392,6 +401,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
EventBus.getDefault().post(new RecipientAvatarModifiedEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isProfileSharing() {
|
public synchronized boolean isProfileSharing() {
|
||||||
@ -455,15 +465,19 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized @NonNull FallbackContactPhoto getFallbackContactPhoto() {
|
public synchronized @NonNull FallbackContactPhoto getFallbackContactPhoto() {
|
||||||
if (isLocalNumber) return new ResourceContactPhoto(R.drawable.ic_note_to_self);
|
|
||||||
if (isResolving()) return new TransparentContactPhoto();
|
if (isResolving()) return new TransparentContactPhoto();
|
||||||
else if (isGroupRecipient()) return new ResourceContactPhoto(R.drawable.ic_group_white_24dp, R.drawable.ic_group_large);
|
else if (isGroupRecipient()) return new GeneratedContactPhoto(name, R.drawable.ic_profile_default);
|
||||||
else if (!TextUtils.isEmpty(name)) return new GeneratedContactPhoto(name, R.drawable.ic_profile_default);
|
else {
|
||||||
else return new ResourceContactPhoto(R.drawable.ic_profile_default, R.drawable.ic_person_large);
|
String currentUser = TextSecurePreferences.getLocalNumber(context);
|
||||||
|
String recipientAddress = address.serialize();
|
||||||
|
String primaryAddress = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||||
|
String profileAddress = (recipientAddress.equalsIgnoreCase(currentUser) && primaryAddress != null) ? primaryAddress : recipientAddress;
|
||||||
|
return new JazzIdenticonContactPhoto(profileAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized @Nullable ContactPhoto getContactPhoto() {
|
public synchronized @Nullable ContactPhoto getContactPhoto() {
|
||||||
if (isLocalNumber) return null;
|
if (isLocalNumber) return new ProfileContactPhoto(address, String.valueOf(TextSecurePreferences.getProfileAvatarId(context)));
|
||||||
else if (isGroupRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId);
|
else if (isGroupRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId);
|
||||||
else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0);
|
else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0);
|
||||||
else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar);
|
else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.thoughtcrime.securesms.registration;
|
package org.thoughtcrime.securesms.registration;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@ -8,6 +10,7 @@ import android.support.annotation.NonNull;
|
|||||||
import org.thoughtcrime.securesms.BaseActionBarActivity;
|
import org.thoughtcrime.securesms.BaseActionBarActivity;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
@ -23,6 +26,24 @@ public class WelcomeActivity extends BaseActionBarActivity {
|
|||||||
Analytics.Companion.getShared().track("Landing Screen Viewed");
|
Analytics.Companion.getShared().track("Landing Screen Viewed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (TextSecurePreferences.databaseResetFromUnpair(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.setDatabaseResetFromUnpair(getBaseContext(), false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||||
|
@ -76,6 +76,10 @@ public class KeyCachingService extends Service {
|
|||||||
|
|
||||||
private static MasterSecret masterSecret;
|
private static MasterSecret masterSecret;
|
||||||
|
|
||||||
|
// Loki - Caching
|
||||||
|
private static MasterSecret cachedSecret;
|
||||||
|
private static long cacheTime = 0;
|
||||||
|
|
||||||
public KeyCachingService() {}
|
public KeyCachingService() {}
|
||||||
|
|
||||||
public static synchronized boolean isLocked(Context context) {
|
public static synchronized boolean isLocked(Context context) {
|
||||||
@ -85,7 +89,13 @@ public class KeyCachingService extends Service {
|
|||||||
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
|
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
|
||||||
if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
|
if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
|
||||||
try {
|
try {
|
||||||
return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
// Loki - Cache the secret.
|
||||||
|
// Don't know if this will affect any other signal code :( but it makes it so we're not wasting time re-fetching the same secret from the database
|
||||||
|
if (cachedSecret == null || cacheTime < System.currentTimeMillis()) {
|
||||||
|
cachedSecret = MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||||
|
cacheTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5);
|
||||||
|
}
|
||||||
|
return cachedSecret;
|
||||||
} catch (InvalidPassphraseException e) {
|
} catch (InvalidPassphraseException e) {
|
||||||
Log.w("KeyCachingService", e);
|
Log.w("KeyCachingService", e);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package org.thoughtcrime.securesms.sms;
|
package org.thoughtcrime.securesms.sms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
@ -44,6 +45,7 @@ import org.thoughtcrime.securesms.jobs.SmsSendJob;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.loki.BackgroundMessage;
|
||||||
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
|
import org.thoughtcrime.securesms.loki.FriendRequestHandler;
|
||||||
import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt;
|
import org.thoughtcrime.securesms.loki.GeneralUtilitiesKt;
|
||||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||||
@ -58,7 +60,11 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
|
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
|
||||||
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
|
||||||
@ -123,11 +129,15 @@ public class MessageSender {
|
|||||||
// We don't call the message sender here directly and instead we just opt to create a specific job for the send
|
// We don't call the message sender here directly and instead we just opt to create a specific job for the send
|
||||||
// This is because calling message sender directly would cause the application to freeze in some cases as it was blocking the thread when waiting for a response from the send
|
// This is because calling message sender directly would cause the application to freeze in some cases as it was blocking the thread when waiting for a response from the send
|
||||||
public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) {
|
public static void sendBackgroundMessage(Context context, String contactHexEncodedPublicKey) {
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey));
|
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.create(contactHexEncodedPublicKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendBackgroundFriendRequest(Context context, String contactHexEncodedPublicKey, String messageBody) {
|
public static void sendBackgroundFriendRequest(Context context, String contactHexEncodedPublicKey, String messageBody) {
|
||||||
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(contactHexEncodedPublicKey, messageBody, true));
|
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createFriendRequest(contactHexEncodedPublicKey, messageBody)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendUnpairRequest(Context context, String contactHexEncodedPublicKey) {
|
||||||
|
ApplicationContext.getInstance(context).getJobManager().add(new PushBackgroundMessageSendJob(BackgroundMessage.createUnpairingRequest(contactHexEncodedPublicKey)));
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import java.io.File;
|
|||||||
|
|
||||||
public class FileProviderUtil {
|
public class FileProviderUtil {
|
||||||
|
|
||||||
private static final String AUTHORITY = "org.thoughtcrime.securesms.fileprovider";
|
private static final String AUTHORITY = "network.loki.securesms.fileprovider";
|
||||||
|
|
||||||
public static Uri getUriFor(@NonNull Context context, @NonNull File file) {
|
public static Uri getUriFor(@NonNull Context context, @NonNull File file) {
|
||||||
if (Build.VERSION.SDK_INT >= 24) return FileProvider.getUriForFile(context, AUTHORITY, file);
|
if (Build.VERSION.SDK_INT >= 24) return FileProvider.getUriForFile(context, AUTHORITY, file);
|
||||||
|
@ -121,6 +121,7 @@ public class TextSecurePreferences {
|
|||||||
private static final String PROFILE_KEY_PREF = "pref_profile_key";
|
private static final String PROFILE_KEY_PREF = "pref_profile_key";
|
||||||
private static final String PROFILE_NAME_PREF = "pref_profile_name";
|
private static final String PROFILE_NAME_PREF = "pref_profile_name";
|
||||||
private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id";
|
private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id";
|
||||||
|
private static final String PROFILE_AVATAR_URL_PREF = "pref_profile_avatar_url";
|
||||||
public static final String READ_RECEIPTS_PREF = "pref_read_receipts";
|
public static final String READ_RECEIPTS_PREF = "pref_read_receipts";
|
||||||
public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard";
|
public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard";
|
||||||
private static final String UNAUTHORIZED_RECEIVED = "pref_unauthorized_received";
|
private static final String UNAUTHORIZED_RECEIVED = "pref_unauthorized_received";
|
||||||
@ -401,6 +402,14 @@ public class TextSecurePreferences {
|
|||||||
return getIntegerPreference(context, PROFILE_AVATAR_ID_PREF, 0);
|
return getIntegerPreference(context, PROFILE_AVATAR_ID_PREF, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setProfileAvatarUrl(Context context, String url) {
|
||||||
|
setStringPreference(context, PROFILE_AVATAR_URL_PREF, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getProfileAvatarUrl(Context context) {
|
||||||
|
return getStringPreference(context, PROFILE_AVATAR_URL_PREF, null);
|
||||||
|
}
|
||||||
|
|
||||||
public static int getNotificationPriority(Context context) {
|
public static int getNotificationPriority(Context context) {
|
||||||
return Integer.valueOf(getStringPreference(context, NOTIFICATION_PRIORITY_PREF, String.valueOf(NotificationCompat.PRIORITY_HIGH)));
|
return Integer.valueOf(getStringPreference(context, NOTIFICATION_PRIORITY_PREF, String.valueOf(NotificationCompat.PRIORITY_HIGH)));
|
||||||
}
|
}
|
||||||
@ -1185,5 +1194,35 @@ public class TextSecurePreferences {
|
|||||||
public static void setMasterHexEncodedPublicKey(Context context, String masterHexEncodedPublicKey) {
|
public static void setMasterHexEncodedPublicKey(Context context, String masterHexEncodedPublicKey) {
|
||||||
setStringPreference(context, "master_hex_encoded_public_key", masterHexEncodedPublicKey.toLowerCase());
|
setStringPreference(context, "master_hex_encoded_public_key", masterHexEncodedPublicKey.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setResetDatabase(Context context, boolean resetDatabase) {
|
||||||
|
// We do it this way so that it gets persisted in storage straight away
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("database_reset", resetDatabase).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean resetDatabase(Context context) {
|
||||||
|
return getBooleanPreference(context, "database_reset", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDatabaseResetFromUnpair(Context context, boolean value) {
|
||||||
|
// We do it this way so that it gets persisted in storage straight away
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("database_reset_unpair", value).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean databaseResetFromUnpair(Context context) {
|
||||||
|
return getBooleanPreference(context, "database_reset_unpair", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setNeedsRevocationCheck(Context context, boolean needsCheck) {
|
||||||
|
setBooleanPreference(context, "needs_revocation", needsCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean needsRevocationCheck(Context context) {
|
||||||
|
return getBooleanPreference(context, "needs_revocation", false);
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
public static void clearAll(Context context) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user