Merge branch 'dev'

This commit is contained in:
nielsandriesse 2020-04-17 16:00:54 +10:00
commit 18b95c7f8a
69 changed files with 1268 additions and 207 deletions

View File

@ -23,9 +23,13 @@ Describe here the issue that you are experiencing.
- list the steps - list the steps
- that reproduce the bug - that reproduce the bug
**Actual result:** Describe here what happens after you run the steps above (i.e. the buggy behaviour) **Actual result:**
**Expected result:** Describe here what should happen after you run the steps above (i.e. what would be the correct behaviour) Describe here what happens after you run the steps above (i.e. the buggy behaviour)
**Expected result:**
Describe here what should happen after you run the steps above (i.e. what would be the correct behaviour)
### Screenshots ### Screenshots
<!-- you can drag and drop images below --> <!-- you can drag and drop images below -->
@ -33,10 +37,11 @@ Describe here the issue that you are experiencing.
### Device info ### Device info
<!-- replace the examples with your info --> <!-- replace the examples with your info -->
**Device:** Manufacturer Model XVI **Device:** Manufacturer Model XVI
**Android version:** 0.0.0 **Android version:** 0.0.0
**Session version:** 0.0.0 **Session version:** 0.0.0
### Link to debug log

View File

@ -23,7 +23,7 @@ If applicable, add screenshots or logs to help explain your problem.
- Device: [e.g. Samsung Galaxy S8] - Device: [e.g. Samsung Galaxy S8]
- OS: [e.g. Android Pie] - OS: [e.g. Android Pie]
- Version of Loki Messenger or latest commit hash - Version of Session or latest commit hash
**Additional context** **Additional context**

View File

@ -47,6 +47,7 @@
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -112,6 +113,9 @@
android:name="org.thoughtcrime.securesms.loki.redesign.activities.DisplayNameActivity" android:name="org.thoughtcrime.securesms.loki.redesign.activities.DisplayNameActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.thoughtcrime.securesms.loki.redesign.activities.PNModeActivity"
android:screenOrientation="portrait" />
<activity <activity
android:name="org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity" android:name="org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
@ -509,6 +513,14 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true" android:exported="true"
android:theme="@style/TextSecure.LightNoActionBar" /> android:theme="@style/TextSecure.LightNoActionBar" />
<service
android:name="org.thoughtcrime.securesms.service.PushNotificationService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service <service
android:name="org.thoughtcrime.securesms.service.WebRtcCallService" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"
android:enabled="true" /> android:enabled="true" />

66
BUILDING.md Normal file
View File

