Merge branch 'release/0.1.0'
53
.buildkite/pipeline.yml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
||||||
|
# Last docker plugin version can be found here:
|
||||||
|
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
||||||
|
|
||||||
|
# Build debug version of the RiotX application, from the develop branch and the features branches
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- label: "Assemble GPlay Debug version"
|
||||||
|
agents:
|
||||||
|
# We use a medium sized instance instead of the normal small ones because
|
||||||
|
# gradle build is long
|
||||||
|
queue: "medium"
|
||||||
|
commands:
|
||||||
|
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||||
|
artifact_paths:
|
||||||
|
- "vector/build/outputs/apk/gplay/debug/*.apk"
|
||||||
|
branches: "!master"
|
||||||
|
plugins:
|
||||||
|
- docker#v3.1.0:
|
||||||
|
image: "runmymind/docker-android-sdk"
|
||||||
|
|
||||||
|
- label: "Assemble FDroid Debug version"
|
||||||
|
agents:
|
||||||
|
# We use a medium sized instance instead of the normal small ones because
|
||||||
|
# gradle build is long
|
||||||
|
queue: "medium"
|
||||||
|
commands:
|
||||||
|
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||||
|
artifact_paths:
|
||||||
|
- "vector/build/outputs/apk/fdroid/debug/*.apk"
|
||||||
|
branches: "!master"
|
||||||
|
plugins:
|
||||||
|
- docker#v3.1.0:
|
||||||
|
image: "runmymind/docker-android-sdk"
|
||||||
|
|
||||||
|
- label: "Build Google Play unsigned APK"
|
||||||
|
agents:
|
||||||
|
# We use a medium sized instance instead of the normal small ones because
|
||||||
|
# gradle build is long
|
||||||
|
queue: "medium"
|
||||||
|
commands:
|
||||||
|
- "./gradlew clean assembleGplayRelease --stacktrace"
|
||||||
|
artifact_paths:
|
||||||
|
- "vector/build/outputs/apk/gplay/release/*.apk"
|
||||||
|
branches: "master"
|
||||||
|
plugins:
|
||||||
|
- docker#v3.1.0:
|
||||||
|
image: "runmymind/docker-android-sdk"
|
||||||
|
|
||||||
|
# Code quality
|
||||||
|
|
||||||
|
- label: "Code quality"
|
||||||
|
command: "./tools/check/check_code_quality.sh"
|
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
### Pull Request Checklist
|
||||||
|
|
||||||
|
<!-- Please read [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
|
||||||
|
|
||||||
|
- [ ] Changes has been tested on an Android device or Android emulator with API 16
|
||||||
|
- [ ] UI change has been tested on both light and dark themes
|
||||||
|
- [ ] Pull request is based on the develop branch
|
||||||
|
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/riotX-android/blob/develop/CHANGES.md)
|
||||||
|
- [ ] Pull request includes screenshots or videos if containing UI changes
|
||||||
|
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
4
.gitignore
vendored
@ -1,6 +1,8 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
|
.idea/*
|
||||||
|
/.idea/*
|
||||||
/.idea/libraries
|
/.idea/libraries
|
||||||
/.idea/modules.xml
|
/.idea/modules.xml
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
@ -8,3 +10,5 @@
|
|||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
||||||
|
/tmp
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<Objective-C-extensions>
|
|
||||||
<file>
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
|
||||||
</file>
|
|
||||||
<class>
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
|
||||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
|
||||||
</class>
|
|
||||||
<extensions>
|
|
||||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
|
||||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
|
||||||
</extensions>
|
|
||||||
</Objective-C-extensions>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
@ -1,8 +0,0 @@
|
|||||||
<component name="ProjectDictionaryState">
|
|
||||||
<dictionary name="ganfra">
|
|
||||||
<words>
|
|
||||||
<w>coroutine</w>
|
|
||||||
<w>moshi</w>
|
|
||||||
</words>
|
|
||||||
</dictionary>
|
|
||||||
</component>
|
|
@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GradleSettings">
|
|
||||||
<option name="linkedExternalProjectsSettings">
|
|
||||||
<GradleProjectSettings>
|
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="modules">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
<option value="$PROJECT_DIR$/app" />
|
|
||||||
<option value="$PROJECT_DIR$/matrix-sdk-android" />
|
|
||||||
<option value="$PROJECT_DIR$/matrix-sdk-core" />
|
|
||||||
<option value="$PROJECT_DIR$/matrix-sdk-rx" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="DifferentStdlibGradleVersion" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
@ -1,34 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="NullableNotNullManager">
|
|
||||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
|
||||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
|
||||||
<option name="myNullables">
|
|
||||||
<value>
|
|
||||||
<list size="5">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
<option name="myNotNulls">
|
|
||||||
<value>
|
|
||||||
<list size="4">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectType">
|
|
||||||
<option name="id" value="Android" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RunConfigurationProducerService">
|
|
||||||
<option name="ignoredProducers">
|
|
||||||
<set>
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
60
.travis.yml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# FTR: Configuration on https://travis-ci.org/vector-im/riotX-android/settings
|
||||||
|
#
|
||||||
|
# - Build only if .travis.yml is present -> On
|
||||||
|
# - Limit concurrent jobs -> Off
|
||||||
|
# - Build pushed branches -> On (build the branch)
|
||||||
|
# - Build pushed pull request -> On (build the PR after auto-merge)
|
||||||
|
#
|
||||||
|
# - Auto cancel branch builds -> On
|
||||||
|
# - Auto cancel pull request builds -> On
|
||||||
|
|
||||||
|
language: android
|
||||||
|
jdk: oraclejdk8
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
|
||||||
|
android:
|
||||||
|
components:
|
||||||
|
# Uncomment the lines below if you want to
|
||||||
|
# use the latest revision of Android SDK Tools
|
||||||
|
- tools
|
||||||
|
- platform-tools
|
||||||
|
|
||||||
|
# The BuildTools version used by your project
|
||||||
|
- build-tools-28.0.3
|
||||||
|
|
||||||
|
# The SDK version used to compile your project
|
||||||
|
- android-28
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
|
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.gradle/caches/
|
||||||
|
- $HOME/.gradle/wrapper/
|
||||||
|
- $HOME/.android/build-cache
|
||||||
|
|
||||||
|
# Build with the development SDK
|
||||||
|
before_script:
|
||||||
|
# Not necessary for the moment
|
||||||
|
# - /bin/sh ./set_debug_env.sh
|
||||||
|
|
||||||
|
# Just build the project for now
|
||||||
|
script:
|
||||||
|
# Build app (assembleGplayRelease assembleFdroidRelease)
|
||||||
|
# Build Android test (assembleAndroidTest) (disabled for now)
|
||||||
|
# Code quality (lintGplayRelease lintFdroidRelease)
|
||||||
|
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
||||||
|
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
||||||
|
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
||||||
|
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
|
||||||
|
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
||||||
|
# Other code quality check
|
||||||
|
- ./tools/check/check_code_quality.sh
|
||||||
|
- ./tools/travis/check_pr.sh
|
||||||
|
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
|
||||||
|
- diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml
|
0
AUTHORS.md
Normal file
49
CHANGES.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
Changes in RiotX 0.XX (2019-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Contextual action menu for messages in room
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
-
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
-
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
-
|
||||||
|
|
||||||
|
Build:
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=======================================================
|
||||||
|
+ TEMPLATE WHEN PREPARING A NEW RELEASE +
|
||||||
|
=======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Changes in RiotX 0.XX (2019-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
-
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
-
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
-
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
-
|
||||||
|
|
||||||
|
Build:
|
||||||
|
-
|
||||||
|
|
76
CONTRIBUTING.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Contributing code to Matrix
|
||||||
|
|
||||||
|
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst
|
||||||
|
|
||||||
|
Android support can be found in this [![Riot Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riot-android:matrix.org.svg?label=%23riot-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riot-android:matrix.org) room.
|
||||||
|
|
||||||
|
Dedicated room for RiotX: [![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org)
|
||||||
|
|
||||||
|
# Specific rules for Matrix Android projects
|
||||||
|
|
||||||
|
## Android Studio settings
|
||||||
|
|
||||||
|
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).
|
||||||
|
|
||||||
|
## Compilation
|
||||||
|
|
||||||
|
For now, the Matrix SDK and the RiotX application are in the same project. So there is no specific thing to do, this project should compile without any special action.
|
||||||
|
|
||||||
|
## I want to help translating RiotX
|
||||||
|
|
||||||
|
If you want to fix an issue with an English string, please submit a PR.
|
||||||
|
If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please use [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||||
|
|
||||||
|
For the moment, Strings from Riot will be used, there is no dedicated project in Weblate for RiotX.
|
||||||
|
|
||||||
|
## I want to submit a PR to fix an issue
|
||||||
|
|
||||||
|
Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it.
|
||||||
|
If an issue does not exist yet, it may be relevant to open a new issue and let us know that you're implementing it.
|
||||||
|
|
||||||
|
### Kotlin
|
||||||
|
|
||||||
|
This project is full Kotlin. Please do not write Java classes.
|
||||||
|
|
||||||
|
### CHANGES.md
|
||||||
|
|
||||||
|
Please add a line to the top of the file `CHANGES.md` describing your change.
|
||||||
|
|
||||||
|
### Code quality
|
||||||
|
|
||||||
|
Make sure the following commands execute without any error:
|
||||||
|
|
||||||
|
> ./tools/check/check_code_quality.sh
|
||||||
|
|
||||||
|
> ./gradlew lintGplayRelease
|
||||||
|
|
||||||
|
### Unit tests
|
||||||
|
|
||||||
|
Make sure the following commands execute without any error:
|
||||||
|
|
||||||
|
> ./gradlew testGplayReleaseUnitTest
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
RiotX is currently supported on Android Jelly Bean (API 16+): please test your change on an Android device (or Android emulator) running with API 16. Many issues can happen (including crashes) on older devices.
|
||||||
|
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||||
|
|
||||||
|
### Internationalisation
|
||||||
|
|
||||||
|
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||||
|
Do not hesitate to use plurals when appropriate.
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
|
||||||
|
You can check this in the layout editor preview by selecting any RTL language (ex: Arabic).
|
||||||
|
|
||||||
|
Also please check that the colors are ok for all the current themes of RiotX. Please use `?attr` instead of `@color` to reference colors in the layout. You can check this in the layout editor preview by selecting all the main themes (`AppTheme.Status`, `AppTheme.Dark`, etc.).
|
||||||
|
|
||||||
|
### Authors
|
||||||
|
|
||||||
|
Feel free to add an entry in file AUTHORS.md
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
Thanks for contributing to Matrix projects!
|
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
21
README.md
@ -1 +1,20 @@
|
|||||||
# riot-android-redesign-PoC
|
[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android)
|
||||||
|
[![Weblate](https://translate.riot.im/widgets/riot-android/-/svg-badge.svg)](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||||
|
[![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org)
|
||||||
|
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=alert_status)](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||||
|
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||||
|
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=bugs)](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||||
|
|
||||||
|
# RiotX Android
|
||||||
|
|
||||||
|
RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore.
|
||||||
|
|
||||||
|
It's based on a new Matrix SDK, written in Kotlin.
|
||||||
|
|
||||||
|
Download nightly build here: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||||
|
|
||||||
|
Matrix Room: [![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects!
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 28
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "im.vector.riotredesign"
|
|
||||||
minSdkVersion 21
|
|
||||||
targetSdkVersion 28
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
implementation project(":matrix-sdk-core")
|
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
|
||||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
|
||||||
implementation "org.koin:koin-core:$koin_version"
|
|
||||||
implementation "org.koin:koin-core-ext:$koin_version"
|
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
|
||||||
compile project(path: ':matrix-sdk-android')
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package im.vector.riotredesign
|
|
||||||
|
|
||||||
import android.support.test.InstrumentationRegistry
|
|
||||||
import android.support.test.runner.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getTargetContext()
|
|
||||||
assertEquals("im.vector.riotredesign", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="im.vector.riotredesign">
|
|
||||||
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/AppTheme">
|
|
||||||
<activity android:name=".features.login.LoginActivity">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".features.home.HomeActivity" />
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
@ -1,13 +0,0 @@
|
|||||||
package im.vector.riotredesign
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import org.koin.standalone.StandAloneContext.startKoin
|
|
||||||
|
|
||||||
class Riot : Application() {
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
startKoin(emptyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package im.vector.riotredesign.core.platform
|
|
||||||
|
|
||||||
import android.support.v7.app.AppCompatActivity
|
|
||||||
|
|
||||||
open class RiotActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package im.vector.riotredesign.core.platform
|
|
||||||
|
|
||||||
import android.support.v4.app.Fragment
|
|
||||||
|
|
||||||
class RiotFragment : Fragment() {
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package im.vector.riotredesign.features.home
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import im.vector.riotredesign.R
|
|
||||||
import im.vector.riotredesign.core.platform.RiotActivity
|
|
||||||
|
|
||||||
class HomeActivity : RiotActivity() {
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_home)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun newIntent(context: Context): Intent {
|
|
||||||
return Intent(context, HomeActivity::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
package im.vector.riotredesign.features.login
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
|
||||||
import im.vector.matrix.android.thread.MainThreadExecutor
|
|
||||||
import im.vector.matrix.core.api.Matrix
|
|
||||||
import im.vector.matrix.core.api.MatrixCallback
|
|
||||||
import im.vector.matrix.core.api.MatrixOptions
|
|
||||||
import im.vector.matrix.core.api.failure.Failure
|
|
||||||
import im.vector.matrix.core.api.login.data.Credentials
|
|
||||||
import im.vector.matrix.core.api.login.data.HomeServerConnectionConfig
|
|
||||||
import im.vector.riotredesign.R
|
|
||||||
import im.vector.riotredesign.core.platform.RiotActivity
|
|
||||||
import im.vector.riotredesign.features.home.HomeActivity
|
|
||||||
import kotlinx.android.synthetic.main.activity_login.*
|
|
||||||
|
|
||||||
class LoginActivity : RiotActivity() {
|
|
||||||
|
|
||||||
private val matrixOptions = MatrixOptions(mainExecutor = MainThreadExecutor())
|
|
||||||
private val matrix = Matrix(matrixOptions)
|
|
||||||
private val homeServerConnectionConfig = HomeServerConnectionConfig("https://matrix.org/")
|
|
||||||
private val session = matrix.createSession(homeServerConnectionConfig)
|
|
||||||
private val authenticator = session.authenticator()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_login)
|
|
||||||
authenticateButton.setOnClickListener { authenticate() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authenticate() {
|
|
||||||
val login = loginField.text.trim().toString()
|
|
||||||
val password = passwordField.text.trim().toString()
|
|
||||||
progressBar.visibility = View.VISIBLE
|
|
||||||
authenticator.authenticate(login, password, object : MatrixCallback<Credentials> {
|
|
||||||
override fun onSuccess(data: Credentials?) {
|
|
||||||
goToHomeScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Failure) {
|
|
||||||
progressBar.visibility = View.GONE
|
|
||||||
Toast.makeText(this@LoginActivity, "Authenticate failure: $failure", Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun goToHomeScreen() {
|
|
||||||
val intent = HomeActivity.newIntent(this)
|
|
||||||
startActivity(intent)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108">
|
|
||||||
<path
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeWidth="1">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="78.5885"
|
|
||||||
android:endY="90.9159"
|
|
||||||
android:startX="48.7653"
|
|
||||||
android:startY="61.0927"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
|
||||||
android:strokeColor="#00000000"
|
|
||||||
android:strokeWidth="1" />
|
|
||||||
</vector>
|
|
@ -1,170 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#26A69A"
|
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeColor="#33FFFFFF"
|
|
||||||
android:strokeWidth="0.8" />
|
|
||||||
</vector>
|
|
@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".features.login.LoginActivity">
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:text="activity_home"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
@ -1,66 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".features.login.LoginActivity">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/loginField"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:ems="10"
|
|
||||||
android:hint="Name"
|
|
||||||
android:inputType="textPersonName"
|
|
||||||
android:singleLine="false"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/passwordField"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.503"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintVertical_chainStyle="spread_inside" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/passwordField"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ems="10"
|
|
||||||
android:hint="Password"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/loginField"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/loginField"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginField" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/authenticateButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginEnd="32dp"
|
|
||||||
android:layout_marginStart="32dp"
|
|
||||||
android:text="Authenticate"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
|
||||||
<color name="colorAccent">#FF4081</color>
|
|
||||||
</resources>
|
|
@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">Riot Redesign</string>
|
|
||||||
</resources>
|
|
@ -1,11 +0,0 @@
|
|||||||
<resources>
|
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
@ -1,17 +0,0 @@
|
|||||||
package im.vector.riotredesign
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
|
||||||
}
|
|
85
build.gradle
@ -1,16 +1,21 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.0-rc-116'
|
ext.kotlin_version = '1.3.21'
|
||||||
ext.koin_version = '1.0.1'
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
|
maven {
|
||||||
|
url "https://plugins.gradle.org/m2/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.2.0'
|
classpath 'com.android.tools.build:gradle:3.4.1'
|
||||||
|
classpath 'com.google.gms:google-services:4.2.0'
|
||||||
|
classpath "com.airbnb.okreplay:gradle-plugin:1.4.0"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2'
|
||||||
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
@ -19,12 +24,82 @@ buildscript {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
// For olm library. This has to be declared first, to ensure that Olm library is not downloaded from another repo
|
||||||
|
maven {
|
||||||
|
url 'https://jitpack.io'
|
||||||
|
content {
|
||||||
|
// Use this repo only for olm library
|
||||||
|
includeGroupByRegex "org\\.matrix\\.gitlab\\.matrix-org"
|
||||||
|
// And also for FilePicker
|
||||||
|
includeGroupByRegex "com\\.github\\.jaiselrahman"
|
||||||
|
// And monarchy
|
||||||
|
includeGroupByRegex "com\\.github\\.Zhuinden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "http://dl.bintray.com/piasy/maven"
|
||||||
|
content {
|
||||||
|
includeGroupByRegex "com\\.github\\.piasy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin: 'org.sonarqube'
|
||||||
|
|
||||||
|
// To run a sonar analysis:
|
||||||
|
// Run './gradlew sonarqube -Dsonar.login=<REPLACE_WITH_SONAR_KEY>'
|
||||||
|
// The SONAR_KEY is stored in passbolt
|
||||||
|
|
||||||
|
sonarqube {
|
||||||
|
properties {
|
||||||
|
property "sonar.projectName", "RiotX-Android"
|
||||||
|
property "sonar.projectKey", "vector.android.riotx"
|
||||||
|
property "sonar.host.url", "https://sonarcloud.io"
|
||||||
|
property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
|
||||||
|
property "sonar.sourceEncoding", "UTF-8"
|
||||||
|
property "sonar.links.homepage", "https://github.com/vector-im/riotX-android/"
|
||||||
|
property "sonar.links.ci", "https://buildkite.com/matrix-dot-org/riotx-android"
|
||||||
|
property "sonar.links.scm", "https://github.com/vector-im/riotX-android/"
|
||||||
|
property "sonar.links.issue", "https://github.com/vector-im/riotX-android/issues"
|
||||||
|
property "sonar.organization", "new_vector_ltd_organization"
|
||||||
|
property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project(":vector") {
|
||||||
|
sonarqube {
|
||||||
|
properties {
|
||||||
|
property "sonar.sources", project(":vector").android.sourceSets.main.java.srcDirs
|
||||||
|
// exclude source code from analyses separated by a colon (:)
|
||||||
|
// property "sonar.exclusions", "**/*.*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//project(":matrix-sdk-android") {
|
||||||
|
// sonarqube {
|
||||||
|
// properties {
|
||||||
|
// property "sonar.sources", project(":matrix-sdk-android").android.sourceSets.main.java.srcDirs
|
||||||
|
// // exclude source code from analyses separated by a colon (:)
|
||||||
|
// // property "sonar.exclusions", "**/*.*"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//project(":matrix-sdk-android-rx") {
|
||||||
|
// sonarqube {
|
||||||
|
// properties {
|
||||||
|
// property "sonar.sources", project(":matrix-sdk-android-rx").android.sourceSets.main.java.srcDirs
|
||||||
|
// // exclude source code from analyses separated by a colon (:)
|
||||||
|
// // property "sonar.exclusions", "**/*.*"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
281
docs/notifications.md
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
This document aims to describe how Riot X android displays notifications to the end user. It also clarifies notifications and background settings in the app.
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
1. [Prerequisites Knowledge](#prerequisites-knowledge)
|
||||||
|
* [How does a matrix client gets a message from a Home Server?](#how-does-a-matrix-client-gets-a-message-from-a-home-server)
|
||||||
|
* [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification)
|
||||||
|
* [Push VS Notification](#push-vs-notification)
|
||||||
|
* [Push in the matrix federated world](#push-in-the-matrix-federated-world)
|
||||||
|
* [How does the Home Server knows when to notify a client?](#how-does-the-home-server-knows-when-to-notify-a-client)
|
||||||
|
* [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation)
|
||||||
|
* [Background processing limitations](#background-processing-limitations)
|
||||||
|
2. [RiotX Notification implementations](#riotx-notification-implementations)
|
||||||
|
* [Requirements](#requirements)
|
||||||
|
* [Foreground sync mode (Gplay & F-Droid)](#foreground-sync-mode-gplay-f-droid)
|
||||||
|
* [Push (FCM) received in background](#push-fcm-received-in-background)
|
||||||
|
* [FCM Fallback mode](#fcm-fallback-mode)
|
||||||
|
* [F-Droid background Mode](#f-droid-background-mode)
|
||||||
|
3. [Application Settings](#application-settings)
|
||||||
|
|
||||||
|
|
||||||
|
First let's start with some prerequisite knowledge
|
||||||
|
|
||||||
|
# Prerequisites Knowledge
|
||||||
|
|
||||||
|
## How does a matrix client gets a message from a Home Server?
|
||||||
|
|
||||||
|
In order to get messages from a home server, a matrix client need to perform a ``sync`` operation.
|
||||||
|
|
||||||
|
`To read events, the intended flow of operation is for clients to first call the /sync API without a since parameter. This returns the most recent message events for each room, as well as the state of the room at the start of the returned timeline. `
|
||||||
|
|
||||||
|
The client need to call the `sync`API periodically in order to get incremental updates of the server state (new messages).
|
||||||
|
This mechanism is known as **HTTP long Polling**.
|
||||||
|
|
||||||
|
Using the **HTTP Long Polling** mechanism a client polls a server requesting new information.
|
||||||
|
The server *holds the request open until new data is available*.
|
||||||
|
Once available, the server responds and sends the new information.
|
||||||
|
When the client receives the new information, it immediately sends another request, and the operation is repeated.
|
||||||
|
This effectively emulates a server push feature.
|
||||||
|
|
||||||
|
The HTTP long Polling can be fine tuned in the **SDK** using two parameters:
|
||||||
|
* timout (Sync request timeout)
|
||||||
|
* delay (Delay between each sync)
|
||||||
|
|
||||||
|
**timeout** is a server paramter, defined by:
|
||||||
|
```
|
||||||
|
The maximum time to wait, in milliseconds, before returning this request.`
|
||||||
|
If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
|
||||||
|
By default, this is 0, so the server will return immediately even if the response is empty.
|
||||||
|
```
|
||||||
|
|
||||||
|
**delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync.
|
||||||
|
|
||||||
|
When the Riot X Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0.
|
||||||
|
|
||||||
|
## How does a mobile app receives push notification
|
||||||
|
|
||||||
|
Push notification is used as a way to wake up a mobile application when some important information is available and should be processed.
|
||||||
|
|
||||||
|
Typically in order to get push notification, an application relies on a **Push Notification Service** or **Push Provider**.
|
||||||
|
|
||||||
|
For example iOS uses APNS (Apple Push Notification Service).
|
||||||
|
Most of android devices relies on Google's Firebase Cloud Messaging (FCM).
|
||||||
|
> FCM has replaced Google Cloud Messaging (GCM - deprecated April 10 2018)
|
||||||
|
|
||||||
|
FCM will only work on android devices that have Google plays services installed
|
||||||
|
(In simple terms, Google Play Services is a background service that runs on Android, which in turn helps in integrating Google’s advanced functionalities to other applications)
|
||||||
|
|
||||||
|
De-Googlified devices need to rely on something else in order to stay up to date with a server.
|
||||||
|
There some cases when devices with google services cannot use FCM (network infrastructure limitations -firewalls- ,
|
||||||
|
privacy and or independency requirement, source code licence)
|
||||||
|
|
||||||
|
## Push VS Notification
|
||||||
|
|
||||||
|
This need some disambiguation, because it is the source of common confusion:
|
||||||
|
|
||||||
|
|
||||||
|
*The fact that you see a notification on your screen does not mean that you have successfully configured your PUSH plateform.*
|
||||||
|
|
||||||
|
Technically there is a difference between a push and a notification. A notification is what you see on screen and/or in the notification Menu/Drawer (in the top bar of the phone).
|
||||||
|
|
||||||
|
Notifications are not always triggered by a push (One can display a notification locally triggered by an alarm)
|
||||||
|
|
||||||
|
|
||||||
|
## Push in the matrix federated world
|
||||||
|
|
||||||
|
In order to send a push to a mobile, App developers need to have a server that will use the FCM APIs, and these APIs requires authentication!
|
||||||
|
This server is called a **Push Gateway** in the matrix world
|
||||||
|
|
||||||
|
That means that Riot X Android, a matrix client created by New Vector, is using a **Push Gateway** with the needed credentials (FCM API secret Key) in order to send push to the New Vector client.
|
||||||
|
|
||||||
|
If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app.
|
||||||
|
|
||||||
|
On registration, a matrix client must tell to it's Home Server what Push Gateway to use.
|
||||||
|
|
||||||
|
See [Sygnal](https://github.com/matrix-org/sygnal/) for a reference implementation.
|
||||||
|
```
|
||||||
|
|
||||||
|
+--------------------+ +-------------------+
|
||||||
|
Matrix HTTP | | | |
|
||||||
|
Notification Protocol | App Developer | | Device Vendor |
|
||||||
|
| | | |
|
||||||
|
+-------------------+ | +----------------+ | | +---------------+ |
|
||||||
|
| | | | | | | | | |
|
||||||
|
| Matrix homeserver +-----> Push Gateway +------> Push Provider | |
|
||||||
|
| | | | | | | | | |
|
||||||
|
+-^-----------------+ | +----------------+ | | +----+----------+ |
|
||||||
|
| | | | | |
|
||||||
|
Matrix | | | | | |
|
||||||
|
Client/Server API + | | | | |
|
||||||
|
| | +--------------------+ +-------------------+
|
||||||
|
| +--+-+ |
|
||||||
|
| | <-------------------------------------------+
|
||||||
|
+---+ |
|
||||||
|
| | Provider Push Protocol
|
||||||
|
+----+
|
||||||
|
|
||||||
|
Mobile Device or Client
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended reading:
|
||||||
|
* https://thomask.sdf.org/blog/2016/12/11/riots-magical-push-notifications-in-ios.html
|
||||||
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id128
|
||||||
|
|
||||||
|
|
||||||
|
## How does the Home Server knows when to notify a client?
|
||||||
|
|
||||||
|
This is defined by [**push rules**](https://matrix.org/docs/spec/client_server/r0.4.0.html#push-rules-).
|
||||||
|
|
||||||
|
`A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented (sound / importance).`
|
||||||
|
|
||||||
|
A Home Server can be configured with default rules (for Direct messages, group messages, mentions, etc.. ).
|
||||||
|
|
||||||
|
There are different kind of push rules, it can be per room (each new message on this room should be notified), it can also define a pattern that a message should match (when you are mentioned, or key word based).
|
||||||
|
|
||||||
|
Notifications have 2 'levels' (`highlighted = true/false sound = default/custom`). In RiotX these notifications level are reflected as Noisy/Silent.
|
||||||
|
|
||||||
|
**What about encrypted messages?**
|
||||||
|
|
||||||
|
Of course, content patterns matching cannot be used for encrypted messages server side (as the content is encrypted).
|
||||||
|
|
||||||
|
That is why clients are able to **process the push rules client side** to decide what kind of notification should be presented for a given event.
|
||||||
|
|
||||||
|
## Push vs privacy, and mitigation
|
||||||
|
|
||||||
|
As seen previously, App developers don't directly send a push to the end user's device, they use a Push Provider as intermediary. So technically this intermediary is able to read the content of what is sent.
|
||||||
|
|
||||||
|
App developers usually mitigate this by sending a `silent notification`, that is a notification with no identifiable data, or with an encrypted payload. When the push is received the app can then synchronise to it's server in order to generate a local notification.
|
||||||
|
|
||||||
|
|
||||||
|
## Background processing limitations
|
||||||
|
|
||||||
|
A mobile applications process live in a managed word, meaning that its process can be limited (e.g no network access), stopped or killed at almost anytime by the Operating System.
|
||||||
|
|
||||||
|
In order to improve the battery life of their devices some constructors started to implement mechanism to drastically limit background execution of applications (e.g MIUI/Xiaomi restrictions, Sony stamina mode).
|
||||||
|
Then starting android M, android has also put more focus on improving device performances, introducing several IDLE modes, App-Standby, Light Doze, Doze.
|
||||||
|
|
||||||
|
In a nutshell, apps can't do much in background now.
|
||||||
|
|
||||||
|
If the devices is not plugged and stays IDLE for a certain amount of time, radio (mobile connectivity) and CPU can/will be turned off.
|
||||||
|
|
||||||
|
For an application like RiotX, where users can receive important information at anytime, the best option is to rely on a push system (Google's Firebase Message a.k.a FCM). FCM high priority push can wake up the device and whitelist an application to perform background task (for a limited but unspecified amount of time).
|
||||||
|
|
||||||
|
Notice that this is still evolving, and in future versions application that has been 'background restricted' by users won't be able to wake up even when a high priority push is received. Also high priority notifications could be rate limited (not defined anywhere)
|
||||||
|
|
||||||
|
It's getting a lot more complicated when you cannot rely on FCM (because: closed sources, network/firewall restrictions, privacy concerns).
|
||||||
|
The documentation on this subject is vague, and as per our experiments not always exact, also device's behaviour is fragmented.
|
||||||
|
|
||||||
|
It is getting more and more complex to have reliable notifications when FCM is not used.
|
||||||
|
|
||||||
|
# RiotX Notification implementations
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
RiotX Android must work with and without FCM.
|
||||||
|
* The RiotX android app published on F-Droid do not rely on FCM (all related dependencies are not present)
|
||||||
|
* The RiotX android app published on google play rely on FCM, with a fallback mode when FCM registration has failed (e.g outdated or missing Google Play Services)
|
||||||
|
|
||||||
|
## Foreground sync mode (Gplay & F-Droid)
|
||||||
|
|
||||||
|
When in foreground, RiotX performs sync continuously with a timeout value set to 10 seconds (see HttpPooling).
|
||||||
|
|
||||||
|
As this mode does not need to live beyond the scope of the application, and as per Google recommendation, RiotX uses the internal app resources (Thread and Timers) to perform the syncs.
|
||||||
|
|
||||||
|
This mode is turned on when the app enters foreground, and off when enters background.
|
||||||
|
|
||||||
|
In background, and depending on wether push is available or not, RiotX will use different methods to perform the syncs (Workers / Alarms / Service)
|
||||||
|
|
||||||
|
## Push (FCM) received in background
|
||||||
|
|
||||||
|
In order to enable Push, RiotX must first get a push token from the firebase SDK, then register a pusher with this token on the HomeServer.
|
||||||
|
|
||||||
|
When a message should be notified to a user, the user's homeserver notifies the registered `push gateway` for RiotX, that is [sygnal](https://github.com/matrix-org/sygnal) _- The reference implementation for push gateways -_ hosted by matrix.org.
|
||||||
|
|
||||||
|
This sygnal instance is configured with the required FCM API authentication token, and will then use the FCM API in order to notify the user's device running riotX.
|
||||||
|
|
||||||
|
```
|
||||||
|
Homeserver ----> Sygnal (configured for RiotX) ----> FCM ----> RiotX
|
||||||
|
```
|
||||||
|
|
||||||
|
The push gateway is configured to only send `(eventId,roomId)` in the push payload (for better [privacy](#push-vs-privacy-and-mitigation)).
|
||||||
|
|
||||||
|
RiotX needs then to synchronise with the user's HomeServer, in order to resolve the event and create a notification.
|
||||||
|
|
||||||
|
As per [Google recommendation](https://android-developers.googleblog.com/2018/09/notifying-your-users-with-fcm.html), RiotX will then use the WorkManager API in order to trigger a background sync.
|
||||||
|
|
||||||
|
**Google recommendations:**
|
||||||
|
> We recommend using FCM messages in combination with the WorkManager 1 or JobScheduler API
|
||||||
|
|
||||||
|
> Avoid background services. One common pitfall is using a background service to fetch data in the FCM message handler, since background service will be stopped by the system per recent changes to Google Play Policy
|
||||||
|
|
||||||
|
```
|
||||||
|
Homeserver ----> Sygnal ----> FCM ----> RiotX
|
||||||
|
(Sync) ----> Homeserver
|
||||||
|
<----
|
||||||
|
Display notification
|
||||||
|
```
|
||||||
|
|
||||||
|
**Possible outcomes**
|
||||||
|
|
||||||
|
Upon reception of the FCM push, RiotX will perform a sync call to the Home Server, during this process it is possible that:
|
||||||
|
* Happy path, the sync is performed, the message resolved and displayed in the notification drawer
|
||||||
|
* The notified message is not in the sync. Can happen if a lot of things did happen since the push (`gappy sync`)
|
||||||
|
* The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally)
|
||||||
|
* The sync takes too long and the process is killed before completion, or network is not reliable and the sync fails.
|
||||||
|
|
||||||
|
Riot X implements several strategies in these cases (TODO document)
|
||||||
|
|
||||||
|
## FCM Fallback mode
|
||||||
|
|
||||||
|
It is possible that RiotX is not able to get a FCM push token.
|
||||||
|
Common errors (amoung several others) that can cause that:
|
||||||
|
* Google Play Services is outdated
|
||||||
|
* Google Play Service fails in someways with FCM servers (infamous `SERVICE_NOT_AVAILABLE`)
|
||||||
|
|
||||||
|
If RiotX is able to detect one of this cases, it will notifies it to the users and when possible help him fix it via a dedicated troubleshoot screen.
|
||||||
|
|
||||||
|
Meanwhile, in order to offer a minimal service, and as per Google's recommendation for background activities, RiotX will launch periodic background sync in order to stays in sync with servers.
|
||||||
|
|
||||||
|
The fallback mode is impacted by all the battery life saving mechanism implemented by android. Meaning that if the app is not used for a certain amount of time (`App-Standby`), or the device stays still and unplugged (`Light Doze`) , the sync will become less frequent.
|
||||||
|
|
||||||
|
And if the device stays unplugged and still for too long (`Doze Mode`), no background sync will be perform at all (the system's `Ignore Battery Optimization option` has no effect on that).
|
||||||
|
|
||||||
|
Also the time interval between sync is elastic, controlled by the system to group other apps background sync request and start radio/cpu only once for all.
|
||||||
|
|
||||||
|
Usually in this mode, what happen is when you take back your phone in your hand, you suddenly receive notifications.
|
||||||
|
|
||||||
|
The fallback mode is supposed to be a temporary state waiting for the user to fix issues for FCM, or for App Developers that has done a fork to correctly configure their FCM settings.
|
||||||
|
|
||||||
|
## F-Droid background Mode
|
||||||
|
|
||||||
|
The F-Droid RiotX flavor has no dependencies to FCM, therefore cannot relies on Push.
|
||||||
|
|
||||||
|
Also Google's recommended background processing method cannot be applied. This is because all of these methods are affected by IDLE modes, and will result on the user not being notified at all when the app is in a Doze mode (only in maintenance windows that could happens only after hours).
|
||||||
|
|
||||||
|
Only solution left is to use `AlarmManager`, that offers new API to allow launching some process even if the App is in IDLE modes.
|
||||||
|
|
||||||
|
Notice that these alarms, due to their potential impact on battery life, can still be restricted by the system. Documentation says that they will not be triggered more than every minutes under normal system operation, and when in low power mode about every 15 mn.
|
||||||
|
|
||||||
|
These restrictions can be relaxed by requirering the app to be white listed from battery optimization.
|
||||||
|
|
||||||
|
F-Droid version will schedule alarms that will then trigger a Broadcast Receiver, that in turn will launch a Service (in the classic android way), and the reschedule an alarm for next time.
|
||||||
|
|
||||||
|
Depending on the system status (or device make), it is still possible that the app is not given enough time to launch the service, or that the radio is still turned off thus preventing the sync to success (that's why Alarms are not recommended for network related tasks).
|
||||||
|
|
||||||
|
That is why on RiotX F-Droid, the broadcast receiver will acquire a temporary WAKE_LOCK for several seconds (thus securing cpu/network), and launch the service in foreground. The service performs the sync.
|
||||||
|
|
||||||
|
Note that foreground services require to put a notification informing the user that the app is doing something even if not launched).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Application Settings
|
||||||
|
|
||||||
|
**Notifications > Enable notifications for this account**
|
||||||
|
|
||||||
|
Configure Sygnal to send or not notifications to all user devices.
|
||||||
|
|
||||||
|
**Notifications > Enable notifications for this device**
|
||||||
|
|
||||||
|
Disable notifications locally. The push server will continue to send notifications to the device but this one will ignore them.
|
||||||
|
|
||||||
|
|
@ -6,8 +6,18 @@
|
|||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
|
||||||
|
vector.debugPrivateData=false
|
||||||
|
vector.httpLogLevel=NONE
|
||||||
|
|
||||||
|
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
|
||||||
|
#vector.debugPrivateData=true
|
||||||
|
#vector.httpLogLevel=BODY
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Fri Sep 28 19:08:12 CEST 2018
|
#Tue Mar 19 09:53:05 CET 2019
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
|
||||||
|
2
gradlew
vendored
@ -28,7 +28,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
2
gradlew.bat
vendored
@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
45
matrix-sdk-android-rx/build.gradle
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
implementation project(":matrix-sdk-android")
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
|
||||||
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.rx;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("im.vector.matrix.rx.test", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
2
matrix-sdk-android-rx/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="im.vector.matrix.rx" />
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.android.MainThreadDisposable
|
||||||
|
|
||||||
|
private class LiveDataObservable<T>(
|
||||||
|
private val liveData: LiveData<T>,
|
||||||
|
private val valueIfNull: T? = null
|
||||||
|
) : Observable<T>() {
|
||||||
|
|
||||||
|
override fun subscribeActual(observer: io.reactivex.Observer<in T>) {
|
||||||
|
val relay = RemoveObserverInMainThread(observer)
|
||||||
|
observer.onSubscribe(relay)
|
||||||
|
liveData.observeForever(relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class RemoveObserverInMainThread(private val observer: io.reactivex.Observer<in T>)
|
||||||
|
: MainThreadDisposable(), Observer<T> {
|
||||||
|
|
||||||
|
override fun onChanged(t: T?) {
|
||||||
|
if (!isDisposed) {
|
||||||
|
if (t == null) {
|
||||||
|
if (valueIfNull != null) {
|
||||||
|
observer.onNext(valueIfNull)
|
||||||
|
} else {
|
||||||
|
observer.onError(NullPointerException(
|
||||||
|
"convert liveData value t to RxJava onNext(t), t cannot be null"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
observer.onNext(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDispose() {
|
||||||
|
liveData.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||||
|
return LiveDataObservable(this)
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
|
class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
|
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||||
|
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||||
|
return room.getRoomMemberIdsLive().asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||||
|
return room.getEventSummaryLive(eventId).asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveTimelineEvent(eventId: String): Observable<TimelineEvent> {
|
||||||
|
return room.liveTimeLineEvent(eventId).asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Room.rx(): RxRoom {
|
||||||
|
return RxRoom(this)
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
|
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||||
|
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||||
|
return session.liveGroupSummaries().asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
|
return session.syncState().asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun livePushers(): Observable<List<Pusher>> {
|
||||||
|
return session.livePushers().asObservable().observeOn(Schedulers.computation())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Session.rx(): RxSession {
|
||||||
|
return RxSession(this)
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.rx;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,167 @@
|
|||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
apply plugin: 'realm-android'
|
||||||
|
apply plugin: 'okreplay'
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "io.realm:realm-gradle-plugin:5.12.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "0.0.1"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
// Multidex is useful for tests
|
||||||
|
multiDexEnabled true
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
|
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||||
|
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
||||||
|
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
||||||
|
debug {
|
||||||
|
// Set to true to log privacy or sensible data, such as token
|
||||||
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
|
||||||
|
// Set to BODY instead of NONE to enable logging
|
||||||
|
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level." + project.property("vector.httpLogLevel")
|
||||||
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||||
|
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||||
|
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adbOptions {
|
||||||
|
installOptions "-g"
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
lintConfig file("lint.xml")
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static def gitRevision() {
|
||||||
|
def cmd = "git rev-parse --short HEAD"
|
||||||
|
return cmd.execute().text.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
static def gitRevisionUnixDate() {
|
||||||
|
def cmd = "git show -s --format=%ct HEAD^{commit}"
|
||||||
|
return cmd.execute().text.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
static def gitRevisionDate() {
|
||||||
|
def cmd = "git show -s --format=%ci HEAD^{commit}"
|
||||||
|
return cmd.execute().text.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
|
|
||||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
def arrow_version = "0.8.0"
|
||||||
|
def support_version = '1.1.0-beta01'
|
||||||
|
def moshi_version = '1.8.0'
|
||||||
|
def lifecycle_version = '2.0.0'
|
||||||
|
def coroutines_version = "1.0.1"
|
||||||
|
def markwon_version = '3.0.0'
|
||||||
|
def daggerVersion = '2.23.1'
|
||||||
|
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
implementation "androidx.appcompat:appcompat:1.1.0-beta01"
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.1.0-alpha06"
|
||||||
|
|
||||||
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
|
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||||
|
|
||||||
|
// Network
|
||||||
|
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||||
|
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
|
||||||
|
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||||
|
implementation 'com.novoda:merlin:1.1.6'
|
||||||
|
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||||
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||||
|
|
||||||
|
implementation "ru.noties.markwon:core:$markwon_version"
|
||||||
|
|
||||||
|
// Database
|
||||||
|
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||||
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
|
||||||
|
// Work
|
||||||
|
implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"
|
||||||
|
|
||||||
|
// FP
|
||||||
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
|
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
|
||||||
|
implementation "io.arrow-kt:arrow-effects:$arrow_version"
|
||||||
|
implementation "io.arrow-kt:arrow-effects-instances:$arrow_version"
|
||||||
|
implementation "io.arrow-kt:arrow-integration-retrofit-adapter:$arrow_version"
|
||||||
|
|
||||||
|
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
|
||||||
|
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'
|
||||||
|
|
||||||
|
// DI
|
||||||
|
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||||
|
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||||
|
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
|
||||||
|
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
|
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
|
||||||
|
|
||||||
|
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
|
||||||
|
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
||||||
|
androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
testImplementation 'org.robolectric:robolectric:4.0.2'
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||||
|
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
||||||
|
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||||
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||||
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||||
|
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
||||||
|
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||||
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
BIN
matrix-sdk-android/libs/react-native-webrtc.aar
Normal file
33
matrix-sdk-android/lint.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<lint>
|
||||||
|
<!-- Modify some severity -->
|
||||||
|
|
||||||
|
<!-- Resource -->
|
||||||
|
<issue id="MissingTranslation" severity="warning" />
|
||||||
|
<issue id="TypographyEllipsis" severity="error" />
|
||||||
|
<issue id="ImpliedQuantity" severity="warning" />
|
||||||
|
|
||||||
|
<!-- UX -->
|
||||||
|
<issue id="ButtonOrder" severity="error" />
|
||||||
|
|
||||||
|
<!-- Layout -->
|
||||||
|
<issue id="UnknownIdInLayout" severity="error" />
|
||||||
|
<issue id="StringFormatCount" severity="error" />
|
||||||
|
<issue id="HardcodedText" severity="error" />
|
||||||
|
<issue id="SpUsage" severity="error" />
|
||||||
|
<issue id="ObsoleteLayoutParam" severity="error" />
|
||||||
|
<issue id="InefficientWeight" severity="error" />
|
||||||
|
<issue id="DisableBaselineAlignment" severity="error" />
|
||||||
|
<issue id="ScrollViewSize" severity="error" />
|
||||||
|
|
||||||
|
<!-- RTL -->
|
||||||
|
<issue id="RtlEnabled" severity="error" />
|
||||||
|
<issue id="RtlHardcoded" severity="error" />
|
||||||
|
<issue id="RtlSymmetry" severity="error" />
|
||||||
|
|
||||||
|
<!-- Code -->
|
||||||
|
<issue id="SetTextI18n" severity="error" />
|
||||||
|
<issue id="ViewConstructor" severity="error" />
|
||||||
|
<issue id="UseValueOf" severity="error" />
|
||||||
|
|
||||||
|
</lint>
|
@ -1,26 +0,0 @@
|
|||||||
package im.vector.matrix.android;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.test.InstrumentationRegistry;
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
|
||||||
|
|
||||||
assertEquals("im.vector.matrix.android.test", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.InstrumentationRegistry
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface InstrumentedTest {
|
||||||
|
fun context(): Context {
|
||||||
|
return InstrumentationRegistry.getTargetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheDir(): File {
|
||||||
|
return context().cacheDir
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public final class LiveDataTestObserver<T> implements Observer<T> {
|
||||||
|
private final List<T> valueHistory = new ArrayList<>();
|
||||||
|
private final List<Observer<T>> childObservers = new ArrayList<>();
|
||||||
|
|
||||||
|
@Deprecated // will be removed in version 1.0
|
||||||
|
private final LiveData<T> observedLiveData;
|
||||||
|
|
||||||
|
private CountDownLatch valueLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
private LiveDataTestObserver(LiveData<T> observedLiveData) {
|
||||||
|
this.observedLiveData = observedLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable T value) {
|
||||||
|
valueHistory.add(value);
|
||||||
|
valueLatch.countDown();
|
||||||
|
for (Observer<T> childObserver : childObservers) {
|
||||||
|
childObserver.onChanged(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T value() {
|
||||||
|
assertHasValue();
|
||||||
|
return valueHistory.get(valueHistory.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> valueHistory() {
|
||||||
|
return Collections.unmodifiableList(valueHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes and removes observer from observed live data.
|
||||||
|
*
|
||||||
|
* @return This Observer
|
||||||
|
* @deprecated Please use {@link LiveData#removeObserver(Observer)} instead, will be removed in 1.0
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public LiveDataTestObserver<T> dispose() {
|
||||||
|
observedLiveData.removeObserver(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertHasValue() {
|
||||||
|
if (valueHistory.isEmpty()) {
|
||||||
|
throw fail("Observer never received any value");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertNoValue() {
|
||||||
|
if (!valueHistory.isEmpty()) {
|
||||||
|
throw fail("Expected no value, but received: " + value());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertHistorySize(int expectedSize) {
|
||||||
|
int size = valueHistory.size();
|
||||||
|
if (size != expectedSize) {
|
||||||
|
throw fail("History size differ; Expected: " + expectedSize + ", Actual: " + size);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertValue(T expected) {
|
||||||
|
T value = value();
|
||||||
|
|
||||||
|
if (expected == null && value == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value.equals(expected)) {
|
||||||
|
throw fail("Expected: " + valueAndClass(expected) + ", Actual: " + valueAndClass(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertValue(Function<T, Boolean> valuePredicate) {
|
||||||
|
T value = value();
|
||||||
|
|
||||||
|
if (!valuePredicate.apply(value)) {
|
||||||
|
throw fail("Value not present");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveDataTestObserver<T> assertNever(Function<T, Boolean> valuePredicate) {
|
||||||
|
int size = valueHistory.size();
|
||||||
|
for (int valueIndex = 0; valueIndex < size; valueIndex++) {
|
||||||
|
T value = this.valueHistory.get(valueIndex);
|
||||||
|
if (valuePredicate.apply(value)) {
|
||||||
|
throw fail("Value at position " + valueIndex + " matches predicate "
|
||||||
|
+ valuePredicate.toString() + ", which was not expected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits until this TestObserver has any value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then this method returns immediately.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitValue() throws InterruptedException {
|
||||||
|
valueLatch.await();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits the specified amount of time or until this TestObserver has any value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then this method returns immediately.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitValue(long timeout, TimeUnit timeUnit) throws InterruptedException {
|
||||||
|
valueLatch.await(timeout, timeUnit);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits until this TestObserver receives next value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then it awaits for another one.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitNextValue() throws InterruptedException {
|
||||||
|
return withNewLatch().awaitValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits the specified amount of time or until this TestObserver receives next value.
|
||||||
|
* <p>
|
||||||
|
* If this TestObserver has already value then it awaits for another one.
|
||||||
|
*
|
||||||
|
* @return this
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while waiting
|
||||||
|
*/
|
||||||
|
public LiveDataTestObserver<T> awaitNextValue(long timeout, TimeUnit timeUnit) throws InterruptedException {
|
||||||
|
return withNewLatch().awaitValue(timeout, timeUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveDataTestObserver<T> withNewLatch() {
|
||||||
|
valueLatch = new CountDownLatch(1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AssertionError fail(String message) {
|
||||||
|
return new AssertionError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String valueAndClass(Object value) {
|
||||||
|
if (value != null) {
|
||||||
|
return value + " (class: " + value.getClass().getSimpleName() + ")";
|
||||||
|
}
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> LiveDataTestObserver<T> create() {
|
||||||
|
return new LiveDataTestObserver<>(new MutableLiveData<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> LiveDataTestObserver<T> test(LiveData<T> liveData) {
|
||||||
|
LiveDataTestObserver<T> observer = new LiveDataTestObserver<>(liveData);
|
||||||
|
liveData.observeForever(observer);
|
||||||
|
return observer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
public class MainThreadExecutor implements Executor {
|
||||||
|
|
||||||
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable runnable) {
|
||||||
|
handler.post(runnable);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android
|
||||||
|
|
||||||
|
import okreplay.OkReplayConfig
|
||||||
|
import okreplay.PermissionRule
|
||||||
|
import okreplay.RecorderRule
|
||||||
|
import org.junit.rules.RuleChain
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
|
||||||
|
class OkReplayRuleChainNoActivity(
|
||||||
|
private val configuration: OkReplayConfig) {
|
||||||
|
|
||||||
|
fun get(): TestRule {
|
||||||
|
return RuleChain.outerRule(PermissionRule(configuration))
|
||||||
|
.around(RecorderRule(configuration))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
|
|
||||||
|
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.auth
|
||||||
|
|
||||||
|
import androidx.test.annotation.UiThreadTest
|
||||||
|
import androidx.test.rule.GrantPermissionRule
|
||||||
|
import androidx.test.runner.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import im.vector.matrix.android.OkReplayRuleChainNoActivity
|
||||||
|
import im.vector.matrix.android.api.auth.Authenticator
|
||||||
|
import okreplay.*
|
||||||
|
import org.junit.ClassRule
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
internal class AuthenticatorTest : InstrumentedTest {
|
||||||
|
|
||||||
|
lateinit var authenticator: Authenticator
|
||||||
|
lateinit var okReplayInterceptor: OkReplayInterceptor
|
||||||
|
|
||||||
|
private val okReplayConfig = OkReplayConfig.Builder()
|
||||||
|
.tapeRoot(AndroidTapeRoot(
|
||||||
|
context(), javaClass))
|
||||||
|
.defaultMode(TapeMode.READ_WRITE) // or TapeMode.READ_ONLY
|
||||||
|
.sslEnabled(true)
|
||||||
|
.interceptor(okReplayInterceptor)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val testRule = OkReplayRuleChainNoActivity(okReplayConfig).get()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@UiThreadTest
|
||||||
|
@OkReplay(tape = "auth", mode = TapeMode.READ_WRITE)
|
||||||
|
fun auth() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@ClassRule
|
||||||
|
@JvmField
|
||||||
|
val grantExternalStoragePermissionRule: GrantPermissionRule =
|
||||||
|
GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal class CryptoStoreHelper {
|
||||||
|
|
||||||
|
fun createStore(): IMXCryptoStore {
|
||||||
|
return RealmCryptoStore(
|
||||||
|
realmConfiguration = RealmConfiguration.Builder()
|
||||||
|
.name("test.realm")
|
||||||
|
.modules(RealmCryptoStoreModule())
|
||||||
|
.build(),
|
||||||
|
credentials = createCredential())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCredential() = Credentials(
|
||||||
|
userId = "userId_" + Random().nextInt(),
|
||||||
|
homeServer = "http://matrix.org",
|
||||||
|
accessToken = "access_token",
|
||||||
|
refreshToken = null,
|
||||||
|
deviceId = "deviceId_sample"
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.olm.OlmAccount
|
||||||
|
import org.matrix.olm.OlmManager
|
||||||
|
import org.matrix.olm.OlmSession
|
||||||
|
|
||||||
|
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
||||||
|
|
||||||
|
class CryptoStoreTest {
|
||||||
|
|
||||||
|
private val cryptoStoreHelper = CryptoStoreHelper()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_metadata_realm_ok() {
|
||||||
|
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||||
|
|
||||||
|
assertFalse(cryptoStore.hasData())
|
||||||
|
|
||||||
|
cryptoStore.open()
|
||||||
|
|
||||||
|
assertEquals("deviceId_sample", cryptoStore.getDeviceId())
|
||||||
|
|
||||||
|
assertTrue(cryptoStore.hasData())
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cryptoStore.close()
|
||||||
|
cryptoStore.deleteStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_lastSessionUsed() {
|
||||||
|
// Ensure Olm is initialized
|
||||||
|
OlmManager()
|
||||||
|
|
||||||
|
val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore()
|
||||||
|
|
||||||
|
assertNull(cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||||
|
|
||||||
|
val olmAccount1 = OlmAccount().apply {
|
||||||
|
generateOneTimeKeys(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val olmSession1 = OlmSession().apply {
|
||||||
|
initOutboundSession(olmAccount1,
|
||||||
|
olmAccount1.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY],
|
||||||
|
olmAccount1.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
val sessionId1 = olmSession1.sessionIdentifier()
|
||||||
|
val olmSessionWrapper1 = OlmSessionWrapper(olmSession1)
|
||||||
|
|
||||||
|
cryptoStore.storeSession(olmSessionWrapper1, DUMMY_DEVICE_KEY)
|
||||||
|
|
||||||
|
assertEquals(sessionId1, cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||||
|
|
||||||
|
val olmAccount2 = OlmAccount().apply {
|
||||||
|
generateOneTimeKeys(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val olmSession2 = OlmSession().apply {
|
||||||
|
initOutboundSession(olmAccount2,
|
||||||
|
olmAccount2.identityKeys()[OlmAccount.JSON_KEY_IDENTITY_KEY],
|
||||||
|
olmAccount2.oneTimeKeys()[OlmAccount.JSON_KEY_ONE_TIME_KEY]?.values?.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
val sessionId2 = olmSession2.sessionIdentifier()
|
||||||
|
val olmSessionWrapper2 = OlmSessionWrapper(olmSession2)
|
||||||
|
|
||||||
|
cryptoStore.storeSession(olmSessionWrapper2, DUMMY_DEVICE_KEY)
|
||||||
|
|
||||||
|
// Ensure sessionIds are distinct
|
||||||
|
assertNotEquals(sessionId1, sessionId2)
|
||||||
|
|
||||||
|
// Note: we cannot be sure what will be the result of getLastUsedSessionId() here
|
||||||
|
|
||||||
|
olmSessionWrapper2.onMessageReceived()
|
||||||
|
cryptoStore.storeSession(olmSessionWrapper2, DUMMY_DEVICE_KEY)
|
||||||
|
|
||||||
|
// sessionId2 is returned now
|
||||||
|
assertEquals(sessionId2, cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||||
|
|
||||||
|
Thread.sleep(2)
|
||||||
|
|
||||||
|
olmSessionWrapper1.onMessageReceived()
|
||||||
|
cryptoStore.storeSession(olmSessionWrapper1, DUMMY_DEVICE_KEY)
|
||||||
|
|
||||||
|
// sessionId1 is returned now
|
||||||
|
assertEquals(sessionId1, cryptoStore.getLastUsedSessionId(DUMMY_DEVICE_KEY))
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
olmSession1.releaseSession()
|
||||||
|
olmSession2.releaseSession()
|
||||||
|
|
||||||
|
olmAccount1.releaseAccount()
|
||||||
|
olmAccount2.releaseAccount()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.util
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
internal class JsonCanonicalizerTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun identityTest() {
|
||||||
|
listOf(
|
||||||
|
"{}",
|
||||||
|
"""{"a":true}""",
|
||||||
|
"""{"a":false}""",
|
||||||
|
"""{"a":1}""",
|
||||||
|
"""{"a":1.2}""",
|
||||||
|
"""{"a":null}""",
|
||||||
|
"""{"a":[]}""",
|
||||||
|
"""{"a":["b":"c"]}""",
|
||||||
|
"""{"a":["c":"b","d":"e"]}""",
|
||||||
|
"""{"a":["d":"b","c":"e"]}"""
|
||||||
|
).forEach {
|
||||||
|
assertEquals(it,
|
||||||
|
JsonCanonicalizer.canonicalize(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun reorderTest() {
|
||||||
|
assertEquals("""{"a":true,"b":false}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{"b":false,"a":true}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun realSampleTest() {
|
||||||
|
assertEquals("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX\/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doubleQuoteTest() {
|
||||||
|
assertEquals("{\"a\":\"\\\"\"}",
|
||||||
|
JsonCanonicalizer.canonicalize("{\"a\":\"\\\"\"}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Test from https://matrix.org/docs/spec/appendices.html#examples
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg001Test() {
|
||||||
|
assertEquals("""{}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg002Test() {
|
||||||
|
assertEquals("""{"one":1,"two":"Two"}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{
|
||||||
|
"one": 1,
|
||||||
|
"two": "Two"
|
||||||
|
}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg003Test() {
|
||||||
|
assertEquals("""{"a":"1","b":"2"}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{
|
||||||
|
"b": "2",
|
||||||
|
"a": "1"
|
||||||
|
}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg004Test() {
|
||||||
|
assertEquals("""{"a":"1","b":"2"}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg005Test() {
|
||||||
|
assertEquals("""{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{
|
||||||
|
"auth": {
|
||||||
|
"success": true,
|
||||||
|
"mxid": "@john.doe:example.com",
|
||||||
|
"profile": {
|
||||||
|
"display_name": "John Doe",
|
||||||
|
"three_pids": [
|
||||||
|
{
|
||||||
|
"medium": "email",
|
||||||
|
"address": "john.doe@example.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"medium": "msisdn",
|
||||||
|
"address": "123456789"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg006Test() {
|
||||||
|
assertEquals("""{"a":"日本語"}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{
|
||||||
|
"a": "日本語"
|
||||||
|
}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg007Test() {
|
||||||
|
assertEquals("""{"日":1,"本":2}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{
|
||||||
|
"本": 2,
|
||||||
|
"日": 1
|
||||||
|
}"""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg008Test() {
|
||||||
|
assertEquals("""{"a":"日"}""",
|
||||||
|
JsonCanonicalizer.canonicalize("{\"a\": \"\u65E5\"}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun matrixOrg009Test() {
|
||||||
|
assertEquals("""{"a":null}""",
|
||||||
|
JsonCanonicalizer.canonicalize("""{
|
||||||
|
"a": null
|
||||||
|
}"""))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import im.vector.matrix.android.internal.database.helper.add
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addAll
|
||||||
|
import im.vector.matrix.android.internal.database.helper.isUnlinked
|
||||||
|
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
||||||
|
import im.vector.matrix.android.internal.database.helper.merge
|
||||||
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||||
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||||
|
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeRoomMemberEvent
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import org.amshove.kluent.shouldBeFalse
|
||||||
|
import org.amshove.kluent.shouldBeTrue
|
||||||
|
import org.amshove.kluent.shouldEqual
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
internal class ChunkEntityTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private lateinit var monarchy: Monarchy
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
Realm.init(context())
|
||||||
|
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build()
|
||||||
|
monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
|
val fakeEvent = createFakeMessageEvent()
|
||||||
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
|
chunk.timelineEvents.size shouldEqual 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
|
val fakeEvent = createFakeMessageEvent()
|
||||||
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
|
chunk.timelineEvents.size shouldEqual 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
|
val fakeEvent = createFakeRoomMemberEvent()
|
||||||
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
|
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
|
val fakeEvent = createFakeMessageEvent()
|
||||||
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
|
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addAll_shouldStateIndexIncremented_whenStateEventsAreAddedForward() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
|
val fakeEvents = createFakeListOfEvents(30)
|
||||||
|
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
|
||||||
|
chunk.addAll("roomId", fakeEvents, PaginationDirection.FORWARDS)
|
||||||
|
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual numberOfStateEvents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addAll_shouldStateIndexDecremented_whenStateEventsAreAddedBackward() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
|
val fakeEvents = createFakeListOfEvents(30)
|
||||||
|
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
|
||||||
|
val lastIsState = fakeEvents.last().isStateEvent()
|
||||||
|
val expectedStateIndex = if (lastIsState) -numberOfStateEvents + 1 else -numberOfStateEvents
|
||||||
|
chunk.addAll("roomId", fakeEvents, PaginationDirection.BACKWARDS)
|
||||||
|
chunk.lastStateIndex(PaginationDirection.BACKWARDS) shouldEqual expectedStateIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun merge_shouldAddEvents_whenMergingBackward() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk1: ChunkEntity = realm.createObject()
|
||||||
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
|
||||||
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
|
chunk1.timelineEvents.size shouldEqual 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun merge_shouldAddOnlyDifferentEvents_whenMergingBackward() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk1: ChunkEntity = realm.createObject()
|
||||||
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
|
val eventsForChunk1 = createFakeListOfEvents(30)
|
||||||
|
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
|
||||||
|
chunk1.isLastForward = true
|
||||||
|
chunk2.isLastForward = false
|
||||||
|
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
|
||||||
|
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
|
||||||
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
|
chunk1.timelineEvents.size shouldEqual 40
|
||||||
|
chunk1.isLastForward.shouldBeTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk1: ChunkEntity = realm.createObject()
|
||||||
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||||
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
|
||||||
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
|
chunk1.isUnlinked().shouldBeFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk1: ChunkEntity = realm.createObject()
|
||||||
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||||
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||||
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
|
chunk1.isUnlinked().shouldBeTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun merge_shouldPrevTokenMerged_whenMergingForwards() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk1: ChunkEntity = realm.createObject()
|
||||||
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
|
val prevToken = "prev_token"
|
||||||
|
chunk1.prevToken = prevToken
|
||||||
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||||
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||||
|
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
|
||||||
|
chunk1.prevToken shouldEqual prevToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun merge_shouldNextTokenMerged_whenMergingBackwards() {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val chunk1: ChunkEntity = realm.createObject()
|
||||||
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
|
val nextToken = "next_token"
|
||||||
|
chunk1.nextToken = nextToken
|
||||||
|
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||||
|
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
|
||||||
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
|
chunk1.nextToken shouldEqual nextToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
internal class FakeGetContextOfEventTask constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||||
|
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
|
val tokenChunkEvent = FakeTokenChunkEvent(
|
||||||
|
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||||
|
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||||
|
fakeEvents
|
||||||
|
)
|
||||||
|
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
internal class FakePaginationTask @Inject constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||||
|
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
|
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||||
|
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
|
||||||
|
|
||||||
|
internal data class FakeTokenChunkEvent(override val start: String?,
|
||||||
|
override val end: String?,
|
||||||
|
override val events: List<Event> = emptyList(),
|
||||||
|
override val stateEvents: List<Event> = emptyList()
|
||||||
|
) : TokenChunkEvent
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addAll
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||||
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
object RoomDataHelper {
|
||||||
|
|
||||||
|
private const val FAKE_TEST_SENDER = "@sender:test.org"
|
||||||
|
private val EVENT_FACTORIES = hashMapOf(
|
||||||
|
0 to { createFakeMessageEvent() },
|
||||||
|
1 to { createFakeRoomMemberEvent() }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun createFakeListOfEvents(size: Int = 10): List<Event> {
|
||||||
|
return (0 until size).mapNotNull {
|
||||||
|
val nextInt = Random.nextInt(EVENT_FACTORIES.size)
|
||||||
|
EVENT_FACTORIES[nextInt]?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFakeEvent(type: String,
|
||||||
|
content: Content? = null,
|
||||||
|
prevContent: Content? = null,
|
||||||
|
sender: String = FAKE_TEST_SENDER,
|
||||||
|
stateKey: String = FAKE_TEST_SENDER
|
||||||
|
): Event {
|
||||||
|
return Event(
|
||||||
|
type = type,
|
||||||
|
eventId = Random.nextLong().toString(),
|
||||||
|
content = content,
|
||||||
|
prevContent = prevContent,
|
||||||
|
senderId = sender,
|
||||||
|
stateKey = stateKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFakeMessageEvent(): Event {
|
||||||
|
val message = MessageTextContent(MessageType.MSGTYPE_TEXT, "Fake message #${Random.nextLong()}").toContent()
|
||||||
|
return createFakeEvent(EventType.MESSAGE, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFakeRoomMemberEvent(): Event {
|
||||||
|
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||||
|
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
||||||
|
roomEntity.membership = Membership.JOIN
|
||||||
|
val eventList = createFakeListOfEvents(10)
|
||||||
|
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
||||||
|
nextToken = null
|
||||||
|
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
||||||
|
isLastForward = true
|
||||||
|
}
|
||||||
|
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS)
|
||||||
|
roomEntity.addOrUpdate(chunkEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
|
||||||
|
internal class TimelineTest : InstrumentedTest {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ROOM_ID = "roomId"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var monarchy: Monarchy
|
||||||
|
|
||||||
|
// @Before
|
||||||
|
// fun setup() {
|
||||||
|
// Timber.plant(Timber.DebugTree())
|
||||||
|
// Realm.init(context())
|
||||||
|
// val testConfiguration = RealmConfiguration.Builder().name("test-realm")
|
||||||
|
// .modules(SessionRealmModule()).build()
|
||||||
|
//
|
||||||
|
// Realm.deleteRealm(testConfiguration)
|
||||||
|
// monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
||||||
|
// RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun createTimeline(initialEventId: String? = null): Timeline {
|
||||||
|
// val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
||||||
|
// val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy)
|
||||||
|
// val paginationTask = FakePaginationTask @Inject constructor(tokenChunkEventPersistor)
|
||||||
|
// val getContextOfEventTask = FakeGetContextOfEventTask @Inject constructor(tokenChunkEventPersistor)
|
||||||
|
// val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID)
|
||||||
|
// val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
|
||||||
|
// return DefaultTimeline(
|
||||||
|
// ROOM_ID,
|
||||||
|
// initialEventId,
|
||||||
|
// monarchy.realmConfiguration,
|
||||||
|
// taskExecutor,
|
||||||
|
// getContextOfEventTask,
|
||||||
|
// timelineEventFactory,
|
||||||
|
// paginationTask,
|
||||||
|
// null)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// fun backPaginate_shouldLoadMoreEvents_whenPaginateIsCalled() {
|
||||||
|
// val timeline = createTimeline()
|
||||||
|
// timeline.start()
|
||||||
|
// val paginationCount = 30
|
||||||
|
// var initialLoad = 0
|
||||||
|
// val latch = CountDownLatch(2)
|
||||||
|
// var timelineEvents: List<TimelineEvent> = emptyList()
|
||||||
|
// timeline.listener = object : Timeline.Listener {
|
||||||
|
// override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
// if (snapshot.isNotEmpty()) {
|
||||||
|
// if (initialLoad == 0) {
|
||||||
|
// initialLoad = snapshot.size
|
||||||
|
// }
|
||||||
|
// timelineEvents = snapshot
|
||||||
|
// latch.countDown()
|
||||||
|
// timeline.paginate(Timeline.Direction.BACKWARDS, paginationCount)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// latch.await()
|
||||||
|
// timelineEvents.size shouldEqual initialLoad + paginationCount
|
||||||
|
// timeline.dispose()
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Jeff Gilfelt.
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.network.interceptors
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.di.MatrixScope
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import okio.Buffer
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OkHttp interceptor that logs requests as curl shell commands. They can then
|
||||||
|
* be copied, pasted and executed inside a terminal environment. This might be
|
||||||
|
* useful for troubleshooting client/server API interaction during development,
|
||||||
|
* making it easy to isolate and share requests made by the app. <p> Warning: The
|
||||||
|
* logs generated by this interceptor have the potential to leak sensitive
|
||||||
|
* information. It should only be used in a controlled manner or in a
|
||||||
|
* non-production environment.
|
||||||
|
*/
|
||||||
|
@MatrixScope
|
||||||
|
internal class CurlLoggingInterceptor @Inject constructor(private val logger: HttpLoggingInterceptor.Logger)
|
||||||
|
: Interceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set any additional curl command options (see 'curl --help').
|
||||||
|
*/
|
||||||
|
var curlOptions: String? = null
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
|
||||||
|
var compressed = false
|
||||||
|
|
||||||
|
var curlCmd = "curl"
|
||||||
|
if (curlOptions != null) {
|
||||||
|
curlCmd += " " + curlOptions!!
|
||||||
|
}
|
||||||
|
curlCmd += " -X " + request.method()
|
||||||
|
|
||||||
|
val requestBody = request.body()
|
||||||
|
if (requestBody != null) {
|
||||||
|
val buffer = Buffer()
|
||||||
|
requestBody.writeTo(buffer)
|
||||||
|
var charset: Charset? = UTF8
|
||||||
|
val contentType = requestBody.contentType()
|
||||||
|
if (contentType != null) {
|
||||||
|
charset = contentType.charset(UTF8)
|
||||||
|
}
|
||||||
|
// try to keep to a single line and use a subshell to preserve any line breaks
|
||||||
|
curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
val headers = request.headers()
|
||||||
|
var i = 0
|
||||||
|
val count = headers.size()
|
||||||
|
while (i < count) {
|
||||||
|
val name = headers.name(i)
|
||||||
|
val value = headers.value(i)
|
||||||
|
if ("Accept-Encoding".equals(name, ignoreCase = true) && "gzip".equals(value, ignoreCase = true)) {
|
||||||
|
compressed = true
|
||||||
|
}
|
||||||
|
curlCmd += " -H \"$name: $value\""
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
curlCmd += ((if (compressed) " --compressed " else " ") + "'" + request.url().toString()
|
||||||
|
// Replace localhost for emulator by localhost for shell
|
||||||
|
.replace("://10.0.2.2:8080/".toRegex(), "://127.0.0.1:8080/")
|
||||||
|
+ "'")
|
||||||
|
|
||||||
|
// Add Json formatting
|
||||||
|
curlCmd += " | python -m json.tool"
|
||||||
|
|
||||||
|
logger.log("--- cURL (" + request.url() + ")")
|
||||||
|
logger.log(curlCmd)
|
||||||
|
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val UTF8 = Charset.forName("UTF-8")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.network.interceptors
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import im.vector.matrix.android.BuildConfig
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val INDENT_SPACE = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the message and try to log it again as a JSON formatted string
|
||||||
|
* Note: it can consume a lot of memory but it is only in DEBUG mode
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
override fun log(@NonNull message: String) {
|
||||||
|
// In RELEASE there is no log, but for sure, test again BuildConfig.DEBUG
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Timber.v(message)
|
||||||
|
|
||||||
|
if (message.startsWith("{")) {
|
||||||
|
// JSON Detected
|
||||||
|
try {
|
||||||
|
val o = JSONObject(message)
|
||||||
|
logJson(o.toString(INDENT_SPACE))
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
// Finally this is not a JSON string...
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (message.startsWith("[")) {
|
||||||
|
// JSON Array detected
|
||||||
|
try {
|
||||||
|
val o = JSONArray(message)
|
||||||
|
logJson(o.toString(INDENT_SPACE))
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
// Finally not JSON...
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// Else not a json string to log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logJson(formattedJson: String) {
|
||||||
|
val arr = formattedJson.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
for (s in arr) {
|
||||||
|
Timber.v(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,19 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="im.vector.matrix.android" />
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="im.vector.matrix.android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<provider android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
|
android:exported="false"
|
||||||
|
tools:node="remove" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import androidx.work.Configuration
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.BuildConfig
|
||||||
|
import im.vector.matrix.android.api.auth.Authenticator
|
||||||
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
|
import im.vector.matrix.android.internal.di.DaggerMatrixComponent
|
||||||
|
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||||
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
|
import org.matrix.olm.OlmManager
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
data class MatrixConfiguration(
|
||||||
|
val applicationFlavor: String = "Default-application-flavor"
|
||||||
|
) {
|
||||||
|
|
||||||
|
interface Provider {
|
||||||
|
fun providesMatrixConfiguration(): MatrixConfiguration
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the main entry point to the matrix sdk.
|
||||||
|
* To get the singleton instance, use getInstance static method.
|
||||||
|
*/
|
||||||
|
class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||||
|
|
||||||
|
@Inject internal lateinit var authenticator: Authenticator
|
||||||
|
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||||
|
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||||
|
@Inject internal lateinit var olmManager: OlmManager
|
||||||
|
@Inject internal lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
init {
|
||||||
|
Monarchy.init(context)
|
||||||
|
DaggerMatrixComponent.factory().create(context).inject(this)
|
||||||
|
if (context.applicationContext !is Configuration.Provider) {
|
||||||
|
WorkManager.initialize(context, Configuration.Builder().build())
|
||||||
|
}
|
||||||
|
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||||
|
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserAgent() = userAgentHolder.userAgent
|
||||||
|
|
||||||
|
fun authenticator(): Authenticator {
|
||||||
|
return authenticator
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private lateinit var instance: Matrix
|
||||||
|
private val isInit = AtomicBoolean(false)
|
||||||
|
|
||||||
|
fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||||
|
if (isInit.compareAndSet(false, true)) {
|
||||||
|
instance = Matrix(context.applicationContext, matrixConfiguration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(context: Context): Matrix {
|
||||||
|
if (isInit.compareAndSet(false, true)) {
|
||||||
|
val appContext = context.applicationContext
|
||||||
|
if (appContext is MatrixConfiguration.Provider) {
|
||||||
|
val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
|
||||||
|
instance = Matrix(appContext, matrixConfiguration)
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("Matrix is not initialized properly." +
|
||||||
|
" You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSdkVersion(): String {
|
||||||
|
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic callback interface for asynchronously.
|
||||||
|
* @param <T> the type of data to return on success
|
||||||
|
*/
|
||||||
|
interface MatrixCallback<in T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On success method, default to no-op
|
||||||
|
* @param data the data successfully returned from the async function
|
||||||
|
*/
|
||||||
|
fun onSuccess(data: T) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On failure method, default to no-op
|
||||||
|
* @param failure the failure data returned from the async function
|
||||||
|
*/
|
||||||
|
fun onFailure(failure: Throwable) {
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains pattern to match the different Matrix ids
|
||||||
|
*/
|
||||||
|
object MatrixPatterns {
|
||||||
|
|
||||||
|
// Note: TLD is not mandatory (localhost, IP address...)
|
||||||
|
private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?"
|
||||||
|
|
||||||
|
// regex pattern to find matrix user ids in a string.
|
||||||
|
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
|
||||||
|
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// regex pattern to find room ids in a string.
|
||||||
|
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// regex pattern to find room aliases in a string.
|
||||||
|
private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// regex pattern to find message ids in a string.
|
||||||
|
private const val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// regex pattern to find message ids in a string.
|
||||||
|
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// Ref: https://matrix.org/docs/spec/rooms/v4#event-ids
|
||||||
|
private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// regex pattern to find group ids in a string.
|
||||||
|
private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = MATRIX_GROUP_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// regex pattern to find permalink with message id.
|
||||||
|
// Android does not support in URL so extract it.
|
||||||
|
private const val PERMALINK_BASE_REGEX = "https://matrix\\.to/#/"
|
||||||
|
private const val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/"
|
||||||
|
const val SEP_REGEX = "/"
|
||||||
|
|
||||||
|
private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||||
|
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||||
|
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||||
|
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
// list of patterns to find some matrix item.
|
||||||
|
val MATRIX_PATTERNS = listOf(
|
||||||
|
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID,
|
||||||
|
PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS,
|
||||||
|
PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID,
|
||||||
|
PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS,
|
||||||
|
PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER,
|
||||||
|
PATTERN_CONTAIN_MATRIX_ALIAS,
|
||||||
|
PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER,
|
||||||
|
PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER,
|
||||||
|
PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if a string is a valid user Id.
|
||||||
|
*
|
||||||
|
* @param str the string to test
|
||||||
|
* @return true if the string is a valid user id
|
||||||
|
*/
|
||||||
|
fun isUserId(str: String?): Boolean {
|
||||||
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if a string is a valid room id.
|
||||||
|
*
|
||||||
|
* @param str the string to test
|
||||||
|
* @return true if the string is a valid room Id
|
||||||
|
*/
|
||||||
|
fun isRoomId(str: String?): Boolean {
|
||||||
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if a string is a valid room alias.
|
||||||
|
*
|
||||||
|
* @param str the string to test
|
||||||
|
* @return true if the string is a valid room alias.
|
||||||
|
*/
|
||||||
|
fun isRoomAlias(str: String?): Boolean {
|
||||||
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if a string is a valid event id.
|
||||||
|
*
|
||||||
|
* @param str the string to test
|
||||||
|
* @return true if the string is a valid event id.
|
||||||
|
*/
|
||||||
|
fun isEventId(str: String?): Boolean {
|
||||||
|
return str != null
|
||||||
|
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|
||||||
|
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|
||||||
|
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if a string is a valid group id.
|
||||||
|
*
|
||||||
|
* @param str the string to test
|
||||||
|
* @return true if the string is a valid group id.
|
||||||
|
*/
|
||||||
|
fun isGroupId(str: String?): Boolean {
|
||||||
|
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.auth
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines methods to authenticate to a matrix server.
|
||||||
|
*/
|
||||||
|
interface Authenticator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param homeServerConnectionConfig this param is used to configure the Homeserver
|
||||||
|
* @param login the login field
|
||||||
|
* @param password the password field
|
||||||
|
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||||
|
* @return return a [Cancelable]
|
||||||
|
*/
|
||||||
|
fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is an authenticated [Session].
|
||||||
|
* @return true if there is at least one active session.
|
||||||
|
*/
|
||||||
|
fun hasAuthenticatedSessions(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last authenticated [Session], if there is an active session.
|
||||||
|
* @return the last active session if any, or null
|
||||||
|
*/
|
||||||
|
fun getLastAuthenticatedSession(): Session?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an authenticated session. You should at least call authenticate one time before.
|
||||||
|
* If you logout, this session will no longer be valid.
|
||||||
|
*
|
||||||
|
* @param sessionParams the sessionParams to open with.
|
||||||
|
* @return the associated session if any, or null
|
||||||
|
*/
|
||||||
|
fun getSession(sessionParams: SessionParams): Session?
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This data class hold credentials user data.
|
||||||
|
* You shouldn't have to instantiate it.
|
||||||
|
* The access token should be use to authenticate user in all server requests.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class Credentials(
|
||||||
|
@Json(name = "user_id") val userId: String,
|
||||||
|
@Json(name = "home_server") val homeServer: String,
|
||||||
|
@Json(name = "access_token") val accessToken: String,
|
||||||
|
@Json(name = "refresh_token") val refreshToken: String?,
|
||||||
|
@Json(name = "device_id") val deviceId: String?)
|
@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig.Builder
|
||||||
|
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||||
|
import okhttp3.CipherSuite
|
||||||
|
import okhttp3.TlsVersion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This data class holds how to connect to a specific Homeserver.
|
||||||
|
* It's used with [im.vector.matrix.android.api.auth.Authenticator] class.
|
||||||
|
* You should use the [Builder] to create one.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class HomeServerConnectionConfig(
|
||||||
|
val homeServerUri: Uri,
|
||||||
|
val identityServerUri: Uri,
|
||||||
|
val antiVirusServerUri: Uri? = null,
|
||||||
|
val allowedFingerprints: MutableList<Fingerprint> = ArrayList(),
|
||||||
|
val shouldPin: Boolean = false,
|
||||||
|
val tlsVersions: MutableList<TlsVersion>? = null,
|
||||||
|
val tlsCipherSuites: MutableList<CipherSuite>? = null,
|
||||||
|
val shouldAcceptTlsExtensions: Boolean = true,
|
||||||
|
val allowHttpExtension: Boolean = false,
|
||||||
|
val forceUsageTlsVersions: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This builder should be use to create a [HomeServerConnectionConfig] instance.
|
||||||
|
*/
|
||||||
|
class Builder {
|
||||||
|
|
||||||
|
private lateinit var homeServerUri: Uri
|
||||||
|
private lateinit var identityServerUri: Uri
|
||||||
|
private var antiVirusServerUri: Uri? = null
|
||||||
|
private val allowedFingerprints: MutableList<Fingerprint> = ArrayList()
|
||||||
|
private var shouldPin: Boolean = false
|
||||||
|
private val tlsVersions: MutableList<TlsVersion> = ArrayList()
|
||||||
|
private val tlsCipherSuites: MutableList<CipherSuite> = ArrayList()
|
||||||
|
private var shouldAcceptTlsExtensions: Boolean = true
|
||||||
|
private var allowHttpExtension: Boolean = false
|
||||||
|
private var forceUsageTlsVersions: Boolean = false
|
||||||
|
|
||||||
|
fun withHomeServerUri(hsUriString: String): Builder {
|
||||||
|
return withHomeServerUri(Uri.parse(hsUriString))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param hsUri The URI to use to connect to the homeserver.
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun withHomeServerUri(hsUri: Uri): Builder {
|
||||||
|
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
|
||||||
|
throw RuntimeException("Invalid home server URI: " + hsUri)
|
||||||
|
}
|
||||||
|
// ensure trailing /
|
||||||
|
homeServerUri = if (!hsUri.toString().endsWith("/")) {
|
||||||
|
try {
|
||||||
|
val url = hsUri.toString()
|
||||||
|
Uri.parse("$url/")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hsUri
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withIdentityServerUri(identityServerUriString: String): Builder {
|
||||||
|
return withIdentityServerUri(Uri.parse(identityServerUriString))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param identityServerUri The URI to use to manage identity.
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun withIdentityServerUri(identityServerUri: Uri): Builder {
|
||||||
|
if (identityServerUri.scheme != "http" && identityServerUri.scheme != "https") {
|
||||||
|
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||||
|
}
|
||||||
|
// ensure trailing /
|
||||||
|
if (!identityServerUri.toString().endsWith("/")) {
|
||||||
|
try {
|
||||||
|
val url = identityServerUri.toString()
|
||||||
|
this.identityServerUri = Uri.parse("$url/")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Invalid identity server URI: $identityServerUri")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.identityServerUri = identityServerUri
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param allowedFingerprints If using SSL, allow server certs that match these fingerprints.
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun withAllowedFingerPrints(allowedFingerprints: List<Fingerprint>?): Builder {
|
||||||
|
if (allowedFingerprints != null) {
|
||||||
|
this.allowedFingerprints.addAll(allowedFingerprints)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pin If true only allow certs matching given fingerprints, otherwise fallback to
|
||||||
|
* standard X509 checks.
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun withPin(pin: Boolean): Builder {
|
||||||
|
this.shouldPin = pin
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param shouldAcceptTlsExtension
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun withShouldAcceptTlsExtensions(shouldAcceptTlsExtension: Boolean): Builder {
|
||||||
|
this.shouldAcceptTlsExtensions = shouldAcceptTlsExtension
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an accepted TLS version for TLS connections with the home server.
|
||||||
|
*
|
||||||
|
* @param tlsVersion the tls version to add to the set of TLS versions accepted.
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun addAcceptedTlsVersion(tlsVersion: TlsVersion): Builder {
|
||||||
|
this.tlsVersions.add(tlsVersion)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force the usage of TlsVersion. This can be usefull for device on Android version < 20
|
||||||
|
*
|
||||||
|
* @param forceUsageOfTlsVersions set to true to force the usage of specified TlsVersions (with [.addAcceptedTlsVersion]
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun forceUsageOfTlsVersions(forceUsageOfTlsVersions: Boolean): Builder {
|
||||||
|
this.forceUsageTlsVersions = forceUsageOfTlsVersions
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a TLS cipher suite to the list of accepted TLS connections with the home server.
|
||||||
|
*
|
||||||
|
* @param tlsCipherSuite the tls cipher suite to add.
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun addAcceptedTlsCipherSuite(tlsCipherSuite: CipherSuite): Builder {
|
||||||
|
this.tlsCipherSuites.add(tlsCipherSuite)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withAntiVirusServerUri(antivirusServerUriString: String?): Builder {
|
||||||
|
return withAntiVirusServerUri(antivirusServerUriString?.let { Uri.parse(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the anti-virus server URI.
|
||||||
|
*
|
||||||
|
* @param antivirusServerUri the new anti-virus uri. Can be null
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun withAntiVirusServerUri(antivirusServerUri: Uri?): Builder {
|
||||||
|
if (null != antivirusServerUri && "http" != antivirusServerUri.scheme && "https" != antivirusServerUri.scheme) {
|
||||||
|
throw RuntimeException("Invalid antivirus server URI: $antivirusServerUri")
|
||||||
|
}
|
||||||
|
this.antiVirusServerUri = antivirusServerUri
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenient method to limit the TLS versions and cipher suites for this Builder
|
||||||
|
* Ref:
|
||||||
|
* - https://www.ssi.gouv.fr/uploads/2017/02/security-recommendations-for-tls_v1.1.pdf
|
||||||
|
* - https://developer.android.com/reference/javax/net/ssl/SSLEngine
|
||||||
|
*
|
||||||
|
* @param tlsLimitations true to use Tls limitations
|
||||||
|
* @param enableCompatibilityMode set to true for Android < 20
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
fun withTlsLimitations(tlsLimitations: Boolean, enableCompatibilityMode: Boolean): Builder {
|
||||||
|
if (tlsLimitations) {
|
||||||
|
withShouldAcceptTlsExtensions(false)
|
||||||
|
|
||||||
|
// Tls versions
|
||||||
|
addAcceptedTlsVersion(TlsVersion.TLS_1_2)
|
||||||
|
addAcceptedTlsVersion(TlsVersion.TLS_1_3)
|
||||||
|
|
||||||
|
forceUsageOfTlsVersions(enableCompatibilityMode)
|
||||||
|
|
||||||
|
// Cipher suites
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
|
||||||
|
|
||||||
|
if (enableCompatibilityMode) {
|
||||||
|
// Adopt some preceding cipher suites for Android < 20 to be able to negotiate
|
||||||
|
// a TLS session.
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
|
||||||
|
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withAllowHttpConnection(allowHttpExtension: Boolean): Builder {
|
||||||
|
this.allowHttpExtension = allowHttpExtension
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the [HomeServerConnectionConfig]
|
||||||
|
*/
|
||||||
|
fun build(): HomeServerConnectionConfig {
|
||||||
|
return HomeServerConnectionConfig(
|
||||||
|
homeServerUri,
|
||||||
|
identityServerUri,
|
||||||
|
antiVirusServerUri,
|
||||||
|
allowedFingerprints,
|
||||||
|
shouldPin,
|
||||||
|
tlsVersions,
|
||||||
|
tlsCipherSuites,
|
||||||
|
shouldAcceptTlsExtensions,
|
||||||
|
allowHttpExtension,
|
||||||
|
forceUsageTlsVersions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This data class holds necessary data to open a session.
|
||||||
|
* You don't have to manually instantiate it.
|
||||||
|
*/
|
||||||
|
data class SessionParams(
|
||||||
|
val credentials: Credentials,
|
||||||
|
val homeServerConnectionConfig: HomeServerConnectionConfig
|
||||||
|
)
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.comparators
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.interfaces.DatedObject
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object DatedObjectComparators {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator to sort DatedObjects from the oldest to the latest.
|
||||||
|
*/
|
||||||
|
val ascComparator by lazy {
|
||||||
|
Comparator<DatedObject> { datedObject1, datedObject2 ->
|
||||||
|
(datedObject1.date - datedObject2.date).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator to sort DatedObjects from the latest to the oldest.
|
||||||
|
*/
|
||||||
|
val descComparator by lazy {
|
||||||
|
Comparator<DatedObject> { datedObject1, datedObject2 ->
|
||||||
|
(datedObject2.date - datedObject1.date).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.extensions
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* MXDeviceInfo
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||||
|
?.chunked(4)
|
||||||
|
?.joinToString(separator = " ")
|
||||||
|
|
||||||
|
|
||||||
|
fun List<DeviceInfo>.sortByLastSeen() {
|
||||||
|
Collections.sort(this, DatedObjectComparators.descComparator)
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.failure
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class allows to expose different kinds of error to be then handled by the application.
|
||||||
|
* As it is a sealed class, you typically use it like that :
|
||||||
|
* when(failure) {
|
||||||
|
* is NetworkConnection -> Unit
|
||||||
|
* is ServerError -> Unit
|
||||||
|
* is Unknown -> Unit
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
|
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||||
|
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||||
|
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||||
|
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||||
|
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))
|
||||||
|
|
||||||
|
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||||
|
|
||||||
|
data class CryptoError(val error: MXCryptoError) : Failure(error)
|
||||||
|
|
||||||
|
abstract class FeatureFailure : Failure()
|
||||||
|
|
||||||
|
}
|
@ -1,14 +1,39 @@
|
|||||||
package im.vector.matrix.core.api.failure
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.failure
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
data class MatrixError(@Json(name = "errcode") val code: String,
|
/**
|
||||||
@Json(name = "error") val message: String) {
|
* This data class holds the error defined by the matrix specifications.
|
||||||
|
* You shouldn't have to instantiate it.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MatrixError(
|
||||||
|
@Json(name = "errcode") val code: String,
|
||||||
|
@Json(name = "error") val message: String
|
||||||
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val FORBIDDEN = "M_FORBIDDEN"
|
const val FORBIDDEN = "M_FORBIDDEN"
|
||||||
const val UNKNOWN = "M_UNKNOWN"
|
const val UNKNOWN = "M_UNKNOWN"
|
||||||
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||||
|
const val MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||||
const val BAD_JSON = "M_BAD_JSON"
|
const val BAD_JSON = "M_BAD_JSON"
|
||||||
const val NOT_JSON = "M_NOT_JSON"
|
const val NOT_JSON = "M_NOT_JSON"
|
||||||
const val NOT_FOUND = "M_NOT_FOUND"
|
const val NOT_FOUND = "M_NOT_FOUND"
|
||||||
@ -29,5 +54,6 @@ data class MatrixError(@Json(name = "errcode") val code: String,
|
|||||||
const val TOO_LARGE = "M_TOO_LARGE"
|
const val TOO_LARGE = "M_TOO_LARGE"
|
||||||
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||||
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||||
|
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.interfaces
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be implemented by any object containing a timestamp.
|
||||||
|
* This interface can be use to sort such object
|
||||||
|
*/
|
||||||
|
interface DatedObject {
|
||||||
|
val date: Long
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.listeners
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to send a progress info
|
||||||
|
*/
|
||||||
|
interface ProgressListener {
|
||||||
|
/**
|
||||||
|
* @param progress from 0 to total by contract
|
||||||
|
* @param total
|
||||||
|
*/
|
||||||
|
fun onProgress(progress: Int, total: Int)
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.listeners
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to send a progress info
|
||||||
|
*/
|
||||||
|
interface StepProgressListener {
|
||||||
|
|
||||||
|
sealed class Step {
|
||||||
|
data class ComputingKey(val progress: Int, val total: Int) : Step()
|
||||||
|
object DownloadingKey : Step()
|
||||||
|
data class ImportingKey(val progress: Int, val total: Int) : Step()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param step The current step, containing progress data if available. Else you should consider progress as indeterminate
|
||||||
|
*/
|
||||||
|
fun onStepProgress(step: Step)
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.permalinks
|
||||||
|
|
||||||
|
import android.text.Spannable
|
||||||
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MatrixLinkify take a piece of text and turns all of the
|
||||||
|
* matrix patterns matches in the text into clickable links.
|
||||||
|
*/
|
||||||
|
object MatrixLinkify {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the matrix spans i.e matrix id , user id ... to display them as URL.
|
||||||
|
*
|
||||||
|
* @param spannable the text in which the matrix items has to be clickable.
|
||||||
|
*/
|
||||||
|
fun addLinks(spannable: Spannable?, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||||
|
// sanity checks
|
||||||
|
if (spannable.isNullOrEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val text = spannable.toString()
|
||||||
|
var hasMatch = false
|
||||||
|
for (pattern in MatrixPatterns.MATRIX_PATTERNS) {
|
||||||
|
for (match in pattern.findAll(spannable)) {
|
||||||
|
hasMatch = true
|
||||||
|
val startPos = match.range.first
|
||||||
|
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||||
|
val endPos = match.range.last
|
||||||
|
val url = text.substring(match.range)
|
||||||
|
val span = MatrixPermalinkSpan(url, callback)
|
||||||
|
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.permalinks
|
||||||
|
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back.
|
||||||
|
* @param url the permalink url tied to the span
|
||||||
|
* @param callback the callback to use.
|
||||||
|
*/
|
||||||
|
class MatrixPermalinkSpan(private val url: String,
|
||||||
|
private val callback: Callback? = null) : ClickableSpan() {
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onUrlClicked(url: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(widget: View) {
|
||||||
|
callback?.onUrlClicked(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.permalinks
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sealed class represents all the permalink cases.
|
||||||
|
* You don't have to instantiate yourself but should use [PermalinkParser] instead.
|
||||||
|
*/
|
||||||
|
sealed class PermalinkData {
|
||||||
|
|
||||||
|
data class EventLink(val roomIdOrAlias: String, val eventId: String) : PermalinkData()
|
||||||
|
|
||||||
|
data class RoomLink(val roomIdOrAlias: String) : PermalinkData()
|
||||||
|
|
||||||
|
data class UserLink(val userId: String) : PermalinkData()
|
||||||
|
|
||||||
|
data class GroupLink(val groupId: String) : PermalinkData()
|
||||||
|
|
||||||
|
data class FallbackLink(val uri: Uri) : PermalinkData()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.permalinks
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful methods to create Matrix permalink.
|
||||||
|
*/
|
||||||
|
object PermalinkFactory {
|
||||||
|
|
||||||
|
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a permalink for an event.
|
||||||
|
* Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org"
|
||||||
|
*
|
||||||
|
* @param event the event
|
||||||
|
* @return the permalink, or null in case of error
|
||||||
|
*/
|
||||||
|
fun createPermalink(event: Event): String? {
|
||||||
|
if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return createPermalink(event.roomId, event.eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a permalink for an id (can be a user Id, Room Id, etc.).
|
||||||
|
* Ex: "https://matrix.to/#/@benoit:matrix.org"
|
||||||
|
*
|
||||||
|
* @param id the id
|
||||||
|
* @return the permalink, or null in case of error
|
||||||
|
*/
|
||||||
|
fun createPermalink(id: String): String? {
|
||||||
|
return if (TextUtils.isEmpty(id)) {
|
||||||
|
null
|
||||||
|
} else MATRIX_TO_URL_BASE + escape(id)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a permalink for an event. If you have an event you can use [.createPermalink]
|
||||||
|
* Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org"
|
||||||
|
*
|
||||||
|
* @param roomId the id of the room
|
||||||
|
* @param eventId the id of the event
|
||||||
|
* @return the permalink
|
||||||
|
*/
|
||||||
|
fun createPermalink(roomId: String, eventId: String): String {
|
||||||
|
return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the linked id from the universal link
|
||||||
|
*
|
||||||
|
* @param url the universal link, Ex: "https://matrix.to/#/@benoit:matrix.org"
|
||||||
|
* @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink
|
||||||
|
*/
|
||||||
|
fun getLinkedId(url: String?): String? {
|
||||||
|
val isSupported = url != null && url.startsWith(MATRIX_TO_URL_BASE)
|
||||||
|
|
||||||
|
return if (isSupported) {
|
||||||
|
url!!.substring(MATRIX_TO_URL_BASE.length)
|
||||||
|
} else null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape '/' in id, because it is used as a separator
|
||||||
|
*
|
||||||
|
* @param id the id to escape
|
||||||
|
* @return the escaped id
|
||||||
|
*/
|
||||||
|
private fun escape(id: String): String {
|
||||||
|
return id.replace("/".toRegex(), "%2F")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.api.permalinks
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class turns an uri to a [PermalinkData]
|
||||||
|
*/
|
||||||
|
object PermalinkParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns an uri string to a [PermalinkData]
|
||||||
|
*/
|
||||||
|
fun parse(uriString: String): PermalinkData {
|
||||||
|
val uri = Uri.parse(uriString)
|
||||||
|
return parse(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns an uri to a [PermalinkData]
|
||||||
|
*/
|
||||||
|
fun parse(uri: Uri): PermalinkData {
|
||||||
|
if (!uri.toString().startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) {
|
||||||
|
return PermalinkData.FallbackLink(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
val fragment = uri.fragment
|
||||||
|
if (fragment.isNullOrEmpty()) {
|
||||||
|
return PermalinkData.FallbackLink(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
val indexOfQuery = fragment.indexOf("?")
|
||||||
|
val safeFragment = if (indexOfQuery != -1) fragment.substring(0, indexOfQuery) else fragment
|
||||||
|
|
||||||
|
// we are limiting to 2 params
|
||||||
|
val params = safeFragment
|
||||||
|
.split(MatrixPatterns.SEP_REGEX.toRegex())
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.take(2)
|
||||||
|
|
||||||
|
val identifier = params.getOrNull(0)
|
||||||
|
val extraParameter = params.getOrNull(1)
|
||||||
|
if (identifier.isNullOrEmpty()) {
|
||||||
|
return PermalinkData.FallbackLink(uri)
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||||
|
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||||
|
MatrixPatterns.isRoomId(identifier) -> {
|
||||||
|
if (!extraParameter.isNullOrEmpty() && MatrixPatterns.isEventId(extraParameter)) {
|
||||||
|
PermalinkData.EventLink(roomIdOrAlias = identifier, eventId = extraParameter)
|
||||||
|
} else {
|
||||||
|
PermalinkData.RoomLink(roomIdOrAlias = identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> PermalinkData.FallbackLink(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.api.pushrules
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
|
class Action(val type: Type) {
|
||||||
|
|
||||||
|
enum class Type(val value: String) {
|
||||||
|
NOTIFY("notify"),
|
||||||
|
DONT_NOTIFY("dont_notify"),
|
||||||
|
COALESCE("coalesce"),
|
||||||
|
SET_TWEAK("set_tweak");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun safeValueOf(value: String): Type? {
|
||||||
|
try {
|
||||||
|
return valueOf(value)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tweak_action: String? = null
|
||||||
|
var stringValue: String? = null
|
||||||
|
var boolValue: Boolean? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun mapFrom(pushRule: PushRule): List<Action>? {
|
||||||
|
val actions = ArrayList<Action>()
|
||||||
|
pushRule.actions.forEach { actionStrOrObj ->
|
||||||
|
if (actionStrOrObj is String) {
|
||||||
|
when (actionStrOrObj) {
|
||||||
|
Action.Type.NOTIFY.value -> Action(Action.Type.NOTIFY)
|
||||||
|
Action.Type.DONT_NOTIFY.value -> Action(Action.Type.DONT_NOTIFY)
|
||||||
|
else -> {
|
||||||
|
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
actions.add(it)
|
||||||
|
}
|
||||||
|
} else if (actionStrOrObj is Map<*, *>) {
|
||||||
|
val tweakAction = actionStrOrObj["set_tweak"] as? String
|
||||||
|
when (tweakAction) {
|
||||||
|
"sound" -> {
|
||||||
|
(actionStrOrObj["value"] as? String)?.let { stringValue ->
|
||||||
|
Action(Action.Type.SET_TWEAK).also {
|
||||||
|
it.tweak_action = "sound"
|
||||||
|
it.stringValue = stringValue
|
||||||
|
actions.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"highlight" -> {
|
||||||
|
(actionStrOrObj["value"] as? Boolean)?.let { boolValue ->
|
||||||
|
Action(Action.Type.SET_TWEAK).also {
|
||||||
|
it.tweak_action = "highlight"
|
||||||
|
it.boolValue = boolValue
|
||||||
|
actions.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.w("Unsupported action type ${actionStrOrObj}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return if (actions.isEmpty()) null else actions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|