@ -0,0 +1,66 @@
Building Session
===============
Basics
------
Session uses [Gradle](http://gradle.org) to build the project and to maintain
dependencies. However, you needn't install it yourself; the
"gradle wrapper" `gradlew`, mentioned below, will do that for you.
Building Session
---------------
The following steps should help you (re)build Session from the command line.
1. Checkout the session-android project source with the command:
git clone https://github.com/loki-project/session-android.git
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed.
3. Ensure that the following packages are installed from the Android SDK manager:
* Android SDK Build Tools (see buildToolsVersion in build.gradle)
* SDK Platform (All API levels)
* Android Support Repository
* Google Repository
4. Create a local.properties file at the root of your source checkout and add an sdk.dir entry to it. For example:
sdk.dir=/Application/android-sdk-macosx
5. Using Java 8
6. Execute Gradle:
./gradlew build
Visual assets
----------------------
Source assets tend to be large binary blobs, which are best stored outside of git repositories. Some source files are SVGs that can be auto-colored and sized using a tool like [android-res-utils](https://github.com/sebkur/android-res-utils).
Sample command for generating our audio placeholder image:
```bash
pngs_from_svg.py ic_audio.svg /path/to/Session/res/ 150 --color #000 --opacity 0.54 --suffix _light
pngs_from_svg.py ic_audio.svg /path/to/Session/res/ 150 --color #fff --opacity 1.00 --suffix _light
```
Setting up a development environment
------------------------------------
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
1. Install Android Studio.
2. Open Android Studio. On a new installation, the Quickstart panel will appear. If you have open projects, close them using "File > Close Project" to see the Quickstart panel.
3. From the Quickstart panel, choose "Configure" then "SDK Manager".
4. In the SDK Tools tab of the SDK Manager, make sure that the "Android Support Repository" is installed, and that the latest "Android SDK build-tools" are installed. Click "OK" to return to the Quickstart panel.
5. From the Quickstart panel, choose "Checkout from Version Control" then "git".
6. Paste the URL for the session-android project when prompted (https://github.com/loki-project/session-android.git).
7. Android studio should detect the presence of a project file and ask you whether to open it. Click "yes".
9. Default config options should be good enough.
9. Project initialisation and build should proceed.
Contributing code
-----------------
Code contributions should be sent via github as pull requests, from feature branches [as explained here](https://help.github.com/articles/using-pull-requests).

View File

@ -6,13 +6,13 @@
## Summary ## Summary
Session integrates directly with [Loki Service Nodes](https://lokidocs.com/ServiceNodes/SNOverview/), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper). Session integrates directly with [Loki Service Nodes](https://lokidocs.com/ServiceNodes/SNOverview/), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users' IP addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper).
![AndroidSession](https://i.imgur.com/0YC9TyI.png) ![AndroidSession](https://i.imgur.com/0YC9TyI.png)
## Want to Contribute? Found a Bug or Have a feature request? ## Want to Contribute? Found a Bug or Have a feature request?
Please search for any [existing issues](https://github.com/loki-project/session-android/issues) that describe your bugs in order to avoid duplicate submissions. Submissions can be made by making a pull request to our development branch. If you don't know where to start contributing , try reading the Github issues page for ideas. Please search for any [existing issues](https://github.com/loki-project/session-android/issues) that describe your bugs in order to avoid duplicate submissions. Submissions can be made by making a pull request to our development branch. If you don't know where to start contributing, try reading the Github issues page for ideas.
## Build instruction ## Build instruction

View File

@ -6,6 +6,8 @@ buildscript {
ext.kovenant_version = "3.3.0" ext.kovenant_version = "3.3.0"
ext.identicon_version = "v11" ext.identicon_version = "v11"
ext.rss_parser_version = "2.0.4" ext.rss_parser_version = "2.0.4"
ext.google_services_version = "4.3.3"
ext.firebase_messaging_version = "18.0.0"
repositories { repositories {
mavenLocal() mavenLocal()
@ -16,6 +18,7 @@ buildscript {
classpath "com.android.tools.build:gradle:$gradle_version" classpath "com.android.tools.build:gradle:$gradle_version"
classpath files('libs/gradle-witness.jar') classpath files('libs/gradle-witness.jar')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.gms:google-services:$google_services_version"
} }
} }
@ -24,6 +27,7 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'witness' apply plugin: 'witness'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
repositories { repositories {
mavenLocal() mavenLocal()
@ -87,6 +91,8 @@ dependencies {
implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:common-java8:1.1.1' implementation 'android.arch.lifecycle:common-java8:1.1.1'
implementation "com.google.firebase:firebase-messaging:$firebase_messaging_version"
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1' implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
@ -184,8 +190,8 @@ dependencies {
implementation "com.github.ybq:Android-SpinKit:1.4.0" implementation "com.github.ybq:Android-SpinKit:1.4.0"
} }
def canonicalVersionCode = 48 def canonicalVersionCode = 49
def canonicalVersionName = "1.0.11" def canonicalVersionName = "1.1.0"
def postFixSize = 10 def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1, def abiPostFix = ['armeabi-v7a' : 1,

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid
android:color="@color/pn_option_background" />
<stroke
android:color="@color/pn_option_border"
android:width="@dimen/border_thickness" />
<corners android:radius="@dimen/pn_option_corner_radius" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/pn_option_background_selected" />
<item android:drawable="@drawable/pn_option_background" />
</transition>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/pn_option_background" />
<item android:drawable="@drawable/pn_option_background_selected" />
</transition>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid
android:color="@color/pn_option_background" />
<stroke
android:color="@color/accent"
android:width="@dimen/border_thickness" />
<corners android:radius="@dimen/pn_option_corner_radius" />
</shape>

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/default_session_background"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/very_large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="10dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_explanation" />
<LinearLayout
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/activity_pn_mode_fcm_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_fcm_option_explanation" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/activity_pn_mode_recommended_option_tag" />
</LinearLayout>
<LinearLayout
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/activity_pn_mode_background_polling_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_background_polling_option_explanation" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/MediumProminentFilledButton"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/medium_spacing"
android:text="Continue" />
</LinearLayout>

View File

@ -41,6 +41,6 @@
android:textColor="@color/text" android:textColor="@color/text"
android:alpha="0.6" android:alpha="0.6"
android:textAlignment="center" android:textAlignment="center"
android:text="Open groups can be joined by anyone and do not provide full metadata protection" /> android:text="Open groups can be joined by anyone and do not provide full privacy protection" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,118 @@
<?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"
android:gravity="center_horizontal"
app:behavior_hideable="true"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
android:background="@drawable/default_bottom_sheet_background_inset">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/very_large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="6dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_explanation" />
<LinearLayout
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/sheet_pn_mode_fcm_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_fcm_option_explanation" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/sheet_pn_mode_recommended_option_tag" />
</LinearLayout>
<LinearLayout
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/sheet_pn_mode_background_polling_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_background_polling_option_explanation" />
</LinearLayout>
<Button
style="@style/MediumProminentOutlineButton"
android:id="@+id/confirmButton"
android:layout_width="240dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/medium_spacing"
android:text="@string/sheet_pn_mode_confirm_button_title" />
<Button
style="@style/MediumUnimportantOutlineButton"
android:id="@+id/skipButton"
android:layout_width="240dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginBottom="@dimen/medium_spacing"
android:text="@string/sheet_pn_mode_skip_button_title" />
</LinearLayout>

View File

@ -32,7 +32,7 @@
android:textColor="@color/text" android:textColor="@color/text"
android:alpha="0.6" android:alpha="0.6"
android:textAlignment="center" android:textAlignment="center"
android:text="Closed groups are end-to-end encrypted group chats for up to 10 members. They provide the same privacy protections as one-on-one sessions." /> android:text="Closed groups support up to 10 members and provide the same privacy protections as one-on-one sessions." />
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/default_session_background"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="6dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/small_font_size"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_explanation" />
<LinearLayout
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/activity_pn_mode_fcm_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_fcm_option_explanation" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/activity_pn_mode_recommended_option_tag" />
</LinearLayout>
<LinearLayout
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/activity_pn_mode_background_polling_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/activity_pn_mode_background_polling_option_explanation" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
style="@style/MediumProminentFilledButton"
android:id="@+id/registerButton"
android:layout_width="match_parent"
android:layout_height="@dimen/medium_button_height"
android:layout_marginLeft="@dimen/massive_spacing"
android:layout_marginRight="@dimen/massive_spacing"
android:layout_marginBottom="@dimen/medium_spacing"
android:text="Continue" />
</LinearLayout>

View File

@ -41,6 +41,6 @@
android:textColor="@color/text" android:textColor="@color/text"
android:alpha="0.6" android:alpha="0.6"
android:textAlignment="center" android:textAlignment="center"
android:text="Open groups can be joined by anyone and do not provide full metadata protection" /> android:text="Open groups can be joined by anyone and do not provide full privacy protection" />
</LinearLayout> </LinearLayout>

View File

@ -58,6 +58,6 @@
android:textColor="@color/text" android:textColor="@color/text"
android:alpha="0.6" android:alpha="0.6"
android:textAlignment="center" android:textAlignment="center"
android:text="Open groups can be joined by anyone and do not provide full metadata protection" /> android:text="Open groups can be joined by anyone and do not provide full privacy protection" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,118 @@
<?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"
android:gravity="center_horizontal"
app:behavior_hideable="true"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
android:background="@drawable/default_bottom_sheet_background_inset">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/medium_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/large_font_size"
android:textStyle="bold"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="6dp"
android:layout_marginRight="@dimen/very_large_spacing"
android:textSize="@dimen/small_font_size"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_explanation" />
<LinearLayout
android:id="@+id/fcmOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/sheet_pn_mode_fcm_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_fcm_option_explanation" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/accent"
android:textStyle="bold"
android:text="@string/sheet_pn_mode_recommended_option_tag" />
</LinearLayout>
<LinearLayout
android:id="@+id/backgroundPollingOptionView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/very_large_spacing"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginRight="@dimen/very_large_spacing"
android:padding="12dp"
android:orientation="vertical"
android:background="@drawable/pn_option_background">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_font_size"
android:textColor="@color/text"
android:textStyle="bold"
android:text="@string/sheet_pn_mode_background_polling_option_title" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
android:text="@string/sheet_pn_mode_background_polling_option_explanation" />
</LinearLayout>
<Button
style="@style/MediumProminentOutlineButton"
android:id="@+id/confirmButton"
android:layout_width="240dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="10dp"
android:text="@string/sheet_pn_mode_confirm_button_title" />
<Button
style="@style/MediumUnimportantOutlineButton"
android:id="@+id/skipButton"
android:layout_width="240dp"
android:layout_height="@dimen/medium_button_height"
android:layout_marginTop="@dimen/small_spacing"
android:layout_marginBottom="@dimen/medium_spacing"
android:text="@string/sheet_pn_mode_skip_button_title" />
</LinearLayout>

View File

@ -26,6 +26,8 @@
<color name="sent_message_background">#3F4146</color> <color name="sent_message_background">#3F4146</color>
<color name="quote_not_found_background">#99FFFFFF</color> <color name="quote_not_found_background">#99FFFFFF</color>
<color name="new_conversation_button_collapsed_background">#1F1F1F</color> <color name="new_conversation_button_collapsed_background">#1F1F1F</color>
<color name="pn_option_background">#1B1B1B</color>
<color name="pn_option_border">#212121</color>
<!-- Session --> <!-- Session -->
<!-- Loki --> <!-- Loki -->

View File

@ -32,6 +32,7 @@
<dimen name="setting_button_height">56dp</dimen> <dimen name="setting_button_height">56dp</dimen>
<dimen name="dialog_corner_radius">8dp</dimen> <dimen name="dialog_corner_radius">8dp</dimen>
<dimen name="dialog_button_corner_radius">4dp</dimen> <dimen name="dialog_button_corner_radius">4dp</dimen>
<dimen name="pn_option_corner_radius">8dp</dimen>
<!-- Distances --> <!-- Distances -->
<dimen name="small_spacing">8dp</dimen> <dimen name="small_spacing">8dp</dimen>

View File

@ -1670,5 +1670,26 @@
<string name="activity_home_leave_group_dialog_message">Are you sure you want to leave this group?</string> <string name="activity_home_leave_group_dialog_message">Are you sure you want to leave this group?</string>
<string name="activity_home_delete_conversation_dialog_message">Are you sure you want to delete this conversation?</string> <string name="activity_home_delete_conversation_dialog_message">Are you sure you want to delete this conversation?</string>
<string name="activity_home_conversation_deleted_message">Conversation deleted</string> <string name="activity_home_conversation_deleted_message">Conversation deleted</string>
<string name="activity_pn_mode_title">Push Notifications</string>
<string name="activity_pn_mode_explanation">There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.</string>
<string name="activity_pn_mode_fcm_option_title">Firebase Cloud Messaging</string>
<string name="activity_pn_mode_fcm_option_explanation">Session will use the Firebase Cloud Messaging service to receive push notifications. Youll be notified of new messages reliably and immediately. Using FCM means that this device will communicate directly with Googles servers to retrieve push notifications, which will expose your IP address to Google. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.</string>
<string name="activity_pn_mode_background_polling_option_title">Background Polling</string>
<string name="activity_pn_mode_background_polling_option_explanation">Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.</string>
<string name="activity_pn_mode_recommended_option_tag">Recommended</string>
<string name="activity_pn_mode_no_option_picked_dialog_title">Please Pick an Option</string>
<string name="sheet_pn_mode_title">Push Notifications</string>
<string name="sheet_pn_mode_explanation">Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose.</string>
<string name="sheet_pn_mode_fcm_option_title">Firebase Cloud Messaging</string>
<string name="sheet_pn_mode_fcm_option_explanation">Session will use the Firebase Cloud Messaging service to receive push notifications. Youll be notified of new messages reliably and immediately. Using FCM means that this device will communicate directly with Googles servers to retrieve push notifications, which will expose your IP address to Google. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.</string>
<string name="sheet_pn_mode_background_polling_option_title">Background Polling</string>
<string name="sheet_pn_mode_background_polling_option_explanation">Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.</string>
<string name="sheet_pn_mode_recommended_option_tag">Recommended</string>
<string name="sheet_pn_mode_no_option_picked_dialog_title">Please Pick an Option</string>
<string name="sheet_pn_mode_confirm_button_title">Confirm</string>
<string name="sheet_pn_mode_skip_button_title">Skip</string>
<string name="preferences_notifications_strategy_category_title">Notification Strategy</string>
<string name="preferences_notifications_use_fcm_option_title">Use FCM</string>
<string name="preferences_notifications_use_fcm_option_explanation">Using Firebase Cloud Messaging allows for more reliable push notifications, but exposes your IP to Google.</string>
</resources> </resources>

View File

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<domain-config cleartextTrafficPermitted="true"> <domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">imaginary.stream</domain> <domain includeSubdomains="true">149.56.148.124</domain>
<domain includeSubdomains="true">chat.getsession.org</domain>
<domain includeSubdomains="true">storage.seed1.loki.network</domain> <domain includeSubdomains="true">storage.seed1.loki.network</domain>
<domain includeSubdomains="true">storage.seed2.loki.network</domain> <domain includeSubdomains="true">storage.seed2.loki.network</domain>
<domain includeSubdomains="true">public.loki.foundation:22023</domain> <domain includeSubdomains="true">public.loki.foundation</domain>
<domain includeSubdomains="true">file-dev.lokinet.org</domain> <domain includeSubdomains="true">file-dev.lokinet.org</domain>
<domain includeSubdomains="true">127.0.0.1</domain> <domain includeSubdomains="true">127.0.0.1</domain>
</domain-config> </domain-config>

View File

@ -21,6 +21,19 @@
<PreferenceCategory android:layout="@layout/preference_divider" /> <PreferenceCategory android:layout="@layout/preference_divider" />
<PreferenceCategory android:title="@string/preferences_notifications_strategy_category_title">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:dependency="pref_key_enable_notifications"
android:key="pref_key_use_fcm"
android:title="@string/preferences_notifications_use_fcm_option_title"
android:summary="@string/preferences_notifications_use_fcm_option_explanation"
android:defaultValue="false" />
</PreferenceCategory>
<PreferenceCategory android:layout="@layout/preference_divider" />
<PreferenceCategory android:title="@string/activity_notification_settings_style_section_title"> <PreferenceCategory android:title="@string/activity_notification_settings_style_section_title">
<org.thoughtcrime.securesms.preferences.widgets.SignalPreference <org.thoughtcrime.securesms.preferences.widgets.SignalPreference

View File

@ -30,6 +30,8 @@ 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;
import com.google.firebase.iid.FirebaseInstanceId;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.signal.aesgcmprovider.AesGcmProvider; import org.signal.aesgcmprovider.AesGcmProvider;
@ -61,6 +63,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.loki.LokiPublicChatManager; import org.thoughtcrime.securesms.loki.LokiPublicChatManager;
import org.thoughtcrime.securesms.loki.LokiPushNotificationManager;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity; import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundOpenGroupPollWorker; import org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundOpenGroupPollWorker;
@ -92,14 +95,17 @@ import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol; import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI;
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.LokiPoller; import org.whispersystems.signalservice.loki.api.LokiPoller;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; import org.whispersystems.signalservice.loki.api.LokiSwarmAPI;
import org.whispersystems.signalservice.loki.api.LokiRSSFeed; import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChatAPI;
import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeed;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -180,6 +186,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
ProcessLifecycleOwner.get().getLifecycle().addObserver(this); ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// Loki - Set up P2P API if needed // Loki - Set up P2P API if needed
setUpP2PAPI(); setUpP2PAPI();
// Loki - Set up push notification acknowledgement
LokiPushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
// Loki - Update device mappings // Loki - Update device mappings
if (setUpStorageAPIIfNeeded()) { if (setUpStorageAPIIfNeeded()) {
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
@ -197,6 +205,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
// Loki - Set up public chat manager // Loki - Set up public chat manager
lokiPublicChatManager = new LokiPublicChatManager(this); lokiPublicChatManager = new LokiPublicChatManager(this);
updatePublicChatProfilePictureIfNeeded(); updatePublicChatProfilePictureIfNeeded();
registerForFCMIfNeeded(false);
} }
@Override @Override
@ -453,6 +462,24 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
}, this); }, this);
} }
public void registerForFCMIfNeeded(Boolean force) {
Context context = this;
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task.getException());
return;
}
String token = task.getResult().getToken();
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
if (userHexEncodedPublicKey == null) return;
if (TextSecurePreferences.isUsingFCM(this)) {
LokiPushNotificationManager.register(token, userHexEncodedPublicKey, context, force);
} else {
LokiPushNotificationManager.unregister(token, context);
}
});
}
@Override @Override
public void ping(@NotNull String s) { public void ping(@NotNull String s) {
// TODO: Implement // TODO: Implement
@ -464,7 +491,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
if (userHexEncodedPublicKey == null) return; if (userHexEncodedPublicKey == null) return;
LokiAPIDatabase lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabase lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(this);
Context context = this; Context context = this;
lokiPoller = new LokiPoller(userHexEncodedPublicKey, lokiAPIDatabase, broadcaster, protos -> { LokiSwarmAPI.Companion.configureIfNeeded(lokiAPIDatabase);
LokiAPI.Companion.configureIfNeeded(userHexEncodedPublicKey, lokiAPIDatabase, broadcaster);
lokiPoller = new LokiPoller(userHexEncodedPublicKey, lokiAPIDatabase, protos -> {
for (SignalServiceProtos.Envelope proto : protos) { for (SignalServiceProtos.Envelope proto : protos) {
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto)); new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto));
} }

View File

@ -52,7 +52,7 @@ 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.crypto.MnemonicCodec; import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
import org.whispersystems.signalservice.loki.utilities.SerializationKt; import org.whispersystems.signalservice.loki.utilities.HexEncodingKt;
import java.io.File; import java.io.File;
@ -341,7 +341,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
try { try {
String hexEncodedSeed = IdentityKeyUtil.retrieve(getContext(), IdentityKeyUtil.lokiSeedKey); String hexEncodedSeed = IdentityKeyUtil.retrieve(getContext(), IdentityKeyUtil.lokiSeedKey);
if (hexEncodedSeed == null) { if (hexEncodedSeed == null) {
hexEncodedSeed = SerializationKt.getHexEncodedPrivateKey(IdentityKeyUtil.getIdentityKeyPair(getContext())); // Legacy account hexEncodedSeed = HexEncodingKt.getHexEncodedPrivateKey(IdentityKeyUtil.getIdentityKeyPair(getContext())); // Legacy account
} }
String seed = new MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.Companion.getEnglish()); String seed = new MnemonicCodec(languageFileDirectory).encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.Companion.getEnglish());
new AlertDialog.Builder(getContext()) new AlertDialog.Builder(getContext())

View File

@ -58,8 +58,8 @@ 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.LokiDotNetAPI;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI; import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChatAPI;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;

View File

@ -31,7 +31,7 @@ import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import java.util.List; import java.util.List;

View File

@ -11,8 +11,8 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.TypingSendJob; import org.thoughtcrime.securesms.jobs.TypingSendJob;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI; import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;

View File

@ -228,10 +228,10 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.loki.api.DeviceLink; import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink;
import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.Mention; import org.whispersystems.signalservice.loki.messaging.Mention;
@ -317,7 +317,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private Button makeDefaultSmsButton; private Button makeDefaultSmsButton;
private Button registerButton; private Button registerButton;
private InputAwareLayout container; private InputAwareLayout container;
private View composePanel;
protected Stub<ReminderView> reminderView; protected Stub<ReminderView> reminderView;
private Stub<UnverifiedBannerView> unverifiedBannerView; private Stub<UnverifiedBannerView> unverifiedBannerView;
private Stub<GroupShareProfileView> groupShareProfileView; private Stub<GroupShareProfileView> groupShareProfileView;
@ -552,7 +551,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateTitleTextView(recipient); updateTitleTextView(recipient);
updateSubtitleTextView(); updateSubtitleTextView();
setActionBarColor(recipient.getColor()); setActionBarColor(recipient.getColor());
setBlockedUserState(recipient, isSecureText, isDefaultSms); updateInputUI(recipient, isSecureText, isDefaultSms);
setGroupShareProfileReminder(recipient); setGroupShareProfileReminder(recipient);
calculateCharactersRemaining(); calculateCharactersRemaining();
@ -645,7 +644,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateTitleTextView(recipient); updateTitleTextView(recipient);
updateSubtitleTextView(); updateSubtitleTextView();
NotificationChannels.updateContactChannelName(this, recipient); NotificationChannels.updateContactChannelName(this, recipient);
setBlockedUserState(recipient, isSecureText, isDefaultSms); updateInputUI(recipient, isSecureText, isDefaultSms);
supportInvalidateOptionsMenu(); supportInvalidateOptionsMenu();
break; break;
case TAKE_PHOTO: case TAKE_PHOTO:
@ -858,6 +857,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
searchViewModel.onSearchClosed(); searchViewModel.onSearchClosed();
searchNav.setVisibility(View.GONE); searchNav.setVisibility(View.GONE);
inputPanel.setVisibility(View.VISIBLE); inputPanel.setVisibility(View.VISIBLE);
updateInputUI(recipient, isSecureText, isDefaultSms);
fragment.onSearchQueryUpdated(null); fragment.onSearchQueryUpdated(null);
invalidateOptionsMenu(); invalidateOptionsMenu();
return true; return true;
@ -1343,7 +1343,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
calculateCharactersRemaining(); calculateCharactersRemaining();
supportInvalidateOptionsMenu(); supportInvalidateOptionsMenu();
setBlockedUserState(recipient, isSecureText, isDefaultSms); updateInputUI(recipient, isSecureText, isDefaultSms);
} }
///// Initializers ///// Initializers
@ -1627,7 +1627,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
unblockButton = ViewUtil.findById(this, R.id.unblock_button); unblockButton = ViewUtil.findById(this, R.id.unblock_button);
makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button); makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button);
registerButton = ViewUtil.findById(this, R.id.register_button); registerButton = ViewUtil.findById(this, R.id.register_button);
composePanel = ViewUtil.findById(this, R.id.bottom_panel);
container = ViewUtil.findById(this, R.id.layout_container); container = ViewUtil.findById(this, R.id.layout_container);
reminderView = ViewUtil.findStubById(this, R.id.reminder_stub); reminderView = ViewUtil.findStubById(this, R.id.reminder_stub);
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub); unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
@ -1835,7 +1834,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateTitleTextView(recipient); updateTitleTextView(recipient);
updateSubtitleTextView(); updateSubtitleTextView();
// titleView.setVerified(identityRecords.isVerified()); // titleView.setVerified(identityRecords.isVerified());
setBlockedUserState(recipient, isSecureText, isDefaultSms); updateInputUI(recipient, isSecureText, isDefaultSms);
setActionBarColor(recipient.getColor()); setActionBarColor(recipient.getColor());
setGroupShareProfileReminder(recipient); setGroupShareProfileReminder(recipient);
updateReminders(recipient.hasSeenInviteReminder()); updateReminders(recipient.hasSeenInviteReminder());
@ -2043,29 +2042,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
setStatusBarColor(getResources().getColor(R.color.action_bar_background)); setStatusBarColor(getResources().getColor(R.color.action_bar_background));
} }
private void setBlockedUserState(Recipient recipient, boolean isSecureText, boolean isDefaultSms) { // FIXME: This name is confusing because we also have updateInputPanel and setInputPanelEnabled
if (recipient.isGroupRecipient() && recipient.getAddress().isRSSFeed()) { private void updateInputUI(Recipient recipient, boolean isSecureText, boolean isDefaultSms) {
if (recipient.isGroupRecipient() && !isActiveGroup()) {
unblockButton.setVisibility(View.GONE); unblockButton.setVisibility(View.GONE);
composePanel.setVisibility(View.GONE); inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE); makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE); registerButton.setVisibility(View.GONE);
} else if (recipient.isBlocked()) { } else if (recipient.isBlocked()) {
unblockButton.setVisibility(View.VISIBLE); unblockButton.setVisibility(View.VISIBLE);
composePanel.setVisibility(View.GONE); inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE); makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE); registerButton.setVisibility(View.GONE);
} else if (!isSecureText && isPushGroupConversation()) { } else if (!isSecureText && isPushGroupConversation()) {
unblockButton.setVisibility(View.GONE); unblockButton.setVisibility(View.GONE);
composePanel.setVisibility(View.GONE); inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE); makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.VISIBLE); registerButton.setVisibility(View.VISIBLE);
} else if (!isSecureText && !isDefaultSms) { } else if (!isSecureText && !isDefaultSms) {
unblockButton.setVisibility(View.GONE); unblockButton.setVisibility(View.GONE);
composePanel.setVisibility(View.GONE); inputPanel.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.VISIBLE); makeDefaultSmsButton.setVisibility(View.VISIBLE);
registerButton.setVisibility(View.GONE); registerButton.setVisibility(View.GONE);
} else { } else {
composePanel.setVisibility(View.VISIBLE); inputPanel.setVisibility(View.VISIBLE);
unblockButton.setVisibility(View.GONE); unblockButton.setVisibility(View.GONE);
makeDefaultSmsButton.setVisibility(View.GONE); makeDefaultSmsButton.setVisibility(View.GONE);
registerButton.setVisibility(View.GONE); registerButton.setVisibility(View.GONE);
@ -2125,7 +2125,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private boolean isActiveGroup() { private boolean isActiveGroup() {
if (!isGroupConversation()) return false; if (!isGroupConversation() || recipient.getAddress().isRSSFeed()) return false;
Optional<GroupRecord> record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()); Optional<GroupRecord> record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString());
return record.isPresent() && record.get().isActive(); return record.isPresent() && record.get().isActive();
@ -2314,7 +2314,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
String hint = enabled ? "Message" : "Pending session request"; String hint = enabled ? "Message" : "Pending session request";
inputPanel.setHint(hint); inputPanel.setHint(hint);
inputPanel.setEnabled(enabled); inputPanel.setEnabled(enabled);
if (enabled) { if (enabled && inputPanel.getVisibility() == View.VISIBLE) {
inputPanel.composeText.requestFocus(); inputPanel.composeText.requestFocus();
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(inputPanel.composeText, 0); inputMethodManager.showSoftInput(inputPanel.composeText, 0);
@ -2939,6 +2939,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void handleReplyMessage(MessageRecord messageRecord) { public void handleReplyMessage(MessageRecord messageRecord) {
if (recipient.isGroupRecipient() && !isActiveGroup()) { return; }
Recipient author; Recipient author;
if (messageRecord.isOutgoing()) { if (messageRecord.isOutgoing()) {
@ -3152,7 +3154,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
subtitleTextView.setVisibility(View.GONE); subtitleTextView.setVisibility(View.GONE);
} }
} else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) { } else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) {
subtitleTextView.setText(recipient.getAddress().toString()); String ourMasterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this);
String hexEncodedPublicKey = (recipient.isLocalNumber() && ourMasterHexEncodedPublicKey != null) ? ourMasterHexEncodedPublicKey : recipient.getAddress().toPhoneString();
subtitleTextView.setText(hexEncodedPublicKey);
} else { } else {
subtitleTextView.setVisibility(View.GONE); subtitleTextView.setVisibility(View.GONE);
} }

View File

@ -101,8 +101,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChatAPI;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@ -112,8 +112,8 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.util.views.Stub;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChatAPI;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;

View File

@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import java.io.File; import java.io.File;

View File

@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities; import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities;
import org.thoughtcrime.securesms.util.AsyncLoader; import org.thoughtcrime.securesms.util.AsyncLoader;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec; import org.whispersystems.signalservice.loki.crypto.MnemonicCodec;
import java.io.File; import java.io.File;

View File

@ -36,7 +36,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.util.Collections; import java.util.Collections;
@ -137,49 +137,57 @@ public class GroupMessageProcessor {
GroupDatabase database = DatabaseFactory.getGroupDatabase(context); GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
String id = GroupUtil.getEncodedId(group); String id = GroupUtil.getEncodedId(group);
// Only update group if admin sent the message String ourHexEncodedPublicKey = getMasterHexEncodedPublicKey(context, TextSecurePreferences.getLocalNumber(context));
if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) { if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) {
// Only update group if the group admin sent the message
String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender()); String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender());
if (!groupRecord.getAdmins().contains(Address.fromSerialized(hexEncodedPublicKey))) { if (!groupRecord.getAdmins().contains(Address.fromSerialized(hexEncodedPublicKey))) {
Log.d("Loki - Group Message", "Received a group update message from a non-admin user for " + id +". Ignoring."); Log.d("Loki - Group Message", "Received a group update message from a non-admin user for " + id +". Ignoring.");
return null; return null;
} }
// We should only process update messages if we're in the group
Address ourAddress = Address.fromSerialized(ourHexEncodedPublicKey);
if (!groupRecord.getMembers().contains(ourAddress) &&
!group.getMembers().or(Collections.emptyList()).contains(ourHexEncodedPublicKey)) {
Log.d("Loki - Group Message", "Received a group update message from a group we are not a member of: " + id + "; ignoring.");
database.setActive(id, false);
return null;
}
} }
Set<Address> recordMembers = new HashSet<>(groupRecord.getMembers()); Set<Address> currentMembers = new HashSet<>(groupRecord.getMembers());
Set<Address> messageMembers = new HashSet<>(); Set<Address> newMembers = new HashSet<>();
for (String messageMember : group.getMembers().get()) { for (String messageMember : group.getMembers().get()) {
messageMembers.add(Address.fromExternal(context, messageMember)); newMembers.add(Address.fromExternal(context, messageMember));
} }
Set<Address> addedMembers = new HashSet<>(messageMembers); // Added members are the members who are present in newMembers but not in currentMembers
addedMembers.removeAll(recordMembers); Set<Address> addedMembers = new HashSet<>(newMembers);
addedMembers.removeAll(currentMembers);
Set<Address> missingMembers = new HashSet<>(recordMembers); // Kicked members are members who are present in currentMembers but not in newMembers
missingMembers.removeAll(messageMembers); Set<Address> removedMembers = new HashSet<>(currentMembers);
removedMembers.removeAll(newMembers);
GroupContext.Builder builder = createGroupContext(group); GroupContext.Builder builder = createGroupContext(group);
builder.setType(GroupContext.Type.UPDATE); builder.setType(GroupContext.Type.UPDATE);
if (addedMembers.size() > 0) { // Update our group members if they're different
Set<Address> unionMembers = new HashSet<>(recordMembers); if (!currentMembers.equals(newMembers)) {
unionMembers.addAll(messageMembers); database.updateMembers(id, new LinkedList<>(newMembers));
database.updateMembers(id, new LinkedList<>(unionMembers)); }
builder.clearMembers();
// We add any new or removed members to the group context
// This will allow us later to iterate over them to check if they left or were added for UI purposes
for (Address addedMember : addedMembers) { for (Address addedMember : addedMembers) {
builder.addMembers(addedMember.serialize()); builder.addNewMembers(addedMember.serialize());
}
} else {
builder.clearMembers();
} }
if (missingMembers.size() > 0) { for (Address removedMember : removedMembers) {
for (Address removedMember : missingMembers) { builder.addRemovedMembers(removedMember.serialize());
builder.addMembers(removedMember.serialize());
}
} }
if (group.getName().isPresent() || group.getAvatar().isPresent()) { if (group.getName().isPresent() || group.getAvatar().isPresent()) {
@ -191,11 +199,16 @@ public class GroupMessageProcessor {
builder.clearName(); builder.clearName();
} }
// If we were removed then we need to disable the chat
if (removedMembers.contains(Address.fromSerialized(ourHexEncodedPublicKey))) {
database.setActive(id, false);
} else {
if (!groupRecord.isActive()) database.setActive(id, true); if (!groupRecord.isActive()) database.setActive(id, true);
if (group.getMembers().isPresent()) { if (group.getMembers().isPresent()) {
establishSessionsWithMembersIfNeeded(context, group.getMembers().get()); establishSessionsWithMembersIfNeeded(context, group.getMembers().get());
} }
}
return storeMessage(context, content, group, builder.build(), outgoing); return storeMessage(context, content, group, builder.build(), outgoing);
} }

View File

@ -27,7 +27,7 @@ import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI; import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@ -137,12 +137,12 @@ import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOper
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.messages.shared.SharedContact; 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.DeviceLink; import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink;
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession; import org.whispersystems.signalservice.loki.api.multidevice.DeviceLinkingSession;
import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI; import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher; import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus; import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage; import org.whispersystems.signalservice.loki.messaging.LokiServiceMessage;
@ -1895,7 +1895,28 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get()); boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT; boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage); boolean isClosedGroup = conversation.getAddress().isSignalGroup();
boolean isGroupMember = true;
// Only allow messages from group members
if (isClosedGroup) {
String senderHexEncodedPublicKey = content.getSender();
try {
String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()), 5000).get();
if (masterHexEncodedPublicKey != null) {
senderHexEncodedPublicKey = masterHexEncodedPublicKey;
}
} catch (Exception e) {
e.printStackTrace();
}
Recipient senderMasterAddress = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false);
isGroupMember = groupId.isPresent() && groupDatabase.getGroupMembers(groupId.get(), true).contains(senderMasterAddress);
}
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage) || (isContentMessage && !isGroupMember);
} else { } else {
return sender.isBlocked(); return sender.isBlocked();
} }

View File

@ -50,8 +50,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.shared.SharedContact; 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.internal.push.SignalServiceProtos.GroupContext; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.api.LokiPublicChat; import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;
import java.io.IOException; import java.io.IOException;

View File

@ -45,7 +45,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.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;

View File

@ -33,7 +33,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.loki.api.LokiAPI; import org.whispersystems.signalservice.loki.api.LokiAPI;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage; import org.whispersystems.signalservice.loki.messaging.LokiSyncMessage;
import org.whispersystems.signalservice.loki.utilities.PromiseUtil; import org.whispersystems.signalservice.loki.utilities.PromiseUtil;

View File

@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPublicChatPoller import org.thoughtcrime.securesms.loki.redesign.messaging.LokiPublicChatPoller
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat
class LokiPublicChatManager(private val context: Context) { class LokiPublicChatManager(private val context: Context) {
private var chats = mutableMapOf<Long, LokiPublicChat>() private var chats = mutableMapOf<Long, LokiPublicChat>()

View File

@ -0,0 +1,82 @@
package org.thoughtcrime.securesms.loki
import android.content.Context
import okhttp3.*
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.logging.Log
import org.whispersystems.signalservice.internal.util.JsonUtil
import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement
import java.io.IOException
object LokiPushNotificationManager {
private val connection = OkHttpClient()
private val server by lazy {
LokiPushNotificationAcknowledgement.shared.server
}
private const val tokenExpirationInterval = 2 * 24 * 60 * 60 * 1000
@JvmStatic
fun unregister(token: String, context: Context?) {
val parameters = mapOf( "token" to token )
val url = "${server}/register"
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body).build()
connection.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
when (response.code()) {
200 -> {
val bodyAsString = response.body()!!.string()
val json = JsonUtil.fromJson(bodyAsString, Map::class.java)
val code = json?.get("code") as? Int
if (code != null && code != 0) {
TextSecurePreferences.setIsUsingFCM(context, false)
} else {
Log.d("Loki", "Couldn't disable FCM due to error: ${json?.get("message") as? String ?: "null"}.")
}
}
}
}
override fun onFailure(call: Call, exception: IOException) {
Log.d("Loki", "Couldn't disable FCM.")
}
})
}
@JvmStatic
fun register(token: String, hexEncodedPublicKey: String, context: Context?, force: Boolean) {
val oldToken = TextSecurePreferences.getFCMToken(context)
val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
val parameters = mapOf( "token" to token, "pubKey" to hexEncodedPublicKey )
val url = "${server}/register"
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body).build()
connection.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
when (response.code()) {
200 -> {
val bodyAsString = response.body()!!.string()
val json = JsonUtil.fromJson(bodyAsString, Map::class.java)
val code = json?.get("code") as? Int
if (code != null && code != 0) {
TextSecurePreferences.setIsUsingFCM(context, true)
TextSecurePreferences.setFCMToken(context, token)
TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis())
} else {
Log.d("Loki", "Couldn't register for FCM due to error: ${json?.get("message") as? String ?: "null"}.")
}
}
}
}
override fun onFailure(call: Call, exception: IOException) {
Log.d("Loki", "Couldn't register for FCM.")
}
})
}
}

View File

@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.loki.LokiSessionResetStatus import org.whispersystems.libsignal.loki.LokiSessionResetStatus
import org.whispersystems.signalservice.internal.util.JsonUtil import org.whispersystems.signalservice.internal.util.JsonUtil
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat
import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol import org.whispersystems.signalservice.loki.messaging.LokiThreadDatabaseProtocol
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation import org.whispersystems.signalservice.loki.utilities.PublicKeyValidation

View File

@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.SignalServiceMessageSender import org.whispersystems.signalservice.api.SignalServiceMessageSender
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject

View File

@ -18,9 +18,9 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
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.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
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

View File

@ -2,15 +2,16 @@ package org.thoughtcrime.securesms.loki.redesign.activities
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.TextView.OnEditorActionListener
import android.widget.Toast import android.widget.Toast
import kotlinx.android.synthetic.main.activity_display_name.* import kotlinx.android.synthetic.main.activity_display_name.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.redesign.utilities.push
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.crypto.ProfileCipher import org.whispersystems.signalservice.api.crypto.ProfileCipher
@ -21,6 +22,19 @@ class DisplayNameActivity : BaseActionBarActivity() {
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
setContentView(R.layout.activity_display_name) setContentView(R.layout.activity_display_name)
displayNameEditText.imeOptions = displayNameEditText.imeOptions or 16777216 // Always use incognito keyboard displayNameEditText.imeOptions = displayNameEditText.imeOptions or 16777216 // Always use incognito keyboard
displayNameEditText.setOnEditorActionListener(
OnEditorActionListener { _, actionId, event ->
// Handle validation from keyboard to trigger registration
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
actionId == EditorInfo.IME_ACTION_DONE ||
(event.action === KeyEvent.ACTION_DOWN
&& event.keyCode === KeyEvent.KEYCODE_ENTER)) {
this.register();
return@OnEditorActionListener true
}
// Return true if you have consumed the action, else false.
false
})
registerButton.setOnClickListener { register() } registerButton.setOnClickListener { register() }
} }
@ -38,20 +52,7 @@ class DisplayNameActivity : BaseActionBarActivity() {
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(displayNameEditText.windowToken, 0) inputMethodManager.hideSoftInputFromWindow(displayNameEditText.windowToken, 0)
TextSecurePreferences.setProfileName(this, displayName) TextSecurePreferences.setProfileName(this, displayName)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true) val intent = Intent(this, PNModeActivity::class.java)
TextSecurePreferences.setPromptedPushRegistration(this, true) push(intent)
val application = ApplicationContext.getInstance(this)
application.setUpStorageAPIIfNeeded()
application.setUpP2PAPI()
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
if (publicChatAPI != null) {
// TODO: This won't be necessary anymore when we don't auto-join the Loki Public Chat anymore
application.createDefaultPublicChatsIfNeeded()
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
servers.forEach { publicChatAPI.setDisplayName(displayName, it) }
}
val intent = Intent(this, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
show(intent)
} }
} }

View File

@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.loki.getColorWithID import org.thoughtcrime.securesms.loki.getColorWithID
import org.thoughtcrime.securesms.loki.redesign.dialogs.PNModeBottomSheet
import org.thoughtcrime.securesms.loki.redesign.utilities.push import org.thoughtcrime.securesms.loki.redesign.utilities.push
import org.thoughtcrime.securesms.loki.redesign.utilities.show import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
@ -156,32 +157,20 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
if (hasViewedSeed || !isMasterDevice) { if (hasViewedSeed || !isMasterDevice) {
seedReminderView.visibility = View.GONE seedReminderView.visibility = View.GONE
} }
// if (!TextSecurePreferences.getHasSeenOpenGroupSuggestionSheet(this)) { if (!TextSecurePreferences.hasSeenPNModeSheet(this)) {
// val bottomSheet = OpenGroupSuggestionBottomSheet() val bottomSheet = PNModeBottomSheet()
// bottomSheet.onJoinTapped = { bottomSheet.onConfirmTapped = { isUsingFCM ->
// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this) TextSecurePreferences.setHasSeenPNModeSheet(this, true)
// bottomSheet.dismiss() TextSecurePreferences.setIsUsingFCM(this, isUsingFCM)
// // TODO: Duplication of the code in JoinPublicChatActivity ApplicationContext.getInstance(this).registerForFCMIfNeeded(true)
// val application = ApplicationContext.getInstance(this) bottomSheet.dismiss()
// val channel: Long = 1 }
// val displayName = TextSecurePreferences.getProfileName(this) bottomSheet.onSkipTapped = {
// val lokiPublicChatAPI = application.lokiPublicChatAPI!! TextSecurePreferences.setHasSeenPNModeSheet(this, true)
// val url = "https://chat.getsession.org" bottomSheet.dismiss()
// application.lokiPublicChatManager.addChat(url, channel).successUi { }
// lokiPublicChatAPI.getMessages(channel, url) bottomSheet.show(supportFragmentManager, bottomSheet.tag)
// lokiPublicChatAPI.setDisplayName(displayName, url) }
// lokiPublicChatAPI.join(channel, url)
// val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(this)
// val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(this)
// lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
// }
// }
// bottomSheet.onDismissTapped = {
// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this)
// bottomSheet.dismiss()
// }
// bottomSheet.show(supportFragmentManager, bottomSheet.tag)
// }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@ -26,7 +26,7 @@ import org.whispersystems.curve25519.Curve25519
import org.whispersystems.libsignal.ecc.Curve import org.whispersystems.libsignal.ecc.Curve
import org.whispersystems.libsignal.ecc.ECKeyPair import org.whispersystems.libsignal.ecc.ECKeyPair
import org.whispersystems.libsignal.util.KeyHelper import org.whispersystems.libsignal.util.KeyHelper
import org.whispersystems.signalservice.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink
import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey import org.whispersystems.signalservice.loki.utilities.hexEncodedPublicKey
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded import org.whispersystems.signalservice.loki.utilities.retryIfNeeded

View File

@ -23,9 +23,9 @@ import org.thoughtcrime.securesms.loki.redesign.dialogs.*
import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage import org.thoughtcrime.securesms.loki.signAndSendDeviceLinkMessage
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink
import org.whispersystems.signalservice.loki.api.LokiAPI import org.whispersystems.signalservice.loki.api.LokiAPI
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule

View File

@ -6,7 +6,7 @@ import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.AsyncLoader import org.thoughtcrime.securesms.util.AsyncLoader
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
import java.io.File import java.io.File

View File

@ -0,0 +1,112 @@
package org.thoughtcrime.securesms.loki.redesign.activities
import android.app.AlertDialog
import android.content.Intent
import android.graphics.drawable.TransitionDrawable
import android.os.Bundle
import android.os.Handler
import android.support.annotation.DrawableRes
import android.view.View
import android.widget.LinearLayout
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_display_name.registerButton
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.android.synthetic.main.activity_pn_mode.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
class PNModeActivity : BaseActionBarActivity() {
private var selectedOptionView: LinearLayout? = null
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpActionBarSessionLogo()
setContentView(R.layout.activity_pn_mode)
fcmOptionView.setOnClickListener { toggleFCM() }
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
registerButton.setOnClickListener { register() }
}
// endregion
// region Animation
private fun performTransition(@DrawableRes transitionID: Int, subject: View) {
val drawable = resources.getDrawable(transitionID, theme) as TransitionDrawable
subject.background = drawable
drawable.startTransition(250)
}
// endregion
// region Interaction
private fun toggleFCM() {
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
selectedOptionView = fcmOptionView
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
selectedOptionView = null
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
selectedOptionView = fcmOptionView
}
}
}
private fun toggleBackgroundPolling() {
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
selectedOptionView = backgroundPollingOptionView
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
selectedOptionView = null
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
selectedOptionView = backgroundPollingOptionView
}
}
}
private fun register() {
if (selectedOptionView == null) {
val dialog = AlertDialog.Builder(this)
dialog.setTitle(R.string.activity_pn_mode_no_option_picked_dialog_title)
dialog.setPositiveButton(R.string.ok) { _, _ -> }
dialog.create().show()
return
}
val displayName = TextSecurePreferences.getProfileName(this)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
TextSecurePreferences.setHasSeenPNModeSheet(this, true) // Shouldn't be shown to users who've done the new onboarding
val application = ApplicationContext.getInstance(this)
application.setUpStorageAPIIfNeeded()
application.setUpP2PAPI()
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
if (publicChatAPI != null) {
// TODO: This won't be necessary anymore when we don't auto-join the Loki Public Chat anymore
application.createDefaultPublicChatsIfNeeded()
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
servers.forEach { publicChatAPI.setDisplayName(displayName, it) }
}
application.registerForFCMIfNeeded(true)
val intent = Intent(this, HomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
show(intent)
}
// endregion
}

View File

@ -42,7 +42,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
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.LokiFileServerAPI import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.security.SecureRandom import java.security.SecureRandom

View File

@ -17,9 +17,9 @@ import org.thoughtcrime.securesms.loki.redesign.utilities.QRCodeUtilities
import org.thoughtcrime.securesms.loki.toPx import org.thoughtcrime.securesms.loki.toPx
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession import org.whispersystems.signalservice.loki.api.multidevice.DeviceLinkingSession
import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener import org.whispersystems.signalservice.loki.api.multidevice.DeviceLinkingSessionListener
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener { class LinkDeviceMasterModeDialog : DialogFragment(), DeviceLinkingSessionListener {

View File

@ -15,9 +15,9 @@ import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.redesign.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.loki.api.DeviceLink import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession import org.whispersystems.signalservice.loki.api.multidevice.DeviceLinkingSession
import org.whispersystems.signalservice.loki.api.DeviceLinkingSessionListener import org.whispersystems.signalservice.loki.api.multidevice.DeviceLinkingSessionListener
import org.whispersystems.signalservice.loki.crypto.MnemonicCodec import org.whispersystems.signalservice.loki.crypto.MnemonicCodec
class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener { class LinkDeviceSlaveModeDialog : DialogFragment(), DeviceLinkingSessionListener {

View File

@ -8,7 +8,7 @@ import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_open_group_suggestion_bottom_sheet.* import kotlinx.android.synthetic.main.fragment_open_group_suggestion_bottom_sheet.*
import network.loki.messenger.R import network.loki.messenger.R
public class OpenGroupSuggestionBottomSheet : BottomSheetDialogFragment() { class OpenGroupSuggestionBottomSheet : BottomSheetDialogFragment() {
var onJoinTapped: (() -> Unit)? = null var onJoinTapped: (() -> Unit)? = null
var onDismissTapped: (() -> Unit)? = null var onDismissTapped: (() -> Unit)? = null

View File

@ -0,0 +1,100 @@
package org.thoughtcrime.securesms.loki.redesign.dialogs
import android.app.AlertDialog
import android.content.DialogInterface
import android.graphics.drawable.TransitionDrawable
import android.os.Bundle
import android.support.annotation.DrawableRes
import android.support.design.widget.BottomSheetDialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.fragment_pn_mode_bottom_sheet.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.util.TextSecurePreferences
class PNModeBottomSheet : BottomSheetDialogFragment() {
private var selectedOptionView: LinearLayout? = null
var onConfirmTapped: ((Boolean) -> Unit)? = null
var onSkipTapped: (() -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.SessionBottomSheetDialogTheme)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_pn_mode_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fcmOptionView.setOnClickListener { toggleFCM() }
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
confirmButton.setOnClickListener { confirm() }
skipButton.setOnClickListener { onSkipTapped?.invoke() }
}
override fun onDismiss(dialog: DialogInterface?) {
TextSecurePreferences.setHasSeenPNModeSheet(context, true)
super.onDismiss(dialog)
}
// region Animation
private fun performTransition(@DrawableRes transitionID: Int, subject: View) {
val drawable = resources.getDrawable(transitionID, context!!.theme) as TransitionDrawable
subject.background = drawable
drawable.startTransition(250)
}
// endregion
// region Interaction
private fun toggleFCM() {
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
selectedOptionView = fcmOptionView
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
selectedOptionView = null
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
selectedOptionView = fcmOptionView
}
}
}
private fun toggleBackgroundPolling() {
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
selectedOptionView = backgroundPollingOptionView
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
selectedOptionView = null
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
selectedOptionView = backgroundPollingOptionView
}
}
}
private fun confirm() {
if (selectedOptionView == null) {
val dialog = AlertDialog.Builder(context)
dialog.setTitle(R.string.sheet_pn_mode_no_option_picked_dialog_title)
dialog.setPositiveButton(R.string.ok) { _, _ -> }
dialog.create().show()
return
}
onConfirmTapped?.invoke(selectedOptionView == fcmOptionView)
}
// endregion
}

View File

@ -28,13 +28,15 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
} }
override fun onAlarm(context: Context, scheduledTime: Long): Long { override fun onAlarm(context: Context, scheduledTime: Long): Long {
if (TextSecurePreferences.isUsingFCM(context)) { return 0L }
if (scheduledTime != 0L) { if (scheduledTime != 0L) {
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context) val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
try { try {
val applicationContext = context.applicationContext as ApplicationContext val applicationContext = context.applicationContext as ApplicationContext
val broadcaster = applicationContext.broadcaster val broadcaster = applicationContext.broadcaster
LokiAPI(userHexEncodedPublicKey, lokiAPIDatabase, broadcaster).getMessages().map { messages -> LokiAPI.configureIfNeeded(userHexEncodedPublicKey, lokiAPIDatabase, broadcaster)
LokiAPI.shared.getMessages().map { messages ->
messages.forEach { messages.forEach {
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it)) PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it))
} }

View File

@ -7,9 +7,9 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.redesign.utilities.* import org.thoughtcrime.securesms.loki.redesign.utilities.*
import org.thoughtcrime.securesms.util.Base64 import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.DeviceLink
import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol
import org.whispersystems.signalservice.loki.api.LokiAPITarget import org.whispersystems.signalservice.loki.api.LokiAPITarget
import org.whispersystems.signalservice.loki.api.multidevice.DeviceLink
// TODO: Clean this up a bit // TODO: Clean this up a bit
@ -84,7 +84,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
var string = "${target.address}-${target.port}" var string = "${target.address}-${target.port}"
val keySet = target.publicKeySet val keySet = target.publicKeySet
if (keySet != null) { if (keySet != null) {
string += "-${keySet.idKey}-${keySet.encryptionKey}" string += "-${keySet.ed25519Key}-${keySet.x25519Key}"
} }
string string
} }

View File

@ -21,7 +21,11 @@ import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.messages.SignalServiceGroup import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.* import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI
import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities
import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat
import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChatAPI
import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChatMessage
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus 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.security.MessageDigest

View File

@ -13,8 +13,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.messages.SignalServiceGroup import org.whispersystems.signalservice.api.messages.SignalServiceGroup
import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.loki.api.LokiRSSFeed import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeed
import org.whispersystems.signalservice.loki.api.LokiRSSFeedProxy import org.whispersystems.signalservice.loki.api.rssfeeds.LokiRSSFeedProxy
import org.whispersystems.signalservice.loki.utilities.successBackground import org.whispersystems.signalservice.loki.utilities.successBackground
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.regex.Pattern import java.util.regex.Pattern

View File

@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat
object OpenGroupUtilities { object OpenGroupUtilities {

View File

@ -9,7 +9,7 @@ import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_mention_candidate.view.* import kotlinx.android.synthetic.main.view_mention_candidate.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChatAPI
import org.whispersystems.signalservice.loki.messaging.Mention import org.whispersystems.signalservice.loki.messaging.Mention
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) { class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {

View File

@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable
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.util.TextSecurePreferences
// TODO: Look into a better way of handling different sizes. Maybe an enum (with associated values) encapsulating the different modes? // TODO: Look into a better way of handling different sizes. Maybe an enum (with associated values) encapsulating the different modes?
@ -60,12 +61,15 @@ class ProfilePictureView : RelativeLayout {
fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String, @DimenRes sizeID: Int) { fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String, @DimenRes sizeID: Int) {
glide.clear(imageView) glide.clear(imageView)
if (hexEncodedPublicKey.isNotEmpty()) { if (hexEncodedPublicKey.isNotEmpty()) {
val signalProfilePicture = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).contactPhoto val recipient = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
val signalProfilePicture = recipient.contactPhoto
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0" && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") { if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0" && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
} else { } else {
val size = resources.getDimensionPixelSize(sizeID) val size = resources.getDimensionPixelSize(sizeID)
val jazzIcon = JazzIdenticonDrawable(size, size, hexEncodedPublicKey) val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
val hepk = if (recipient.isLocalNumber && masterHexEncodedPublicKey != null) masterHexEncodedPublicKey else hexEncodedPublicKey
val jazzIcon = JazzIdenticonDrawable(size, size, hepk)
glide.load(jazzIcon).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) glide.load(jazzIcon).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
} }
} else { } else {

View File

@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context; import android.content.Context;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI; import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
public class PushMediaConstraints extends MediaConstraints { public class PushMediaConstraints extends MediaConstraints {

View File

@ -23,7 +23,7 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities; import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.loki.api.LokiFileServerAPI; import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;

View File

@ -14,6 +14,7 @@ import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
import android.text.TextUtils; import android.text.TextUtils;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
@ -32,6 +33,16 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
public void onCreate(Bundle paramBundle) { public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle); super.onCreate(paramBundle);
// Set up FCM toggle
String fcmKey = "pref_key_use_fcm";
((SwitchPreferenceCompat)findPreference(fcmKey)).setChecked(TextSecurePreferences.isUsingFCM(getContext()));
this.findPreference(fcmKey)
.setOnPreferenceChangeListener((preference, newValue) -> {
TextSecurePreferences.setIsUsingFCM(getContext(), (boolean) newValue);
ApplicationContext.getInstance(getContext()).registerForFCMIfNeeded(true);
return true;
});
Preference ledBlinkPref = this.findPreference(TextSecurePreferences.LED_BLINK_PREF); Preference ledBlinkPref = this.findPreference(TextSecurePreferences.LED_BLINK_PREF);
if (NotificationChannels.supported()) { if (NotificationChannels.supported()) {

View File

@ -0,0 +1,36 @@
package org.thoughtcrime.securesms.service
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
import org.thoughtcrime.securesms.loki.LokiPushNotificationManager
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.logging.Log
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
import org.whispersystems.signalservice.internal.util.Base64
import org.whispersystems.signalservice.loki.messaging.LokiMessageWrapper
class PushNotificationService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d("Loki", "New FCM token: $token.")
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return
LokiPushNotificationManager.register(token, userHexEncodedPublicKey, this, false)
}
override fun onMessageReceived(message: RemoteMessage) {
val base64EncodedData = message.data["ENCRYPTED_DATA"]
val data = base64EncodedData?.let { Base64.decode(it) }
if (data != null) {
try {
val envelope = LokiMessageWrapper.unwrap(data)
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope))
} catch (e: Exception) {
Log.d("Loki", "Failed to unwrap data for message.")
}
} else {
Log.d("Loki", "Failed to decode data for message.")
}
}
}

View File

@ -62,7 +62,7 @@ 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.push.ContactTokenDetails; import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import org.whispersystems.signalservice.loki.api.LokiDeviceLinkUtilities; import org.whispersystems.signalservice.loki.api.multidevice.LokiDeviceLinkUtilities;
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;

View File

@ -10,7 +10,6 @@ import com.google.protobuf.ByteString;
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.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -159,7 +158,7 @@ public class GroupUtil {
@NonNull private final Context context; @NonNull private final Context context;
@Nullable private final GroupContext groupContext; @Nullable private final GroupContext groupContext;
private final List<Recipient> members; private final List<Recipient> newMembers;
private final List<Recipient> removedMembers; private final List<Recipient> removedMembers;
private boolean ourDeviceWasRemoved; private boolean ourDeviceWasRemoved;
@ -167,35 +166,35 @@ public class GroupUtil {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.groupContext = groupContext; this.groupContext = groupContext;
this.members = new LinkedList<>(); this.newMembers = new LinkedList<>();
this.removedMembers = new LinkedList<>(); this.removedMembers = new LinkedList<>();
this.ourDeviceWasRemoved = false; this.ourDeviceWasRemoved = false;
if (groupContext != null && !groupContext.getMembersList().isEmpty()) { if (groupContext != null) {
List<String> memberList = groupContext.getMembersList(); List<String> newMembers = groupContext.getNewMembersList();
List<Address> currentMembers = getCurrentGroupMembers(); for (String member : newMembers) {
this.newMembers.add(this.toRecipient(member));
// Add them to the member or removed members lists
for (String member : memberList) {
Address address = Address.fromSerialized(member);
Recipient recipient = Recipient.from(context, address, true);
if (currentMembers == null || currentMembers.contains(address)) {
this.members.add(recipient);
} else {
this.removedMembers.add(recipient);
} }
List<String> removedMembers = groupContext.getRemovedMembersList();
for (String member : removedMembers) {
this.removedMembers.add(this.toRecipient(member));
} }
// Check if our device was removed // Check if our device was removed
if (!removedMembers.isEmpty()) { if (!removedMembers.isEmpty()) {
String masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context); String masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
String hexEncodedPublicKey = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : TextSecurePreferences.getLocalNumber(context); String hexEncodedPublicKey = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : TextSecurePreferences.getLocalNumber(context);
Recipient self = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false); ourDeviceWasRemoved = removedMembers.contains(hexEncodedPublicKey);
ourDeviceWasRemoved = removedMembers.contains(self);
} }
} }
} }
private Recipient toRecipient(String hexEncodedPublicKey) {
Address address = Address.fromSerialized(hexEncodedPublicKey);
return Recipient.from(context, address, false);
}
public String toString(Recipient sender) { public String toString(Recipient sender) {
// Show the local removed message // Show the local removed message
if (ourDeviceWasRemoved) { if (ourDeviceWasRemoved) {
@ -211,10 +210,10 @@ public class GroupUtil {
String title = groupContext.getName(); String title = groupContext.getName();
if (!members.isEmpty()) { if (!newMembers.isEmpty()) {
description.append("\n"); description.append("\n");
description.append(context.getResources().getQuantityString(R.plurals.GroupUtil_joined_the_group, description.append(context.getResources().getQuantityString(R.plurals.GroupUtil_joined_the_group,
members.size(), toString(members))); newMembers.size(), toString(newMembers)));
} }
if (!removedMembers.isEmpty()) { if (!removedMembers.isEmpty()) {
@ -224,8 +223,8 @@ public class GroupUtil {
} }
if (title != null && !title.trim().isEmpty()) { if (title != null && !title.trim().isEmpty()) {
if (!members.isEmpty()) description.append(" "); String separator = (!newMembers.isEmpty() || !removedMembers.isEmpty()) ? " " : "\n";
else description.append("\n"); description.append(separator);
description.append(context.getString(R.string.GroupUtil_group_name_is_now, title)); description.append(context.getString(R.string.GroupUtil_group_name_is_now, title));
} }
@ -233,8 +232,8 @@ public class GroupUtil {
} }
public void addListener(RecipientModifiedListener listener) { public void addListener(RecipientModifiedListener listener) {
if (!this.members.isEmpty()) { if (!this.newMembers.isEmpty()) {
for (Recipient member : this.members) { for (Recipient member : this.newMembers) {
member.addListener(listener); member.addListener(listener);
} }
} }
@ -252,23 +251,5 @@ public class GroupUtil {
return result; return result;
} }
private List<Address> getCurrentGroupMembers() {
if (groupContext == null) { return null; }
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
byte[] decodedGroupId = groupContext.getId().toByteArray();
String signalGroupId = getEncodedId(decodedGroupId, false);
String publicChatId = getEncodedPublicChatId(decodedGroupId);
String rssFeedId = getEncodedRSSFeedId(decodedGroupId);
GroupRecord groupRecord = null;
if (!groupDatabase.isUnknownGroup(signalGroupId)) {
groupRecord = groupDatabase.getGroup(signalGroupId).orNull();
} else if (!groupDatabase.isUnknownGroup(publicChatId)) {
groupRecord = groupDatabase.getGroup(publicChatId).orNull();
} else if (!groupDatabase.isUnknownGroup(rssFeedId)) {
groupRecord = groupDatabase.getGroup(rssFeedId).orNull();
}
return (groupRecord != null) ? groupRecord.getMembers() : null;
}
} }
} }

View File

@ -185,6 +185,45 @@ public class TextSecurePreferences {
private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode"; private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode";
// region FCM
private static final String IS_USING_FCM = "pref_is_using_fcm";
private static final String FCM_TOKEN = "pref_fcm_token";
private static final String LAST_FCM_TOKEN_UPLOAD_TIME = "pref_last_fcm_token_upload_time";
private static final String HAS_SEEN_PN_MODE_SHEET = "pref_has_seen_pn_mode_sheet";
public static boolean isUsingFCM(Context context) {
return getBooleanPreference(context, IS_USING_FCM, false);
}
public static void setIsUsingFCM(Context context, boolean value) {
setBooleanPreference(context, IS_USING_FCM, value);
}
public static String getFCMToken(Context context) {
return getStringPreference(context, FCM_TOKEN, "");
}
public static void setFCMToken(Context context, String value) {
setStringPreference(context, FCM_TOKEN, value);
}
public static long getLastFCMUploadTime(Context context) {
return getLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, 0);
}
public static void setLastFCMUploadTime(Context context, long value) {
setLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, value);
}
public static boolean hasSeenPNModeSheet(Context context) {
return getBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, false);
}
public static void setHasSeenPNModeSheet(Context context, boolean value) {
setBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, value);
}
// endregion
public static boolean isScreenLockEnabled(@NonNull Context context) { public static boolean isScreenLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, SCREEN_LOCK, false); return getBooleanPreference(context, SCREEN_LOCK, false);
} }