Merge branch 'release/1.0.11'
This commit is contained in:
		
						commit
						dffdcfe1e4
					
				
							
								
								
									
										19
									
								
								AUTHORS.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								AUTHORS.md
									
									
									
									
									
								
							| @ -4,7 +4,7 @@ A full developer contributors list can be found [here](https://github.com/vector | |||||||
| 
 | 
 | ||||||
| Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves. | Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves. | ||||||
| 
 | 
 | ||||||
| ## Benoit: Android team leader | ## [Benoit](https://github.com/bmarty): Android team leader | ||||||
| 
 | 
 | ||||||
| [@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org) | [@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org) | ||||||
| - Android team leader and project leader, Android developer, GitHub community manager. | - Android team leader and project leader, Android developer, GitHub community manager. | ||||||
| @ -12,7 +12,7 @@ Even if we try to be able to work on all the functionalities, we have more knowl | |||||||
| - Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager. | - Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager. | ||||||
| - Release manager on the Play Store | - Release manager on the Play Store | ||||||
| 
 | 
 | ||||||
| ## François: Software architect | ## [Ganfra](https://github.com/ganfra) (aka François): Software architect | ||||||
| 
 | 
 | ||||||
| [@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org) | [@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org) | ||||||
| - Software architect, Android developer | - Software architect, Android developer | ||||||
| @ -20,12 +20,17 @@ Even if we try to be able to work on all the functionalities, we have more knowl | |||||||
| - Work mainly on the global architecture of the project. | - Work mainly on the global architecture of the project. | ||||||
| - Specialist of the timeline, and lots of other features. | - Specialist of the timeline, and lots of other features. | ||||||
| 
 | 
 | ||||||
| ## Valere: Product manager, Android developer | ## [Valere](https://github.com/BillCarsonFr): Product manager, Android developer | ||||||
| 
 | 
 | ||||||
| [@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org) | [@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org) | ||||||
| - Product manager, Android developer | - Product manager, Android developer | ||||||
| - Specialist on the crypto implementation. | - Specialist on the crypto implementation. | ||||||
| 
 | 
 | ||||||
|  | ## [Onuray](https://github.com/onurays): Android developer | ||||||
|  | 
 | ||||||
|  | [@onurays:matrix.org](https://matrix.to/#/@onurays:matrix.org) | ||||||
|  | - Android developer | ||||||
|  | 
 | ||||||
| # Other contributors | # Other contributors | ||||||
| 
 | 
 | ||||||
| First of all, we thank all contributors who use Element and report problems on this GitHub project or via the integrated rageshake function. | First of all, we thank all contributors who use Element and report problems on this GitHub project or via the integrated rageshake function. | ||||||
| @ -34,7 +39,7 @@ We do not forget all translators, for their work of translating Element into man | |||||||
| 
 | 
 | ||||||
| Feel free to add your name below, when you contribute to the project! | Feel free to add your name below, when you contribute to the project! | ||||||
| 
 | 
 | ||||||
| Name    | Matrix ID           | GitHub | Name      | Matrix ID                   | GitHub | ||||||
| --------|---------------------|-------------------------------------- | ----------|-----------------------------|-------------------------------------- | ||||||
| gjpower | @gjpower:matrix.org | [gjpower](https://github.com/gjpower) | gjpower   | @gjpower:matrix.org         | [gjpower](https://github.com/gjpower) | ||||||
| 
 | TR_SLimey | @tr_slimey:an-atom-in.space | [TR-SLimey](https://github.com/TR-SLimey) | ||||||
|  | |||||||
							
								
								
									
										45
									
								
								CHANGES.md
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								CHANGES.md
									
									
									
									
									
								
							| @ -1,3 +1,45 @@ | |||||||
|  | Changes in Element 1.0.11 (2020-11-27) | ||||||
|  | =================================================== | ||||||
|  | 
 | ||||||
|  | Features ✨: | ||||||
|  |  - Create DMs with users by scanning their QR code (#2025) | ||||||
|  |  - Add Invite friends quick invite actions (#2348) | ||||||
|  |  - Add friend by scanning QR code, show your code to friends (#2025) | ||||||
|  | 
 | ||||||
|  | Improvements 🙌: | ||||||
|  |  - New room creation tile with quick action (#2346) | ||||||
|  |  - Open an existing DM instead of creating a new one (#2319) | ||||||
|  |  - Use RoomMember instead of User in the context of a Room. | ||||||
|  |  - Ask for explicit user consent to send their contact details to the identity server (#2375) | ||||||
|  |  - Handle events of type "m.room.server_acl" (#890) | ||||||
|  |  - Room creation form: add advanced section to disable federation (#1314) | ||||||
|  |  - Move "Enable Encryption" from room setting screen to room profile screen (#2394) | ||||||
|  |  - Home empty screens quick design update (#2347) | ||||||
|  |  - Improve Invite user screen (seamless search for matrix ID) | ||||||
|  | 
 | ||||||
|  | Bugfix 🐛: | ||||||
|  |  - Fix crash on AttachmentViewer (#2365) | ||||||
|  |  - Exclude yourself when decorating rooms which are direct or don't have more than 2 users (#2370) | ||||||
|  |  - F-Droid version: ensure timeout of sync request can be more than 60 seconds (#2169) | ||||||
|  |  - Fix issue when restoring draft after sharing (#2287) | ||||||
|  |  - Fix issue when updating the avatar of a room (new avatar vanishing) | ||||||
|  |  - Discard change dialog displayed by mistake when avatar has been updated | ||||||
|  |  - Try to fix cropped image in timeline (#2126) | ||||||
|  |  - Registration: annoying error message scares every new user when they add an email (#2391) | ||||||
|  |  - Fix jitsi integration for those with non-vanilla dialler frameworks | ||||||
|  |  - Update profile has no effect if user is in zero rooms | ||||||
|  |  - Fix issues with matrix.to deep linking (#2349) | ||||||
|  | 
 | ||||||
|  | SDK API changes ⚠️: | ||||||
|  |  - AccountService now exposes suspendable function instead of using MatrixCallback (#2354). | ||||||
|  |    Note: We will incrementally migrate all the SDK API in a near future (#2449) | ||||||
|  | 
 | ||||||
|  | Test: | ||||||
|  |  - Add `allScreensTest` to cover all screens of the app | ||||||
|  | 
 | ||||||
|  | Other changes: | ||||||
|  |  - Upgrade Realm dependency to 10.0.0 | ||||||
|  | 
 | ||||||
| Changes in Element 1.0.10 (2020-11-04) | Changes in Element 1.0.10 (2020-11-04) | ||||||
| =================================================== | =================================================== | ||||||
| 
 | 
 | ||||||
| @ -1008,5 +1050,8 @@ SDK API changes ⚠️: | |||||||
| Build 🧱: | Build 🧱: | ||||||
|  - |  - | ||||||
| 
 | 
 | ||||||
|  | Test: | ||||||
|  |  - | ||||||
|  | 
 | ||||||
| Other changes: | Other changes: | ||||||
|  - |  - | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/ca/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/ca/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // TODO | ||||||
							
								
								
									
										30
									
								
								fastlane/metadata/android/ca/full_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								fastlane/metadata/android/ca/full_description.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | Element és un nou tipus d'aplicació de missatgeria i col·laboració que: | ||||||
|  | 
 | ||||||
|  | 1. Et dóna a tu el control per preservar la teva privadesa | ||||||
|  | 2. Et permet comunicar-te amb qualsevol persona de la xarxa Matrix i, fins i tot més enllà gràcies a integracions amb altres aplicacions com Slack | ||||||
|  | 3. Et protegeix de la publicitat, l'obtenció no desitjada de dades i dels navegadors amb accés controlat | ||||||
|  | 4. T'assegura a tu mitjançant l'encriptació d'extrem a extrem i amb signatures creuades per verificar els altres | ||||||
|  | 
 | ||||||
|  | Element és completament diferent a les altres aplicacions de missatgeria i col·laboració ja que és descentralitzat i de codi obert. | ||||||
|  | 
 | ||||||
|  | Element et deixa triar l'allotjament perquè disposis de privadesa, propietat i control de les teves dades i converses. Et dóna accés a una xarxa oberta perquè no et quedis únicament parlant amb els usuaris d'Element. | ||||||
|  | 
 | ||||||
|  | Element pot fer tot això ja que opera sobre Matrix - l'estàndard per a les comunicacions obertes i descentralitzades. | ||||||
|  | 
 | ||||||
|  | Element et dóna el control perquè et deixa escollir qui vols que allotgi les teves converses. Des de l'aplicació d'Element, pots triar l'allotjament de diferents maneres: | ||||||
|  | 
 | ||||||
|  | 1. Crea un compte gratuït al servidor públic de matrix.org allotjat pels desenvolupadors de Matrix o tria'n un entre els milers de servidors públics creats per voluntaris | ||||||
|  | 2. Allotja tu mateix el teu compte en el teu propi servidor | ||||||
|  | 3. Registra el compte en un servidor personalitzat subscrivint-te a la plataforma d'Element Matrix Services (EMS) | ||||||
|  | 
 | ||||||
|  | <b>Per què escollir Element?</b> | ||||||
|  | 
 | ||||||
|  | <b>PROPIETAT DE LES TEVES DADES</b>: Tu decideixes a on desar les teves dades i missatges. Tu les controles i n'ets el propietari, no una mega-corporació que s'aprofita de les teves dades o les cedeix a tercers. | ||||||
|  | 
 | ||||||
|  | <b>MISSATGERIA I COL·LABORACIÓ OBERTA</b>: Pots parlar amb qualsevol que estigui a la xarxa Matrix, ja sigui amb Element o amb qualsevol altre aplicació Matrix, fins i tot encara que utilitzin sistemes de missatgeria diferents com Slack, IRC o XMPP. | ||||||
|  | 
 | ||||||
|  | <b>SUPER-SEGUR</b>: Encriptació d'extrem a extrem real (només qui està conversant pot desxifrar els missatges), i amb signatures creuades per a verificar els dispositius dels participants en les converses. | ||||||
|  | 
 | ||||||
|  | <b>COMUNICACIÓ COMPLETA</b>: Missatgeria, veu i video-trucades, compartició de fitxers, compartició de pantalla i un munt d'integracions, bots i ginys. Crea sales, comunitats, mantén-te en contacte i enllesteix el que et proposes. | ||||||
|  | 
 | ||||||
|  | <b>A TOT ARREU</b>: Mantingues el contacte des de qualsevol lloc on siguis, amb un historial de missatges totalment sincronitzat entre tots els teus dispositius i també a la web: https://app.element.io. | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/ca/short_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/ca/short_description.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Xat i VoIP segurs i descentralitzats. Protegeix les teves dades de tercers. | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/ca/title.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/ca/title.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Element (anteriorment Riot.im) | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/de/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/de/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // TODO | ||||||
							
								
								
									
										2
									
								
								fastlane/metadata/android/en-US/changelogs/40100110.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								fastlane/metadata/android/en-US/changelogs/40100110.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | This new version mainly contains user interface and user experience improvements. Now you can invite friends, and create DM very fast by scanning QR codes. | ||||||
|  | Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.0.11 | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/es/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/es/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // TODO | ||||||
| @ -1,30 +1,30 @@ | |||||||
| Element es un nuevo tipo de aplicación de mensajería y colaboración que: | Element es un nuevo tipo de aplicación de mensajería y colaboración que: | ||||||
| 
 | 
 | ||||||
| 1. Le da el control para preservar su privacidad  | 1. Te da el control para preservar su privacidad  | ||||||
| 2. Le permite comunicarse con cualquier persona en la red Matrix e incluso más allá al integrarse con aplicaciones como Slack. | 2. Te permite comunicarse con cualquier persona en la red Matrix e incluso más allá al integrarse con aplicaciones como Slack | ||||||
| 3. Te protege de la publicidad, la minería de datos y los jardines vallados.  | 3. Te protege de la publicidad, la minería de datos y los jardines vallados | ||||||
| 4. Lo protege a través del cifrado de un extremo a otro, con firma cruzada para verificar a otros | 4. Te protege a través de encriptación de Extremo-a-Extremo, con firma cruzada para verificar a otros | ||||||
| 
 | 
 | ||||||
| Element es completamente diferente de otras aplicaciones de mensajería y colaboración porque es descentralizado y de código abierto. | Element es completamente diferente de otras aplicaciones de mensajería y colaboración porque es descentralizado y de código abierto. | ||||||
| 
 | 
 | ||||||
| Element le permite autohospedarse, o elegir un host, para que tenga privacidad, propiedad y control de sus datos y conversaciones. Te da acceso a una red abierta; para que no se quede atascado hablando solo con otros usuarios de Element. Y es muy seguro. | Element te permite tener su propio servidor privado, o elegir uno público, para que tenga privacidad, posesión, y control de sus datos y conversaciones. Te da acceso a una red abierta; para que no se quede atrapado hablando solo con otros usuarios de Element. Y es muy seguro. | ||||||
| 
 | 
 | ||||||
| Element puede hacer todo esto porque opera en Matrix, el estándar para la comunicación abierta y descentralizada. | Element puede hacer todo esto porque opera en Matrix, el estándar para la comunicación abierta y descentralizada. | ||||||
| 
 | 
 | ||||||
| Element te da el control permitiéndote elegir quién aloja tus conversaciones. Desde la aplicación Element, puede elegir hospedar de diferentes maneras: | Element te da el control permitiéndote elegir quién aloja tus conversaciones. Desde la aplicación Element, puedes elegir hospedar de diferentes maneras: | ||||||
| 
 | 
 | ||||||
| 1. Obtenga una cuenta gratuita en el servidor público de matrix.org alojado por los desarrolladores de Matrix, o elija entre miles de servidores públicos alojados por voluntarios  | 1. Obtén una cuenta gratuita en el servidor público de matrix.org alojado por los desarrolladores de Matrix, o elije entre miles de servidores públicos alojados por voluntarios  | ||||||
| 2. Autohospede su cuenta ejecutando un servidor en su propio hardware  | 2. Autohospeda tu cuenta con un servidor en tu propio hardware  | ||||||
| 3. Regístrese para obtener una cuenta en un servidor personalizado simplemente suscribiéndose a la plataforma de alojamiento de Element Matrix Services | 3. Regístrate para obtener una cuenta en un servidor personalizado simplemente suscribiéndote a la plataforma de alojamiento de Element Matrix Services | ||||||
| 
 | 
 | ||||||
| <b>¿Por qué elegir Element?</b> | <b>¿Por qué elegir Element?</b> | ||||||
| 
 | 
 | ||||||
| <b>POSEE SUS DATOS</b>: Tú decides dónde guardar tus datos y mensajes. Usted es el propietario y lo controla, no algún MEGACORP que extraiga sus datos o dé acceso a terceros. | <b>TOMA POSESIÓN DE TUS DATOS</b>: Tú decides dónde guardar tus datos y mensajes. Tú eres el propietario y quien lo controla, no alguna MEGACORP que extrae tu datos o da acceso a terceros. | ||||||
| 
 | 
 | ||||||
| <b>MENSAJERÍA ABIERTA Y COLABORACIÓN</b>: Puede chatear con cualquier otra persona en la red de Matrix, ya sea que estén usando Element u otra aplicación de Matrix, e incluso si están usando un sistema de mensajería diferente como Slack, IRC o XMPP. | <b>MENSAJERÍA ABIERTA Y COLABORACIÓN</b>: Puede chatear con cualquier otra persona en la red de Matrix, tanto si usan Element u otra aplicación de Matrix, e incluso si están usando un sistema de mensajería diferente como Slack, IRC o XMPP. | ||||||
| 
 | 
 | ||||||
| <b>SUPER SEGURO</b>: Cifrado real de extremo a extremo (solo aquellos en la conversación pueden descifrar mensajes) y firma cruzada para verificar los dispositivos de los participantes de la conversación. | <b>SUPER SEGURO</b>: Encriptación de Extremo-a-Extremo real (solo aquellos en la conversación pueden descifrar mensajes) y firma cruzada para verificar los dispositivos de los participantes de la conversación. | ||||||
| 
 | 
 | ||||||
| <b>COMUNICACIÓN COMPLETA</b>: Mensajería, llamadas de voz y video, uso compartido de archivos, uso compartido de pantalla y un montón de integraciones, bots y widgets. Construya salas, comunidades, manténgase en contacto y haga las cosas. | <b>COMUNICACIÓN COMPLETA</b>: Mensajería, llamadas de voz y video, uso compartido de archivos, uso compartido de pantalla y un montón de integraciones, bots y widgets. Crea salas, comunidades, mantente en contacto y organízate con eficacia. | ||||||
| 
 | 
 | ||||||
| <b>EN TODAS PARTES</b>: Manténgase en contacto donde quiera que esté con un historial de mensajes totalmente sincronizado en todos sus dispositivos y en la web en https://app.element.io. | <b>EN TODAS PARTES</b>: Mantente en contacto donde quiera que estés con un historial de mensajes totalmente sincronizado en todos sus dispositivos y en la web en https://app.element.io. | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| Chat y VoIP descentralizados seguros. Mantenga sus datos a salvo de terceros. | Chat y VoIP descentralizados y seguros. Mantén tus datos a salvo de terceros. | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| Element (anteriorment Riot.im) | Element (previamente Riot.im) | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/fa/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/fa/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // برای انجام | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/it/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/it/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // DA FARE | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/nb/short_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/nb/short_description.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Sikker desentralisert chat & VoIP. Beskytt dataene dine fra tredjeparter. | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/nb/title.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/nb/title.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Element (tidligere Riot.im) | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/pt_BR/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/pt_BR/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // A FAZER | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/sv/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/sv/changelogs/40100100.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | // ATT GÖRA | ||||||
| @ -0,0 +1 @@ | |||||||
|  | // 待辦事項 | ||||||
							
								
								
									
										30
									
								
								fastlane/metadata/android/zh_Hant/full_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								fastlane/metadata/android/zh_Hant/full_description.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | Element 是一種新型態的即時通訊軟體與協作應用程式: | ||||||
|  | 
 | ||||||
|  | 1. 自己的隱私自己掌控 | ||||||
|  | 2. 讓您與任何在 Matrix 網路中的人通訊,甚至可與如 Slack 等的應用程式整合 | ||||||
|  | 3. 保護您免受廣告、資料採礦與圍牆花園的侵害 | ||||||
|  | 4. 透過端到端加密保護您,並使用交叉簽章來驗證其他人 | ||||||
|  | 
 | ||||||
|  | Element 是去中心化且開放原始碼的應用程式,因此與其他即時通訊與協作軟體完全不同。 | ||||||
|  | 
 | ||||||
|  | Element 讓您可以自架(或是自行選擇服務提供者)所以您擁有您資料與對話的隱私、所有權與控制權。它讓您可以存取開放的網路;因此,您不僅可以與其他 Matrix 使用者聊天。而且非常安全。 | ||||||
|  | 
 | ||||||
|  | Element 能作到這些事情是因為它在 Matrix 上執行,這是一個開放的去中心化通訊的標準。 | ||||||
|  | 
 | ||||||
|  | Element 讓您選擇您要在哪裡託管您的對話來將控制權還給您。在 Element 應用程式中,您可以選擇其他方式來託管: | ||||||
|  | 
 | ||||||
|  | 1. 在由 Matrix 開發者架設的 matrix.org 公開伺服器上取得免費的帳號,或是從數千個由志願者所架設的公開伺服器中選擇 | ||||||
|  | 2. 在您自己的硬體上自行架設伺服器並建立帳號 | ||||||
|  | 3. 訂閱 Element Matrix 服務託管平台並在自訂伺服氣上註冊帳號 | ||||||
|  | 
 | ||||||
|  | <b>為何選擇 Element?</b> | ||||||
|  | 
 | ||||||
|  | <b>擁有您的資料</b>:您決定您的資料與訊息要放在哪裡。您擁有並控制它,而非某些科技巨頭會挖掘您的資料並將其售予第三方。 | ||||||
|  | 
 | ||||||
|  | <b>開放的即時通訊與協作</b>:您可以與 Matrix 網路中的任何人聊天,不管他們是使用 Element 或其他 Matrix 應用程式都可以,或甚至是其他的訊息系統,如 Slack、IRC 或 XMPP 也都可以。 | ||||||
|  | 
 | ||||||
|  | <b>超級安全</b>:即時的端到端加密(僅有參與對話的人可以解密訊息),以及交叉簽章以驗證對話參與者的裝置。 | ||||||
|  | 
 | ||||||
|  | <b>完整通訊</b>:即時通訊、語音與視訊通話、檔案分享、畫面分享與超多的整合、機器人與小工具。建立聊天室、保持聯繫並完成工作。 | ||||||
|  | 
 | ||||||
|  | <b>無論您身在何處</b>:無論您身在何處,都可以透過 https://app.element.io 來在所有裝置與網路上保持訊息歷史同步。 | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/zh_Hant/short_description.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/zh_Hant/short_description.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | 安全的去中心化聊天與 VoIP。確保您的資料不受第三方的影響。 | ||||||
							
								
								
									
										1
									
								
								fastlane/metadata/android/zh_Hant/title.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fastlane/metadata/android/zh_Hant/title.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Element(曾名為 Riot.im) | ||||||
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionSha256Sum=0080de8491f0918e4f529a6db6820fa0b9e818ee2386117f4394f95feb1d5583 | distributionSha256Sum=22449f5231796abd892c98b2a07c9ceebe4688d192cd2d6763f8e3bf8acbedeb | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid | |||||||
| import org.matrix.android.sdk.api.session.pushers.Pusher | import org.matrix.android.sdk.api.session.pushers.Pusher | ||||||
| import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams | import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams | ||||||
| import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState | import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState | ||||||
|  | import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary | ||||||
| import org.matrix.android.sdk.api.session.room.model.RoomSummary | import org.matrix.android.sdk.api.session.room.model.RoomSummary | ||||||
| import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams | import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams | ||||||
| import org.matrix.android.sdk.api.session.sync.SyncState | import org.matrix.android.sdk.api.session.sync.SyncState | ||||||
| @ -92,6 +93,13 @@ class RxSession(private val session: Session) { | |||||||
|                 } |                 } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun liveRoomMember(userId: String, roomId: String): Observable<Optional<RoomMemberSummary>> { | ||||||
|  |         return session.getRoomMemberLive(userId, roomId).asObservable() | ||||||
|  |                 .startWithCallable { | ||||||
|  |                     session.getRoomMember(userId, roomId).toOptional() | ||||||
|  |                 } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fun liveUsers(): Observable<List<User>> { |     fun liveUsers(): Observable<List<User>> { | ||||||
|         return session.getUsersLive().asObservable() |         return session.getUsersLive().asObservable() | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ buildscript { | |||||||
|         jcenter() |         jcenter() | ||||||
|     } |     } | ||||||
|     dependencies { |     dependencies { | ||||||
|         classpath "io.realm:realm-gradle-plugin:6.1.0" |         classpath "io.realm:realm-gradle-plugin:10.0.0" | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -149,7 +149,7 @@ dependencies { | |||||||
|     implementation 'androidx.exifinterface:exifinterface:1.3.0' |     implementation 'androidx.exifinterface:exifinterface:1.3.0' | ||||||
| 
 | 
 | ||||||
|     // Database |     // Database | ||||||
|     implementation 'com.github.Zhuinden:realm-monarchy:0.5.1' |     implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' | ||||||
|     kapt 'dk.ilios:realmfieldnameshelper:1.1.1' |     kapt 'dk.ilios:realmfieldnameshelper:1.1.1' | ||||||
| 
 | 
 | ||||||
|     // Work |     // Work | ||||||
|  | |||||||
| @ -43,8 +43,8 @@ class ChangePasswordTest : InstrumentedTest { | |||||||
|         val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) |         val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) | ||||||
| 
 | 
 | ||||||
|         // Change password |         // Change password | ||||||
|         commonTestHelper.doSync<Unit> { |         commonTestHelper.runBlockingTest { | ||||||
|             session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD, it) |             session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Try to login with the previous password, it will fail |         // Try to login with the previous password, it will fail | ||||||
|  | |||||||
| @ -43,8 +43,8 @@ class DeactivateAccountTest : InstrumentedTest { | |||||||
|         val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) |         val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) | ||||||
| 
 | 
 | ||||||
|         // Deactivate the account |         // Deactivate the account | ||||||
|         commonTestHelper.doSync<Unit> { |         commonTestHelper.runBlockingTest { | ||||||
|             session.deactivateAccount(TestConstants.PASSWORD, false, it) |             session.deactivateAccount(TestConstants.PASSWORD, false) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Try to login on the previous account, it will fail (M_USER_DEACTIVATED) |         // Try to login on the previous account, it will fail (M_USER_DEACTIVATED) | ||||||
|  | |||||||
| @ -40,6 +40,7 @@ import kotlinx.coroutines.GlobalScope | |||||||
| import kotlinx.coroutines.delay | import kotlinx.coroutines.delay | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import kotlinx.coroutines.runBlocking | import kotlinx.coroutines.runBlocking | ||||||
|  | import kotlinx.coroutines.withTimeout | ||||||
| import org.junit.Assert.assertEquals | import org.junit.Assert.assertEquals | ||||||
| import org.junit.Assert.assertNotNull | import org.junit.Assert.assertNotNull | ||||||
| import org.junit.Assert.assertTrue | import org.junit.Assert.assertTrue | ||||||
| @ -343,6 +344,14 @@ class CommonTestHelper(context: Context) { | |||||||
|         await(latch, timeout) |         await(latch, timeout) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun <T> runBlockingTest(timeout: Long = TestConstants.timeOutMillis, block: suspend () -> T): T { | ||||||
|  |         return runBlocking { | ||||||
|  |             withTimeout(timeout) { | ||||||
|  |                 block() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // Transform a method with a MatrixCallback to a synchronous method |     // Transform a method with a MatrixCallback to a synchronous method | ||||||
|     inline fun <reified T> doSync(timeout: Long? = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T { |     inline fun <reified T> doSync(timeout: Long? = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T { | ||||||
|         val lock = CountDownLatch(1) |         val lock = CountDownLatch(1) | ||||||
|  | |||||||
| @ -68,8 +68,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { | |||||||
|         if (encryptedRoom) { |         if (encryptedRoom) { | ||||||
|             val room = aliceSession.getRoom(roomId)!! |             val room = aliceSession.getRoom(roomId)!! | ||||||
| 
 | 
 | ||||||
|             mTestHelper.doSync<Unit> { |             mTestHelper.runBlockingTest { | ||||||
|                 room.enableEncryption(callback = it) |                 room.enableEncryption() | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -555,7 +555,7 @@ class SASTest : InstrumentedTest { | |||||||
| 
 | 
 | ||||||
|         mTestHelper.waitWithLatch { |         mTestHelper.waitWithLatch { | ||||||
|             mTestHelper.retryPeriodicallyWithLatch(it) { |             mTestHelper.retryPeriodicallyWithLatch(it) { | ||||||
|                 val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull() |                 val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull() | ||||||
|                 requestID = prAlicePOV?.transactionId |                 requestID = prAlicePOV?.transactionId | ||||||
|                 Log.v("TEST", "== alicePOV is $prAlicePOV") |                 Log.v("TEST", "== alicePOV is $prAlicePOV") | ||||||
|                 prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId |                 prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId | ||||||
| @ -566,7 +566,7 @@ class SASTest : InstrumentedTest { | |||||||
| 
 | 
 | ||||||
|         mTestHelper.waitWithLatch { |         mTestHelper.waitWithLatch { | ||||||
|             mTestHelper.retryPeriodicallyWithLatch(it) { |             mTestHelper.retryPeriodicallyWithLatch(it) { | ||||||
|                 val prBobPOV = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId)?.firstOrNull() |                 val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull() | ||||||
|                 Log.v("TEST", "== prBobPOV is $prBobPOV") |                 Log.v("TEST", "== prBobPOV is $prBobPOV") | ||||||
|                 prBobPOV?.transactionId == requestID |                 prBobPOV?.transactionId == requestID | ||||||
|             } |             } | ||||||
| @ -581,7 +581,7 @@ class SASTest : InstrumentedTest { | |||||||
|         // wait for alice to get the ready |         // wait for alice to get the ready | ||||||
|         mTestHelper.waitWithLatch { |         mTestHelper.waitWithLatch { | ||||||
|             mTestHelper.retryPeriodicallyWithLatch(it) { |             mTestHelper.retryPeriodicallyWithLatch(it) { | ||||||
|                 val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull() |                 val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull() | ||||||
|                 Log.v("TEST", "== prAlicePOV is $prAlicePOV") |                 Log.v("TEST", "== prAlicePOV is $prAlicePOV") | ||||||
|                 prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null |                 prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -71,38 +71,27 @@ class SearchMessagesTest : InstrumentedTest { | |||||||
|             commonTestHelper.await(lock) |             commonTestHelper.await(lock) | ||||||
| 
 | 
 | ||||||
|             lock = CountDownLatch(1) |             lock = CountDownLatch(1) | ||||||
|             aliceSession |             val data = commonTestHelper.runBlockingTest { | ||||||
|                     .searchService() |                 aliceSession | ||||||
|                     .search( |                         .searchService() | ||||||
|                             searchTerm = "lore", |                         .search( | ||||||
|                             limit = 10, |                                 searchTerm = "lore", | ||||||
|                             includeProfile = true, |                                 limit = 10, | ||||||
|                             afterLimit = 0, |                                 includeProfile = true, | ||||||
|                             beforeLimit = 10, |                                 afterLimit = 0, | ||||||
|                             orderByRecent = true, |                                 beforeLimit = 10, | ||||||
|                             nextBatch = null, |                                 orderByRecent = true, | ||||||
|                             roomId = aliceRoomId, |                                 nextBatch = null, | ||||||
|                             callback = object : MatrixCallback<SearchResult> { |                                 roomId = aliceRoomId | ||||||
|                                 override fun onSuccess(data: SearchResult) { |                         ) | ||||||
|                                     super.onSuccess(data) |             } | ||||||
|                                     assertTrue(data.results?.size == 2) |             assertTrue(data.results?.size == 2) | ||||||
|                                     assertTrue( |             assertTrue( | ||||||
|                                             data.results |                     data.results | ||||||
|                                                     ?.all { |                             ?.all { | ||||||
|                                                         (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() |                                 (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() | ||||||
|                                                     }.orFalse() |                             }.orFalse() | ||||||
|                                     ) |             ) | ||||||
|                                     lock.countDown() |  | ||||||
|                                 } |  | ||||||
| 
 |  | ||||||
|                                 override fun onFailure(failure: Throwable) { |  | ||||||
|                                     super.onFailure(failure) |  | ||||||
|                                     fail(failure.localizedMessage) |  | ||||||
|                                     lock.countDown() |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                     ) |  | ||||||
|             lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) |  | ||||||
| 
 | 
 | ||||||
|             aliceTimeline.removeAllListeners() |             aliceTimeline.removeAllListeners() | ||||||
|             cryptoTestData.cleanUp(commonTestHelper) |             cryptoTestData.cleanUp(commonTestHelper) | ||||||
|  | |||||||
| @ -66,9 +66,9 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun logJson(formattedJson: String) { |     private fun logJson(formattedJson: String) { | ||||||
|         val arr = formattedJson.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() |         formattedJson | ||||||
|         for (s in arr) { |                 .lines() | ||||||
|             Timber.v(s) |                 .dropLastWhile { it.isEmpty() } | ||||||
|         } |                 .forEach { Timber.v(it) } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ interface LoginWizard { | |||||||
|      * @param password the password field |      * @param password the password field | ||||||
|      * @param deviceName the initial device name |      * @param deviceName the initial device name | ||||||
|      * @param callback  the matrix callback on which you'll receive the result of authentication. |      * @param callback  the matrix callback on which you'll receive the result of authentication. | ||||||
|      * @return return a [Cancelable] |      * @return a [Cancelable] | ||||||
|      */ |      */ | ||||||
|     fun login(login: String, |     fun login(login: String, | ||||||
|               password: String, |               password: String, | ||||||
|  | |||||||
| @ -22,3 +22,8 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence { | |||||||
|         else               -> "$prefix$this" |         else               -> "$prefix$this" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Append a new line and then the provided string | ||||||
|  |  */ | ||||||
|  | fun StringBuilder.appendNl(str: String) = append("\n").append(str) | ||||||
|  | |||||||
| @ -15,11 +15,9 @@ | |||||||
|  */ |  */ | ||||||
| package org.matrix.android.sdk.api.pushrules | package org.matrix.android.sdk.api.pushrules | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.pushrules.rest.PushRule | import org.matrix.android.sdk.api.pushrules.rest.PushRule | ||||||
| import org.matrix.android.sdk.api.pushrules.rest.RuleSet | import org.matrix.android.sdk.api.pushrules.rest.RuleSet | ||||||
| import org.matrix.android.sdk.api.session.events.model.Event | import org.matrix.android.sdk.api.session.events.model.Event | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 | 
 | ||||||
| interface PushRuleService { | interface PushRuleService { | ||||||
|     /** |     /** | ||||||
| @ -29,13 +27,13 @@ interface PushRuleService { | |||||||
| 
 | 
 | ||||||
|     fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet |     fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet | ||||||
| 
 | 
 | ||||||
|     fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable |     suspend fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean) | ||||||
| 
 | 
 | ||||||
|     fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable |     suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) | ||||||
| 
 | 
 | ||||||
|     fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable |     suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) | ||||||
| 
 | 
 | ||||||
|     fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable |     suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) | ||||||
| 
 | 
 | ||||||
|     fun addPushRuleListener(listener: PushRuleListener) |     fun addPushRuleListener(listener: PushRuleListener) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,9 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.api.raw | package org.matrix.android.sdk.api.raw | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Useful methods to fetch raw data from the server. The access token will not be used to fetched the data |  * Useful methods to fetch raw data from the server. The access token will not be used to fetched the data | ||||||
|  */ |  */ | ||||||
| @ -26,17 +23,15 @@ interface RawService { | |||||||
|     /** |     /** | ||||||
|      * Get a URL, either from cache or from the remote server, depending on the cache strategy |      * Get a URL, either from cache or from the remote server, depending on the cache strategy | ||||||
|      */ |      */ | ||||||
|     fun getUrl(url: String, |     suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String | ||||||
|                rawCacheStrategy: RawCacheStrategy, |  | ||||||
|                matrixCallback: MatrixCallback<String>): Cancelable |  | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Specific case for the well-known file. Cache validity is 8 hours |      * Specific case for the well-known file. Cache validity is 8 hours | ||||||
|      */ |      */ | ||||||
|     fun getWellknown(userId: String, matrixCallback: MatrixCallback<String>): Cancelable |     suspend fun getWellknown(userId: String): String | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Clear all the cache data |      * Clear all the cache data | ||||||
|      */ |      */ | ||||||
|     fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable |     suspend fun clearCache() | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,9 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.api.session.account | package org.matrix.android.sdk.api.session.account | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This interface defines methods to manage the account. It's implemented at the session level. |  * This interface defines methods to manage the account. It's implemented at the session level. | ||||||
|  */ |  */ | ||||||
| @ -28,7 +25,7 @@ interface AccountService { | |||||||
|      * @param password Current password. |      * @param password Current password. | ||||||
|      * @param newPassword New password |      * @param newPassword New password | ||||||
|      */ |      */ | ||||||
|     fun changePassword(password: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable |     suspend fun changePassword(password: String, newPassword: String) | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Deactivate the account. |      * Deactivate the account. | ||||||
| @ -46,5 +43,5 @@ interface AccountService { | |||||||
|      * @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see |      * @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see | ||||||
|      * an incomplete view of conversations |      * an incomplete view of conversations | ||||||
|      */ |      */ | ||||||
|     fun deactivateAccount(password: String, eraseAllData: Boolean, callback: MatrixCallback<Unit>): Cancelable |     suspend fun deactivateAccount(password: String, eraseAllData: Boolean) | ||||||
| } | } | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ interface VerificationService { | |||||||
| 
 | 
 | ||||||
|     fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? |     fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? | ||||||
| 
 | 
 | ||||||
|     fun getExistingVerificationRequest(otherUserId: String): List<PendingVerificationRequest>? |     fun getExistingVerificationRequests(otherUserId: String): List<PendingVerificationRequest> | ||||||
| 
 | 
 | ||||||
|     fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest? |     fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest? | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -56,6 +56,7 @@ object EventType { | |||||||
|     const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups" |     const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups" | ||||||
|     const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events" |     const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events" | ||||||
|     const val STATE_ROOM_ENCRYPTION = "m.room.encryption" |     const val STATE_ROOM_ENCRYPTION = "m.room.encryption" | ||||||
|  |     const val STATE_ROOM_SERVER_ACL = "m.room.server_acl" | ||||||
| 
 | 
 | ||||||
|     // Call Events |     // Call Events | ||||||
|     const val CALL_INVITE = "m.call.invite" |     const val CALL_INVITE = "m.call.invite" | ||||||
|  | |||||||
| @ -16,9 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.api.session.group | package org.matrix.android.sdk.api.session.group | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This interface defines methods to interact within a group. |  * This interface defines methods to interact within a group. | ||||||
|  */ |  */ | ||||||
| @ -28,8 +25,7 @@ interface Group { | |||||||
|     /** |     /** | ||||||
|      * This methods allows you to refresh data about this group. It will be reflected on the GroupSummary. |      * This methods allows you to refresh data about this group. It will be reflected on the GroupSummary. | ||||||
|      * The SDK also takes care of refreshing group data every hour. |      * The SDK also takes care of refreshing group data every hour. | ||||||
|      * @param callback : the matrix callback to be notified of success or failure |  | ||||||
|      * @return a Cancelable to be able to cancel requests. |      * @return a Cancelable to be able to cancel requests. | ||||||
|      */ |      */ | ||||||
|     fun fetchGroupData(callback: MatrixCallback<Unit>): Cancelable |     suspend fun fetchGroupData() | ||||||
| } | } | ||||||
|  | |||||||
| @ -92,9 +92,29 @@ interface IdentityService { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Search MatrixId of users providing email and phone numbers |      * Search MatrixId of users providing email and phone numbers | ||||||
|  |      * Note the the user consent has to be set to true, or it will throw a UserConsentNotProvided failure | ||||||
|  |      * Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent] | ||||||
|  |      * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. | ||||||
|      */ |      */ | ||||||
|     fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable |     fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Return the current user consent for the current identity server, which has been stored using [setUserConsent]. | ||||||
|  |      * If [setUserConsent] has not been called, the returned value will be false. | ||||||
|  |      * Note that if the identity server is changed, the user consent is reset to false. | ||||||
|  |      * @return the value stored using [setUserConsent] or false if [setUserConsent] has never been called, or if the identity server | ||||||
|  |      *         has been changed | ||||||
|  |      */ | ||||||
|  |     fun getUserConsent(): Boolean | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set the user consent to the provided value. Application MUST explicitly ask for the user consent to send their private data | ||||||
|  |      * (email and phone numbers) to the identity server. | ||||||
|  |      * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. | ||||||
|  |      * @param newValue true if the user explicitly give their consent, false if the user wants to revoke their consent. | ||||||
|  |      */ | ||||||
|  |     fun setUserConsent(newValue: Boolean) | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Get the status of the current user's threePid |      * Get the status of the current user's threePid | ||||||
|      * A lookup will be performed, but also pending binding state will be restored |      * A lookup will be performed, but also pending binding state will be restored | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ sealed class IdentityServiceError : Failure.FeatureFailure() { | |||||||
|     object NoIdentityServerConfigured : IdentityServiceError() |     object NoIdentityServerConfigured : IdentityServiceError() | ||||||
|     object TermsNotSignedException : IdentityServiceError() |     object TermsNotSignedException : IdentityServiceError() | ||||||
|     object BulkLookupSha256NotSupported : IdentityServiceError() |     object BulkLookupSha256NotSupported : IdentityServiceError() | ||||||
|  |     object UserConsentNotProvided : IdentityServiceError() | ||||||
|     object BindingError : IdentityServiceError() |     object BindingError : IdentityServiceError() | ||||||
|     object NoCurrentBindingError : IdentityServiceError() |     object NoCurrentBindingError : IdentityServiceError() | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ | |||||||
| package org.matrix.android.sdk.api.session.permalinks | package org.matrix.android.sdk.api.session.permalinks | ||||||
| 
 | 
 | ||||||
| import android.text.Spannable | import android.text.Spannable | ||||||
|  | import org.matrix.android.sdk.api.MatrixPatterns | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  *  MatrixLinkify take a piece of text and turns all of the |  *  MatrixLinkify take a piece of text and turns all of the | ||||||
| @ -35,7 +36,7 @@ object MatrixLinkify { | |||||||
|          * I disable it because it mess up with pills, and even with pills, it does not work correctly: |          * I disable it because it mess up with pills, and even with pills, it does not work correctly: | ||||||
|          * The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to |          * The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to | ||||||
|          */ |          */ | ||||||
|         /* | 
 | ||||||
|         // sanity checks |         // sanity checks | ||||||
|         if (spannable.isEmpty()) { |         if (spannable.isEmpty()) { | ||||||
|             return false |             return false | ||||||
| @ -48,14 +49,21 @@ object MatrixLinkify { | |||||||
|                 val startPos = match.range.first |                 val startPos = match.range.first | ||||||
|                 if (startPos == 0 || text[startPos - 1] != '/') { |                 if (startPos == 0 || text[startPos - 1] != '/') { | ||||||
|                     val endPos = match.range.last + 1 |                     val endPos = match.range.last + 1 | ||||||
|                     val url = text.substring(match.range) |                     var url = text.substring(match.range) | ||||||
|  |                     if (MatrixPatterns.isUserId(url) | ||||||
|  |                             || MatrixPatterns.isRoomAlias(url) | ||||||
|  |                             || MatrixPatterns.isRoomId(url) | ||||||
|  |                             || MatrixPatterns.isGroupId(url) | ||||||
|  |                             || MatrixPatterns.isEventId(url)) { | ||||||
|  |                         url = PermalinkService.MATRIX_TO_URL_BASE  + url | ||||||
|  |                     } | ||||||
|                     val span = MatrixPermalinkSpan(url, callback) |                     val span = MatrixPermalinkSpan(url, callback) | ||||||
|                     spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) |                     spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return hasMatch |         return hasMatch | ||||||
|          */ | 
 | ||||||
|         return false | //        return false | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -44,13 +44,12 @@ object PermalinkParser { | |||||||
|         if (fragment.isNullOrEmpty()) { |         if (fragment.isNullOrEmpty()) { | ||||||
|             return PermalinkData.FallbackLink(uri) |             return PermalinkData.FallbackLink(uri) | ||||||
|         } |         } | ||||||
|         val indexOfQuery = fragment.indexOf("?") |         val safeFragment = fragment.substringBefore('?') | ||||||
|         val safeFragment = if (indexOfQuery != -1) fragment.substring(0, indexOfQuery) else fragment |  | ||||||
|         val viaQueryParameters = fragment.getViaParameters() |         val viaQueryParameters = fragment.getViaParameters() | ||||||
| 
 | 
 | ||||||
|         // we are limiting to 2 params |         // we are limiting to 2 params | ||||||
|         val params = safeFragment |         val params = safeFragment | ||||||
|                 .split(MatrixPatterns.SEP_REGEX.toRegex()) |                 .split(MatrixPatterns.SEP_REGEX) | ||||||
|                 .filter { it.isNotEmpty() } |                 .filter { it.isNotEmpty() } | ||||||
|                 .take(2) |                 .take(2) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.room | |||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
| import org.matrix.android.sdk.api.MatrixCallback | import org.matrix.android.sdk.api.MatrixCallback | ||||||
| import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState | import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState | ||||||
|  | import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary | ||||||
| import org.matrix.android.sdk.api.session.room.model.RoomSummary | import org.matrix.android.sdk.api.session.room.model.RoomSummary | ||||||
| import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams | import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams | ||||||
| import org.matrix.android.sdk.api.util.Cancelable | import org.matrix.android.sdk.api.util.Cancelable | ||||||
| @ -141,4 +142,20 @@ interface RoomService { | |||||||
|      *  - the power level of the users are not taken into account. Normally in a DM, the 2 members are admins of the room |      *  - the power level of the users are not taken into account. Normally in a DM, the 2 members are admins of the room | ||||||
|      */ |      */ | ||||||
|     fun getExistingDirectRoomWithUser(otherUserId: String): String? |     fun getExistingDirectRoomWithUser(otherUserId: String): String? | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get a room member for the tuple {userId,roomId} | ||||||
|  |      * @param userId the userId to look for. | ||||||
|  |      * @param roomId the roomId to look for. | ||||||
|  |      * @return the room member or null | ||||||
|  |      */ | ||||||
|  |     fun getRoomMember(userId: String, roomId: String): RoomMemberSummary? | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Observe a live room member for the tuple {userId,roomId} | ||||||
|  |      * @param userId the userId to look for. | ||||||
|  |      * @param roomId the roomId to look for. | ||||||
|  |      * @return a LiveData of the optional found room member | ||||||
|  |      */ | ||||||
|  |     fun getRoomMemberLive(userId: String, roomId: String): LiveData<Optional<RoomMemberSummary>> | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.api.session.room.crypto | package org.matrix.android.sdk.api.session.room.crypto | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM | import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM | ||||||
| 
 | 
 | ||||||
| interface RoomCryptoService { | interface RoomCryptoService { | ||||||
| @ -30,6 +29,5 @@ interface RoomCryptoService { | |||||||
|     /** |     /** | ||||||
|      * Enable encryption of the room |      * Enable encryption of the room | ||||||
|      */ |      */ | ||||||
|     fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM, |     suspend fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM) | ||||||
|                          callback: MatrixCallback<Unit>) |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,4 +22,9 @@ import org.matrix.android.sdk.api.failure.MatrixError | |||||||
| sealed class CreateRoomFailure : Failure.FeatureFailure() { | sealed class CreateRoomFailure : Failure.FeatureFailure() { | ||||||
|     object CreatedWithTimeout : CreateRoomFailure() |     object CreatedWithTimeout : CreateRoomFailure() | ||||||
|     data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() |     data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() | ||||||
|  |     sealed class RoomAliasError : CreateRoomFailure() { | ||||||
|  |         object AliasEmpty : RoomAliasError() | ||||||
|  |         object AliasNotAvailable : RoomAliasError() | ||||||
|  |         object AliasInvalid : RoomAliasError() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,59 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2020 The Matrix.org Foundation C.I.C. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  * http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package org.matrix.android.sdk.api.session.room.model | ||||||
|  | 
 | ||||||
|  | import com.squareup.moshi.Json | ||||||
|  | import com.squareup.moshi.JsonClass | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class representing the EventType.STATE_ROOM_SERVER_ACL state event content | ||||||
|  |  * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#m-room-server-acl | ||||||
|  |  */ | ||||||
|  | @JsonClass(generateAdapter = true) | ||||||
|  | data class RoomServerAclContent( | ||||||
|  |         /** | ||||||
|  |          * True to allow server names that are IP address literals. False to deny. | ||||||
|  |          * Defaults to true if missing or otherwise not a boolean. | ||||||
|  |          * This is strongly recommended to be set to false as servers running with IP literal names are strongly | ||||||
|  |          * discouraged in order to require legitimate homeservers to be backed by a valid registered domain name. | ||||||
|  |          */ | ||||||
|  |         @Json(name = "allow_ip_literals") | ||||||
|  |         val allowIpLiterals: Boolean = true, | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The server names to allow in the room, excluding any port information. Wildcards may be used to cover | ||||||
|  |          * a wider range of hosts, where * matches zero or more characters and ? matches exactly one character. | ||||||
|  |          * | ||||||
|  |          * This defaults to an empty list when not provided, effectively disallowing every server. | ||||||
|  |          */ | ||||||
|  |         @Json(name = "allow") | ||||||
|  |         val allowList: List<String> = emptyList(), | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The server names to disallow in the room, excluding any port information. Wildcards may be used to cover | ||||||
|  |          * a wider range of hosts, where * matches zero or more characters and ? matches exactly one character. | ||||||
|  |          * | ||||||
|  |          * This defaults to an empty list when not provided. | ||||||
|  |          */ | ||||||
|  |         @Json(name = "deny") | ||||||
|  |         val denyList: List<String> = emptyList() | ||||||
|  | 
 | ||||||
|  | ) { | ||||||
|  |     companion object { | ||||||
|  |         const val ALL = "*" | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -94,7 +94,22 @@ class CreateRoomParams { | |||||||
|      * The server will clobber the following keys: creator. |      * The server will clobber the following keys: creator. | ||||||
|      * Future versions of the specification may allow the server to clobber other keys. |      * Future versions of the specification may allow the server to clobber other keys. | ||||||
|      */ |      */ | ||||||
|     var creationContent: Any? = null |     val creationContent = mutableMapOf<String, Any>() | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set to true to disable federation of this room. | ||||||
|  |      * Default: false | ||||||
|  |      */ | ||||||
|  |     var disableFederation = false | ||||||
|  |         set(value) { | ||||||
|  |             field = value | ||||||
|  |             if (value) { | ||||||
|  |                 creationContent[CREATION_CONTENT_KEY_M_FEDERATE] = false | ||||||
|  |             } else { | ||||||
|  |                 // This is the default value, we remove the field | ||||||
|  |                 creationContent.remove(CREATION_CONTENT_KEY_M_FEDERATE) | ||||||
|  |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The power level content to override in the default power level event |      * The power level content to override in the default power level event | ||||||
| @ -120,4 +135,8 @@ class CreateRoomParams { | |||||||
|     fun enableEncryption() { |     fun enableEncryption() { | ||||||
|         algorithm = MXCRYPTO_ALGORITHM_MEGOLM |         algorithm = MXCRYPTO_ALGORITHM_MEGOLM | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,12 +17,10 @@ | |||||||
| package org.matrix.android.sdk.api.session.room.notification | package org.matrix.android.sdk.api.session.room.notification | ||||||
| 
 | 
 | ||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 | 
 | ||||||
| interface RoomPushRuleService { | interface RoomPushRuleService { | ||||||
| 
 | 
 | ||||||
|     fun getLiveRoomNotificationState(): LiveData<RoomNotificationState> |     fun getLiveRoomNotificationState(): LiveData<RoomNotificationState> | ||||||
| 
 | 
 | ||||||
|     fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable |     suspend fun setRoomNotificationState(roomNotificationState: RoomNotificationState) | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,9 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.api.session.room.reporting | package org.matrix.android.sdk.api.session.room.reporting | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This interface defines methods to report content of an event. |  * This interface defines methods to report content of an event. | ||||||
|  */ |  */ | ||||||
| @ -28,5 +25,5 @@ interface ReportingService { | |||||||
|      * Report content |      * Report content | ||||||
|      * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-rooms-roomid-report-eventid |      * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-rooms-roomid-report-eventid | ||||||
|      */ |      */ | ||||||
|     fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback<Unit>): Cancelable |     suspend fun reportContent(eventId: String, score: Int, reason: String) | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,8 +17,6 @@ | |||||||
| package org.matrix.android.sdk.api.session.room.send | package org.matrix.android.sdk.api.session.room.send | ||||||
| 
 | 
 | ||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.api.util.Optional | import org.matrix.android.sdk.api.util.Optional | ||||||
| 
 | 
 | ||||||
| interface DraftService { | interface DraftService { | ||||||
| @ -26,12 +24,12 @@ interface DraftService { | |||||||
|     /** |     /** | ||||||
|      * Save or update a draft to the room |      * Save or update a draft to the room | ||||||
|      */ |      */ | ||||||
|     fun saveDraft(draft: UserDraft, callback: MatrixCallback<Unit>): Cancelable |     suspend fun saveDraft(draft: UserDraft) | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Delete the last draft, basically just after sending the message |      * Delete the last draft, basically just after sending the message | ||||||
|      */ |      */ | ||||||
|     fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable |     suspend fun deleteDraft() | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Return the current draft or null |      * Return the current draft or null | ||||||
|  | |||||||
| @ -16,9 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.api.session.room.tags | package org.matrix.android.sdk.api.session.room.tags | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This interface defines methods to handle tags of a room. It's implemented at the room level. |  * This interface defines methods to handle tags of a room. It's implemented at the room level. | ||||||
|  */ |  */ | ||||||
| @ -26,10 +23,10 @@ interface TagsService { | |||||||
|     /** |     /** | ||||||
|      * Add a tag to a room |      * Add a tag to a room | ||||||
|      */ |      */ | ||||||
|     fun addTag(tag: String, order: Double?, callback: MatrixCallback<Unit>): Cancelable |     suspend fun addTag(tag: String, order: Double?) | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Remove tag from a room |      * Remove tag from a room | ||||||
|      */ |      */ | ||||||
|     fun deleteTag(tag: String, callback: MatrixCallback<Unit>): Cancelable |     suspend fun deleteTag(tag: String) | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,9 +16,6 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.api.session.search | package org.matrix.android.sdk.api.session.search | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This interface defines methods to search messages in rooms. |  * This interface defines methods to search messages in rooms. | ||||||
|  */ |  */ | ||||||
| @ -35,15 +32,13 @@ interface SearchService { | |||||||
|      * @param beforeLimit how many events before the result are returned. |      * @param beforeLimit how many events before the result are returned. | ||||||
|      * @param afterLimit how many events after the result are returned. |      * @param afterLimit how many events after the result are returned. | ||||||
|      * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. |      * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. | ||||||
|      * @param callback Callback to get the search result |  | ||||||
|      */ |      */ | ||||||
|     fun search(searchTerm: String, |     suspend fun search(searchTerm: String, | ||||||
|                roomId: String, |                        roomId: String, | ||||||
|                nextBatch: String?, |                        nextBatch: String?, | ||||||
|                orderByRecent: Boolean, |                        orderByRecent: Boolean, | ||||||
|                limit: Int, |                        limit: Int, | ||||||
|                beforeLimit: Int, |                        beforeLimit: Int, | ||||||
|                afterLimit: Int, |                        afterLimit: Int, | ||||||
|                includeProfile: Boolean, |                        includeProfile: Boolean): SearchResult | ||||||
|                callback: MatrixCallback<SearchResult>): Cancelable |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -35,6 +35,11 @@ interface UserService { | |||||||
|      */ |      */ | ||||||
|     fun getUser(userId: String): User? |     fun getUser(userId: String): User? | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Try to resolve user from known users, or using profile api | ||||||
|  |      */ | ||||||
|  |     fun resolveUser(userId: String, callback: MatrixCallback<User>) | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Search list of users on server directory. |      * Search list of users on server directory. | ||||||
|      * @param search the searched term |      * @param search the searched term | ||||||
|  | |||||||
| @ -123,6 +123,7 @@ internal abstract class CryptoModule { | |||||||
|                     } |                     } | ||||||
|                     .name("crypto_store.realm") |                     .name("crypto_store.realm") | ||||||
|                     .modules(RealmCryptoStoreModule()) |                     .modules(RealmCryptoStoreModule()) | ||||||
|  |                     .allowWritesOnUiThread(true) | ||||||
|                     .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) |                     .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) | ||||||
|                     .migration(realmCryptoStoreMigration) |                     .migration(realmCryptoStoreMigration) | ||||||
|                     .build() |                     .build() | ||||||
|  | |||||||
| @ -767,9 +767,9 @@ internal class DefaultCryptoService @Inject constructor( | |||||||
|      */ |      */ | ||||||
|     private fun onRoomKeyEvent(event: Event) { |     private fun onRoomKeyEvent(event: Event) { | ||||||
|         val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return |         val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return | ||||||
|         Timber.v("## CRYPTO | GOSSIP onRoomKeyEvent() : type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>") |         Timber.i("## CRYPTO | onRoomKeyEvent() from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>") | ||||||
|         if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) { |         if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) { | ||||||
|             Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : missing fields") |             Timber.e("## CRYPTO | onRoomKeyEvent() : missing fields") | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm) |         val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm) | ||||||
| @ -782,20 +782,20 @@ internal class DefaultCryptoService @Inject constructor( | |||||||
| 
 | 
 | ||||||
|     private fun onKeyWithHeldReceived(event: Event) { |     private fun onKeyWithHeldReceived(event: Event) { | ||||||
|         val withHeldContent = event.getClearContent().toModel<RoomKeyWithHeldContent>() ?: return Unit.also { |         val withHeldContent = event.getClearContent().toModel<RoomKeyWithHeldContent>() ?: return Unit.also { | ||||||
|             Timber.e("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields") |             Timber.i("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields") | ||||||
|         } |         } | ||||||
|         Timber.d("## CRYPTO | onKeyWithHeldReceived() received : content <$withHeldContent>") |         Timber.i("## CRYPTO | onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>") | ||||||
|         val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm) |         val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm) | ||||||
|         if (alg is IMXWithHeldExtension) { |         if (alg is IMXWithHeldExtension) { | ||||||
|             alg.onRoomKeyWithHeldEvent(withHeldContent) |             alg.onRoomKeyWithHeldEvent(withHeldContent) | ||||||
|         } else { |         } else { | ||||||
|             Timber.e("## CRYPTO | onKeyWithHeldReceived() : Unable to handle WithHeldContent for ${withHeldContent.algorithm}") |             Timber.e("## CRYPTO | onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}") | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun onSecretSendReceived(event: Event) { |     private fun onSecretSendReceived(event: Event) { | ||||||
|         Timber.i("## CRYPTO | GOSSIP onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}") |         Timber.i("## CRYPTO | GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}") | ||||||
|         if (!event.isEncrypted()) { |         if (!event.isEncrypted()) { | ||||||
|             // secret send messages must be encrypted |             // secret send messages must be encrypted | ||||||
|             Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event") |             Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event") | ||||||
|  | |||||||
| @ -119,7 +119,7 @@ internal class EventDecryptor @Inject constructor( | |||||||
|                                             markOlmSessionForUnwedging(event.senderId ?: "", it) |                                             markOlmSessionForUnwedging(event.senderId ?: "", it) | ||||||
|                                         } |                                         } | ||||||
|                                         ?: run { |                                         ?: run { | ||||||
|                                             Timber.v("## CRYPTO | markOlmSessionForUnwedging() : Failed to find sender crypto device") |                                             Timber.i("## CRYPTO | internalDecryptEvent() : Failed to find sender crypto device for unwedging") | ||||||
|                                         } |                                         } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| @ -137,16 +137,18 @@ internal class EventDecryptor @Inject constructor( | |||||||
|         val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0 |         val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0 | ||||||
|         val now = System.currentTimeMillis() |         val now = System.currentTimeMillis() | ||||||
|         if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { |         if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { | ||||||
|             Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another") |             Timber.w("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another") | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}") |         Timber.i("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}") | ||||||
|         lastNewSessionForcedDates.setObject(senderId, deviceKey, now) |         lastNewSessionForcedDates.setObject(senderId, deviceKey, now) | ||||||
| 
 | 
 | ||||||
|         // offload this from crypto thread (?) |         // offload this from crypto thread (?) | ||||||
|         cryptoCoroutineScope.launch(coroutineDispatchers.computation) { |         cryptoCoroutineScope.launch(coroutineDispatchers.computation) { | ||||||
|             ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) |             val ensured = ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) | ||||||
|  | 
 | ||||||
|  |             Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}") | ||||||
| 
 | 
 | ||||||
|             // Now send a blank message on that session so the other side knows about it. |             // Now send a blank message on that session so the other side knows about it. | ||||||
|             // (The keyshare request is sent in the clear so that won't do) |             // (The keyshare request is sent in the clear so that won't do) | ||||||
| @ -159,10 +161,14 @@ internal class EventDecryptor @Inject constructor( | |||||||
|             val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) |             val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) | ||||||
|             val sendToDeviceMap = MXUsersDevicesMap<Any>() |             val sendToDeviceMap = MXUsersDevicesMap<Any>() | ||||||
|             sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) |             sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) | ||||||
|             Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}") |             Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}") | ||||||
|             withContext(coroutineDispatchers.io) { |             withContext(coroutineDispatchers.io) { | ||||||
|                 val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) |                 val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) | ||||||
|                 sendToDeviceTask.execute(sendToDeviceParams) |                 try { | ||||||
|  |                     sendToDeviceTask.execute(sendToDeviceParams) | ||||||
|  |                 } catch (failure: Throwable) { | ||||||
|  |                     Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}") | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -54,6 +54,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( | |||||||
|         private val cryptoCoroutineScope: CoroutineScope) { |         private val cryptoCoroutineScope: CoroutineScope) { | ||||||
| 
 | 
 | ||||||
|     private val executor = Executors.newSingleThreadExecutor() |     private val executor = Executors.newSingleThreadExecutor() | ||||||
|  | 
 | ||||||
|     // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations |     // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations | ||||||
|     // we received in the current sync. |     // we received in the current sync. | ||||||
|     private val receivedGossipingRequests = ArrayList<IncomingShareRequestCommon>() |     private val receivedGossipingRequests = ArrayList<IncomingShareRequestCommon>() | ||||||
| @ -103,11 +104,11 @@ internal class IncomingGossipingRequestManager @Inject constructor( | |||||||
|      * @param event the announcement event. |      * @param event the announcement event. | ||||||
|      */ |      */ | ||||||
|     fun onGossipingRequestEvent(event: Event) { |     fun onGossipingRequestEvent(event: Event) { | ||||||
|         Timber.v("## CRYPTO | GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}") |  | ||||||
|         val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>() |         val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>() | ||||||
|  |         Timber.i("## CRYPTO | GOSSIP onGossipingRequestEvent received type ${event.type} from user:${event.senderId}, content:$roomKeyShare") | ||||||
|         // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } |         // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } | ||||||
|         when (roomKeyShare?.action) { |         when (roomKeyShare?.action) { | ||||||
|             GossipingToDeviceObject.ACTION_SHARE_REQUEST      -> { |             GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { | ||||||
|                 if (event.getClearType() == EventType.REQUEST_SECRET) { |                 if (event.getClearType() == EventType.REQUEST_SECRET) { | ||||||
|                     IncomingSecretShareRequest.fromEvent(event)?.let { |                     IncomingSecretShareRequest.fromEvent(event)?.let { | ||||||
|                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { |                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { | ||||||
| @ -324,7 +325,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( | |||||||
|         val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() |         val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() | ||||||
| 
 | 
 | ||||||
|         when (secretName) { |         when (secretName) { | ||||||
|             MASTER_KEY_SSSS_NAME       -> cryptoStore.getCrossSigningPrivateKeys()?.master |             MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master | ||||||
|             SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned |             SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned | ||||||
|             USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user |             USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user | ||||||
|             KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey |             KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey | ||||||
|  | |||||||
| @ -760,7 +760,7 @@ internal class MXOlmDevice @Inject constructor( | |||||||
|                 return session |                 return session | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             Timber.v("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") |             Timber.w("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") | ||||||
|             throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON) |             throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -100,7 +100,7 @@ internal class SendGossipWorker(context: Context, | |||||||
|                         requestId = params.requestId, |                         requestId = params.requestId, | ||||||
|                         state = GossipingRequestState.FAILED_TO_ACCEPTED |                         state = GossipingRequestState.FAILED_TO_ACCEPTED | ||||||
|                 ) |                 ) | ||||||
|                 Timber.e("no session with this device, probably because there were no one-time keys.") |                 Timber.e("no session with this device $requestingDeviceId, probably because there were no one-time keys.") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -69,7 +69,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor( | |||||||
|         // |         // | ||||||
|         // That should eventually resolve itself, but it's poor form. |         // That should eventually resolve itself, but it's poor form. | ||||||
| 
 | 
 | ||||||
|         Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") |         Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") | ||||||
| 
 | 
 | ||||||
|         val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) |         val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) | ||||||
|         val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams) |         val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams) | ||||||
| @ -90,7 +90,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor( | |||||||
|                             oneTimeKey = key |                             oneTimeKey = key | ||||||
|                         } |                         } | ||||||
|                         if (oneTimeKey == null) { |                         if (oneTimeKey == null) { | ||||||
|                             Timber.v("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm |                             Timber.w("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm | ||||||
|                                     + " for device " + userId + " : " + deviceId) |                                     + " for device " + userId + " : " + deviceId) | ||||||
|                             continue |                             continue | ||||||
|                         } |                         } | ||||||
|  | |||||||
| @ -243,8 +243,7 @@ internal class MXMegolmDecryption(private val userId: String, | |||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { |         if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { | ||||||
|             Timber.v("## CRYPTO | onRoomKeyEvent(), forward adding key : roomId ${roomKeyContent.roomId}" + |             Timber.i("## CRYPTO | onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}") | ||||||
|                     " sessionId ${roomKeyContent.sessionId} sessionKey ${roomKeyContent.sessionKey}") |  | ||||||
|             val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>() |             val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>() | ||||||
|                     ?: return |                     ?: return | ||||||
| 
 | 
 | ||||||
| @ -273,9 +272,7 @@ internal class MXMegolmDecryption(private val userId: String, | |||||||
| 
 | 
 | ||||||
|             keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key |             keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key | ||||||
|         } else { |         } else { | ||||||
|             Timber.v("## CRYPTO | onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId |             Timber.i("## CRYPTO | onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}") | ||||||
|                     + " sessionKey " + roomKeyContent.sessionKey) // from " + event); |  | ||||||
| 
 |  | ||||||
|             if (null == senderKey) { |             if (null == senderKey) { | ||||||
|                 Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)") |                 Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)") | ||||||
|                 return |                 return | ||||||
| @ -285,7 +282,7 @@ internal class MXMegolmDecryption(private val userId: String, | |||||||
|             keysClaimed = event.getKeysClaimed().toMutableMap() |             keysClaimed = event.getKeysClaimed().toMutableMap() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Timber.e("## CRYPTO | onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}") |         Timber.i("## CRYPTO | onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}") | ||||||
|         val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId, |         val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId, | ||||||
|                 roomKeyContent.sessionKey, |                 roomKeyContent.sessionKey, | ||||||
|                 roomKeyContent.roomId, |                 roomKeyContent.roomId, | ||||||
| @ -349,10 +346,10 @@ internal class MXMegolmDecryption(private val userId: String, | |||||||
|                             if (olmSessionResult?.sessionId == null) { |                             if (olmSessionResult?.sessionId == null) { | ||||||
|                                 // no session with this device, probably because there |                                 // no session with this device, probably because there | ||||||
|                                 // were no one-time keys. |                                 // were no one-time keys. | ||||||
|  |                                 Timber.e("no session with this device $deviceId, probably because there were no one-time keys.") | ||||||
|                                 return@mapCatching |                                 return@mapCatching | ||||||
|                             } |                             } | ||||||
|                             Timber.v("## CRYPTO | shareKeysWithDevice() : sharing keys for session" + |                             Timber.i("## CRYPTO | shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId") | ||||||
|                                     " ${body.senderKey}|${body.sessionId} with device $userId:$deviceId") |  | ||||||
| 
 | 
 | ||||||
|                             val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) |                             val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) | ||||||
|                             runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) } |                             runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) } | ||||||
| @ -363,6 +360,7 @@ internal class MXMegolmDecryption(private val userId: String, | |||||||
|                                             }, |                                             }, | ||||||
|                                             { |                                             { | ||||||
|                                                 // TODO |                                                 // TODO | ||||||
|  |                                                 Timber.e(it, "## CRYPTO | shareKeysWithDevice: failed to get session for request $body") | ||||||
|                                             } |                                             } | ||||||
| 
 | 
 | ||||||
|                                     ) |                                     ) | ||||||
| @ -370,9 +368,13 @@ internal class MXMegolmDecryption(private val userId: String, | |||||||
|                             val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) |                             val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) | ||||||
|                             val sendToDeviceMap = MXUsersDevicesMap<Any>() |                             val sendToDeviceMap = MXUsersDevicesMap<Any>() | ||||||
|                             sendToDeviceMap.setObject(userId, deviceId, encodedPayload) |                             sendToDeviceMap.setObject(userId, deviceId, encodedPayload) | ||||||
|                             Timber.v("## CRYPTO | shareKeysWithDevice() : sending to $userId:$deviceId") |                             Timber.i("## CRYPTO | shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId") | ||||||
|                             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) |                             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) | ||||||
|                             sendToDeviceTask.execute(sendToDeviceParams) |                             try { | ||||||
|  |                                 sendToDeviceTask.execute(sendToDeviceParams) | ||||||
|  |                             } catch (failure: Throwable) { | ||||||
|  |                                 Timber.e(failure, "## CRYPTO | shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId") | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -217,8 +217,10 @@ internal class MXMegolmEncryption( | |||||||
|         Timber.v("## CRYPTO | shareUserDevicesKey() : starts") |         Timber.v("## CRYPTO | shareUserDevicesKey() : starts") | ||||||
| 
 | 
 | ||||||
|         val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser) |         val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser) | ||||||
|         Timber.v("## CRYPTO | shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after " |         Timber.v( | ||||||
|                 + (System.currentTimeMillis() - t0) + " ms") |                 """## CRYPTO | shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms""" | ||||||
|  |                         .trimMargin() | ||||||
|  |         ) | ||||||
|         val contentMap = MXUsersDevicesMap<Any>() |         val contentMap = MXUsersDevicesMap<Any>() | ||||||
|         var haveTargets = false |         var haveTargets = false | ||||||
|         val userIds = results.userIds |         val userIds = results.userIds | ||||||
| @ -242,7 +244,7 @@ internal class MXMegolmEncryption( | |||||||
| 
 | 
 | ||||||
|                     continue |                     continue | ||||||
|                 } |                 } | ||||||
|                 Timber.v("## CRYPTO | shareUserDevicesKey() : Sharing keys with device $userId:$deviceID") |                 Timber.i("## CRYPTO | shareUserDevicesKey() : Sharing keys with device $userId:$deviceID") | ||||||
|                 contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo))) |                 contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo))) | ||||||
|                 haveTargets = true |                 haveTargets = true | ||||||
|             } |             } | ||||||
| @ -270,21 +272,22 @@ internal class MXMegolmEncryption( | |||||||
| 
 | 
 | ||||||
|         if (haveTargets) { |         if (haveTargets) { | ||||||
|             t0 = System.currentTimeMillis() |             t0 = System.currentTimeMillis() | ||||||
|             Timber.v("## CRYPTO | shareUserDevicesKey() : has target") |             Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target") | ||||||
|             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap) |             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap) | ||||||
|             try { |             try { | ||||||
|                 sendToDeviceTask.execute(sendToDeviceParams) |                 sendToDeviceTask.execute(sendToDeviceParams) | ||||||
|                 Timber.v("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms") |                 Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms") | ||||||
|             } catch (failure: Throwable) { |             } catch (failure: Throwable) { | ||||||
|                 // What to do here... |                 // What to do here... | ||||||
|                 Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ") |                 Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ") | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             Timber.v("## CRYPTO | shareUserDevicesKey() : no need to sharekey") |             Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun notifyKeyWithHeld(targets: List<UserDevice>, sessionId: String, senderKey: String?, code: WithHeldCode) { |     private fun notifyKeyWithHeld(targets: List<UserDevice>, sessionId: String, senderKey: String?, code: WithHeldCode) { | ||||||
|  |         Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId ") | ||||||
|         val withHeldContent = RoomKeyWithHeldContent( |         val withHeldContent = RoomKeyWithHeldContent( | ||||||
|                 roomId = roomId, |                 roomId = roomId, | ||||||
|                 senderKey = senderKey, |                 senderKey = senderKey, | ||||||
| @ -393,16 +396,16 @@ internal class MXMegolmEncryption( | |||||||
|                                     userId: String, |                                     userId: String, | ||||||
|                                     deviceId: String, |                                     deviceId: String, | ||||||
|                                     senderKey: String): Boolean { |                                     senderKey: String): Boolean { | ||||||
|         Timber.d("[MXMegolmEncryption] reshareKey: $sessionId to $userId:$deviceId") |         Timber.i("## Crypto process reshareKey for $sessionId to $userId:$deviceId") | ||||||
|         val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false |         val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false | ||||||
|                 .also { Timber.w("Device not found") } |                 .also { Timber.w("## Crypto reshareKey: Device not found") } | ||||||
| 
 | 
 | ||||||
|         // Get the chain index of the key we previously sent this device |         // Get the chain index of the key we previously sent this device | ||||||
|         val chainIndex = outboundSession?.sharedWithHelper?.wasSharedWith(userId, deviceId) ?: return false |         val chainIndex = outboundSession?.sharedWithHelper?.wasSharedWith(userId, deviceId) ?: return false | ||||||
|                 .also { |                 .also { | ||||||
|                     // Send a room key with held |                     // Send a room key with held | ||||||
|                     notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED) |                     notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED) | ||||||
|                     Timber.w("[MXMegolmEncryption] reshareKey : ERROR : Never share megolm with this device") |                     Timber.w("## Crypto reshareKey: ERROR : Never share megolm with this device") | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|         val devicesByUser = mapOf(userId to listOf(deviceInfo)) |         val devicesByUser = mapOf(userId to listOf(deviceInfo)) | ||||||
| @ -411,9 +414,11 @@ internal class MXMegolmEncryption( | |||||||
|         olmSessionResult?.sessionId |         olmSessionResult?.sessionId | ||||||
|                 ?: // no session with this device, probably because there were no one-time keys. |                 ?: // no session with this device, probably because there were no one-time keys. | ||||||
|                 // ensureOlmSessionsForDevicesAction has already done the logging, so just skip it. |                 // ensureOlmSessionsForDevicesAction has already done the logging, so just skip it. | ||||||
|                 return false |                 return false.also { | ||||||
|  |                     Timber.w("## Crypto reshareKey: no session with this device, probably because there were no one-time keys") | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|         Timber.d("[MXMegolmEncryption] reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId") |         Timber.i("[MXMegolmEncryption] reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId") | ||||||
| 
 | 
 | ||||||
|         val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) |         val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) | ||||||
| 
 | 
 | ||||||
| @ -425,6 +430,7 @@ internal class MXMegolmEncryption( | |||||||
|                         }, |                         }, | ||||||
|                         { |                         { | ||||||
|                             // TODO |                             // TODO | ||||||
|  |                             Timber.e(it, "[MXMegolmEncryption] reshareKey: failed to get session $sessionId|$senderKey|$roomId") | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                 ) |                 ) | ||||||
| @ -432,13 +438,14 @@ internal class MXMegolmEncryption( | |||||||
|         val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) |         val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) | ||||||
|         val sendToDeviceMap = MXUsersDevicesMap<Any>() |         val sendToDeviceMap = MXUsersDevicesMap<Any>() | ||||||
|         sendToDeviceMap.setObject(userId, deviceId, encodedPayload) |         sendToDeviceMap.setObject(userId, deviceId, encodedPayload) | ||||||
|         Timber.v("## CRYPTO | CRYPTO | reshareKey() : sending to $userId:$deviceId") |         Timber.i("## CRYPTO | reshareKey() : sending session $sessionId to $userId:$deviceId") | ||||||
|         val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) |         val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) | ||||||
|         return try { |         return try { | ||||||
|             sendToDeviceTask.execute(sendToDeviceParams) |             sendToDeviceTask.execute(sendToDeviceParams) | ||||||
|  |             Timber.i("## CRYPTO reshareKey() : successfully send <$sessionId> to $userId:$deviceId") | ||||||
|             true |             true | ||||||
|         } catch (failure: Throwable) { |         } catch (failure: Throwable) { | ||||||
|             Timber.e(failure, "## CRYPTO | CRYPTO | reshareKey() : fail to send <$sessionId> to $userId:$deviceId") |             Timber.e(failure, "## CRYPTO reshareKey() : fail to send <$sessionId> to $userId:$deviceId") | ||||||
|             false |             false | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -38,7 +38,6 @@ import org.matrix.android.sdk.internal.task.TaskThread | |||||||
| import org.matrix.android.sdk.internal.task.configureWith | import org.matrix.android.sdk.internal.task.configureWith | ||||||
| import org.matrix.android.sdk.internal.util.JsonCanonicalizer | import org.matrix.android.sdk.internal.util.JsonCanonicalizer | ||||||
| import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers | import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers | ||||||
| import org.matrix.android.sdk.internal.util.withoutPrefix |  | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo | import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo | ||||||
| @ -444,7 +443,7 @@ internal class DefaultCrossSigningService @Inject constructor( | |||||||
|         } else { |         } else { | ||||||
|             // Maybe it's signed by a locally trusted device? |             // Maybe it's signed by a locally trusted device? | ||||||
|             myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> |             myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> | ||||||
|                 val potentialDeviceId = key.withoutPrefix("ed25519:") |                 val potentialDeviceId = key.removePrefix("ed25519:") | ||||||
|                 val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) |                 val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) | ||||||
|                 if (potentialDevice != null && potentialDevice.isVerified) { |                 if (potentialDevice != null && potentialDevice.isVerified) { | ||||||
|                     // Check signature validity? |                     // Check signature validity? | ||||||
|  | |||||||
| @ -241,9 +241,9 @@ internal class UpdateTrustWorker(context: Context, | |||||||
|     private fun computeRoomShield(activeMemberUserIds: List<String>, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { |     private fun computeRoomShield(activeMemberUserIds: List<String>, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { | ||||||
|         Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds") |         Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds") | ||||||
|         // The set of “all users” depends on the type of room: |         // The set of “all users” depends on the type of room: | ||||||
|         // For regular / topic rooms, all users including yourself, are considered when decorating a room |         // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room | ||||||
|         // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room |         // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room | ||||||
|         val listToCheck = if (roomSummaryEntity.isDirect) { |         val listToCheck = if (roomSummaryEntity.isDirect || activeMemberUserIds.size <= 2) { | ||||||
|             activeMemberUserIds.filter { it != myUserId } |             activeMemberUserIds.filter { it != myUserId } | ||||||
|         } else { |         } else { | ||||||
|             activeMemberUserIds |             activeMemberUserIds | ||||||
|  | |||||||
| @ -1679,27 +1679,24 @@ internal class RealmCryptoStore @Inject constructor( | |||||||
|             // Only keep one week history |             // Only keep one week history | ||||||
|             realm.where<IncomingGossipingRequestEntity>() |             realm.where<IncomingGossipingRequestEntity>() | ||||||
|                     .lessThan(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, prevWeekTs) |                     .lessThan(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, prevWeekTs) | ||||||
|                     .findAll().let { |                     .findAll() | ||||||
|                         Timber.i("## Crypto Clean up ${it.size} IncomingGossipingRequestEntity") |                     .also { Timber.i("## Crypto Clean up ${it.size} IncomingGossipingRequestEntity") } | ||||||
|                         it.deleteAllFromRealm() |                     .deleteAllFromRealm() | ||||||
|                     } |  | ||||||
| 
 | 
 | ||||||
|             // Clean the cancelled ones? |             // Clean the cancelled ones? | ||||||
|             realm.where<OutgoingGossipingRequestEntity>() |             realm.where<OutgoingGossipingRequestEntity>() | ||||||
|                     .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, OutgoingGossipingRequestState.CANCELLED.name) |                     .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, OutgoingGossipingRequestState.CANCELLED.name) | ||||||
|                     .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) |                     .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) | ||||||
|                     .findAll().let { |                     .findAll() | ||||||
|                         Timber.i("## Crypto Clean up ${it.size} OutgoingGossipingRequestEntity") |                     .also { Timber.i("## Crypto Clean up ${it.size} OutgoingGossipingRequestEntity") } | ||||||
|                         it.deleteAllFromRealm() |                     .deleteAllFromRealm() | ||||||
|                     } |  | ||||||
| 
 | 
 | ||||||
|             // Only keep one week history |             // Only keep one week history | ||||||
|             realm.where<GossipingEventEntity>() |             realm.where<GossipingEventEntity>() | ||||||
|                     .lessThan(GossipingEventEntityFields.AGE_LOCAL_TS, prevWeekTs) |                     .lessThan(GossipingEventEntityFields.AGE_LOCAL_TS, prevWeekTs) | ||||||
|                     .findAll().let { |                     .findAll() | ||||||
|                         Timber.i("## Crypto Clean up ${it.size} GossipingEventEntityFields") |                     .also { Timber.i("## Crypto Clean up ${it.size} GossipingEventEntityFields") } | ||||||
|                         it.deleteAllFromRealm() |                     .deleteAllFromRealm() | ||||||
|                     } |  | ||||||
| 
 | 
 | ||||||
|             // Can we do something for WithHeldSessionEntity? |             // Can we do something for WithHeldSessionEntity? | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -537,11 +537,10 @@ internal class DefaultVerificationService @Inject constructor( | |||||||
|                     // If there is a corresponding request, we can auto accept |                     // If there is a corresponding request, we can auto accept | ||||||
|                     // as we are the one requesting in first place (or we accepted the request) |                     // as we are the one requesting in first place (or we accepted the request) | ||||||
|                     // I need to check if the pending request was related to this device also |                     // I need to check if the pending request was related to this device also | ||||||
|                     val autoAccept = getExistingVerificationRequest(otherUserId)?.any { |                     val autoAccept = getExistingVerificationRequests(otherUserId).any { | ||||||
|                         it.transactionId == startReq.transactionId |                         it.transactionId == startReq.transactionId | ||||||
|                                 && (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId) |                                 && (it.requestInfo?.fromDevice == this.deviceId || it.readyInfo?.fromDevice == this.deviceId) | ||||||
|                     } |                     } | ||||||
|                             ?: false |  | ||||||
|                     val tx = DefaultIncomingSASDefaultVerificationTransaction( |                     val tx = DefaultIncomingSASDefaultVerificationTransaction( | ||||||
| //                            this, | //                            this, | ||||||
|                             setDeviceVerificationAction, |                             setDeviceVerificationAction, | ||||||
| @ -837,8 +836,8 @@ internal class DefaultVerificationService @Inject constructor( | |||||||
|             // SAS do not care for now? |             // SAS do not care for now? | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Now transactions are udated, let's also update Requests |         // Now transactions are updated, let's also update Requests | ||||||
|         val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == doneReq.transactionId } |         val existingRequest = getExistingVerificationRequests(senderId).find { it.transactionId == doneReq.transactionId } | ||||||
|         if (existingRequest == null) { |         if (existingRequest == null) { | ||||||
|             Timber.e("## SAS Received Done for unknown request txId:${doneReq.transactionId}") |             Timber.e("## SAS Received Done for unknown request txId:${doneReq.transactionId}") | ||||||
|             return |             return | ||||||
| @ -892,7 +891,7 @@ internal class DefaultVerificationService @Inject constructor( | |||||||
|     private fun handleReadyReceived(senderId: String, |     private fun handleReadyReceived(senderId: String, | ||||||
|                                     readyReq: ValidVerificationInfoReady, |                                     readyReq: ValidVerificationInfoReady, | ||||||
|                                     transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) { |                                     transportCreator: (DefaultVerificationTransaction) -> VerificationTransport) { | ||||||
|         val existingRequest = getExistingVerificationRequest(senderId)?.find { it.transactionId == readyReq.transactionId } |         val existingRequest = getExistingVerificationRequests(senderId).find { it.transactionId == readyReq.transactionId } | ||||||
|         if (existingRequest == null) { |         if (existingRequest == null) { | ||||||
|             Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionId} fromDevice ${readyReq.fromDevice}") |             Timber.e("## SAS Received Ready for unknown request txId:${readyReq.transactionId} fromDevice ${readyReq.fromDevice}") | ||||||
|             return |             return | ||||||
| @ -1041,9 +1040,9 @@ internal class DefaultVerificationService @Inject constructor( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun getExistingVerificationRequest(otherUserId: String): List<PendingVerificationRequest>? { |     override fun getExistingVerificationRequests(otherUserId: String): List<PendingVerificationRequest> { | ||||||
|         synchronized(lock = pendingRequests) { |         synchronized(lock = pendingRequests) { | ||||||
|             return pendingRequests[otherUserId] |             return pendingRequests[otherUserId].orEmpty() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1205,7 +1204,7 @@ internal class DefaultVerificationService @Inject constructor( | |||||||
|         Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices") |         Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices") | ||||||
| 
 | 
 | ||||||
|         val targetDevices = otherDevices ?: cryptoStore.getUserDevices(otherUserId) |         val targetDevices = otherDevices ?: cryptoStore.getUserDevices(otherUserId) | ||||||
|                 ?.values?.map { it.deviceId } ?: emptyList() |                 ?.values?.map { it.deviceId }.orEmpty() | ||||||
| 
 | 
 | ||||||
|         val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() } |         val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager | |||||||
| import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction | import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction | ||||||
| import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore | import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore | ||||||
| import org.matrix.android.sdk.internal.extensions.toUnsignedInt | import org.matrix.android.sdk.internal.extensions.toUnsignedInt | ||||||
| import org.matrix.android.sdk.internal.util.withoutPrefix |  | ||||||
| import org.matrix.olm.OlmSAS | import org.matrix.olm.OlmSAS | ||||||
| import org.matrix.olm.OlmUtility | import org.matrix.olm.OlmUtility | ||||||
| import timber.log.Timber | import timber.log.Timber | ||||||
| @ -250,7 +249,7 @@ internal abstract class SASDefaultVerificationTransaction( | |||||||
| 
 | 
 | ||||||
|         // cannot be empty because it has been validated |         // cannot be empty because it has been validated | ||||||
|         theirMacSafe.mac.keys.forEach { |         theirMacSafe.mac.keys.forEach { | ||||||
|             val keyIDNoPrefix = it.withoutPrefix("ed25519:") |             val keyIDNoPrefix = it.removePrefix("ed25519:") | ||||||
|             val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint() |             val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint() | ||||||
|             if (otherDeviceKey == null) { |             if (otherDeviceKey == null) { | ||||||
|                 Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify") |                 Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify") | ||||||
| @ -273,7 +272,7 @@ internal abstract class SASDefaultVerificationTransaction( | |||||||
|         if (otherCrossSigningMasterKeyPublic != null) { |         if (otherCrossSigningMasterKeyPublic != null) { | ||||||
|             // Did the user signed his master key |             // Did the user signed his master key | ||||||
|             theirMacSafe.mac.keys.forEach { |             theirMacSafe.mac.keys.forEach { | ||||||
|                 val keyIDNoPrefix = it.withoutPrefix("ed25519:") |                 val keyIDNoPrefix = it.removePrefix("ed25519:") | ||||||
|                 if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) { |                 if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) { | ||||||
|                     // Check the signature |                     // Check the signature | ||||||
|                     val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it) |                     val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it) | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database | |||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.util.Base64 | import android.util.Base64 | ||||||
| import androidx.core.content.edit | import androidx.core.content.edit | ||||||
|  | import io.realm.Realm | ||||||
| import org.matrix.android.sdk.BuildConfig | import org.matrix.android.sdk.BuildConfig | ||||||
| import org.matrix.android.sdk.internal.session.securestorage.SecretStoringUtils | import org.matrix.android.sdk.internal.session.securestorage.SecretStoringUtils | ||||||
| import io.realm.RealmConfiguration | import io.realm.RealmConfiguration | ||||||
| @ -46,7 +47,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context, | |||||||
|     private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE) |     private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE) | ||||||
| 
 | 
 | ||||||
|     private fun generateKeyForRealm(): ByteArray { |     private fun generateKeyForRealm(): ByteArray { | ||||||
|         val keyForRealm = ByteArray(RealmConfiguration.KEY_LENGTH) |         val keyForRealm = ByteArray(Realm.ENCRYPTION_KEY_LENGTH) | ||||||
|         rng.nextBytes(keyForRealm) |         rng.nextBytes(keyForRealm) | ||||||
|         return keyForRealm |         return keyForRealm | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -69,6 +69,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( | |||||||
|                 .apply { |                 .apply { | ||||||
|                     realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) |                     realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) | ||||||
|                 } |                 } | ||||||
|  |                 .allowWritesOnUiThread(true) | ||||||
|                 .modules(SessionRealmModule()) |                 .modules(SessionRealmModule()) | ||||||
|                 .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) |                 .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) | ||||||
|                 .migration(migration) |                 .migration(migration) | ||||||
|  | |||||||
| @ -52,5 +52,8 @@ internal class TimeOutInterceptor @Inject constructor() : Interceptor { | |||||||
|         const val CONNECT_TIMEOUT = "CONNECT_TIMEOUT" |         const val CONNECT_TIMEOUT = "CONNECT_TIMEOUT" | ||||||
|         const val READ_TIMEOUT = "READ_TIMEOUT" |         const val READ_TIMEOUT = "READ_TIMEOUT" | ||||||
|         const val WRITE_TIMEOUT = "WRITE_TIMEOUT" |         const val WRITE_TIMEOUT = "WRITE_TIMEOUT" | ||||||
|  | 
 | ||||||
|  |         // 1 minute | ||||||
|  |         const val DEFAULT_LONG_TIMEOUT: Long = 60_000 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,45 +16,28 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.internal.raw | package org.matrix.android.sdk.internal.raw | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.raw.RawCacheStrategy | import org.matrix.android.sdk.api.raw.RawCacheStrategy | ||||||
| import org.matrix.android.sdk.api.raw.RawService | import org.matrix.android.sdk.api.raw.RawService | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor |  | ||||||
| import org.matrix.android.sdk.internal.task.configureWith |  | ||||||
| import java.util.concurrent.TimeUnit | import java.util.concurrent.TimeUnit | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| internal class DefaultRawService @Inject constructor( | internal class DefaultRawService @Inject constructor( | ||||||
|         private val taskExecutor: TaskExecutor, |  | ||||||
|         private val getUrlTask: GetUrlTask, |         private val getUrlTask: GetUrlTask, | ||||||
|         private val cleanRawCacheTask: CleanRawCacheTask |         private val cleanRawCacheTask: CleanRawCacheTask | ||||||
| ) : RawService { | ) : RawService { | ||||||
|     override fun getUrl(url: String, |     override suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String { | ||||||
|                         rawCacheStrategy: RawCacheStrategy, |         return getUrlTask.execute(GetUrlTask.Params(url, rawCacheStrategy)) | ||||||
|                         matrixCallback: MatrixCallback<String>): Cancelable { |  | ||||||
|         return getUrlTask |  | ||||||
|                 .configureWith(GetUrlTask.Params(url, rawCacheStrategy)) { |  | ||||||
|                     callback = matrixCallback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun getWellknown(userId: String, |     override suspend fun getWellknown(userId: String): String { | ||||||
|                               matrixCallback: MatrixCallback<String>): Cancelable { |  | ||||||
|         val homeServerDomain = userId.substringAfter(":") |         val homeServerDomain = userId.substringAfter(":") | ||||||
|         return getUrl( |         return getUrl( | ||||||
|                 "https://$homeServerDomain/.well-known/matrix/client", |                 "https://$homeServerDomain/.well-known/matrix/client", | ||||||
|                 RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false), |                 RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) | ||||||
|                 matrixCallback |  | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable { |     override suspend fun clearCache() { | ||||||
|         return cleanRawCacheTask |         cleanRawCacheTask.execute(Unit) | ||||||
|                 .configureWith(Unit) { |  | ||||||
|                     callback = matrixCallback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,30 +16,17 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.internal.session.account | package org.matrix.android.sdk.internal.session.account | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.session.account.AccountService | import org.matrix.android.sdk.api.session.account.AccountService | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor |  | ||||||
| import org.matrix.android.sdk.internal.task.configureWith |  | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| internal class DefaultAccountService @Inject constructor(private val changePasswordTask: ChangePasswordTask, | internal class DefaultAccountService @Inject constructor(private val changePasswordTask: ChangePasswordTask, | ||||||
|                                                          private val deactivateAccountTask: DeactivateAccountTask, |                                                          private val deactivateAccountTask: DeactivateAccountTask) : AccountService { | ||||||
|                                                          private val taskExecutor: TaskExecutor) : AccountService { |  | ||||||
| 
 | 
 | ||||||
|     override fun changePassword(password: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun changePassword(password: String, newPassword: String) { | ||||||
|         return changePasswordTask |         changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword)) | ||||||
|                 .configureWith(ChangePasswordTask.Params(password, newPassword)) { |  | ||||||
|                     this.callback = callback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun deactivateAccount(password: String, eraseAllData: Boolean, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun deactivateAccount(password: String, eraseAllData: Boolean) { | ||||||
|         return deactivateAccountTask |         deactivateAccountTask.execute(DeactivateAccountTask.Params(password, eraseAllData)) | ||||||
|                 .configureWith(DeactivateAccountTask.Params(password, eraseAllData)) { |  | ||||||
|                     this.callback = callback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,20 +16,13 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.internal.session.group | package org.matrix.android.sdk.internal.session.group | ||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.session.group.Group | import org.matrix.android.sdk.api.session.group.Group | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor |  | ||||||
| import org.matrix.android.sdk.internal.task.configureWith |  | ||||||
| 
 | 
 | ||||||
| internal class DefaultGroup(override val groupId: String, | internal class DefaultGroup(override val groupId: String, | ||||||
|                             private val taskExecutor: TaskExecutor, |  | ||||||
|                             private val getGroupDataTask: GetGroupDataTask) : Group { |                             private val getGroupDataTask: GetGroupDataTask) : Group { | ||||||
| 
 | 
 | ||||||
|     override fun fetchGroupData(callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun fetchGroupData() { | ||||||
|         val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId)) |         val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId)) | ||||||
|         return getGroupDataTask.configureWith(params) { |         getGroupDataTask.execute(params) | ||||||
|             this.callback = callback |  | ||||||
|         }.executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.group | |||||||
| 
 | 
 | ||||||
| import org.matrix.android.sdk.api.session.group.Group | import org.matrix.android.sdk.api.session.group.Group | ||||||
| import org.matrix.android.sdk.internal.session.SessionScope | import org.matrix.android.sdk.internal.session.SessionScope | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor |  | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| internal interface GroupFactory { | internal interface GroupFactory { | ||||||
| @ -26,14 +25,12 @@ internal interface GroupFactory { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @SessionScope | @SessionScope | ||||||
| internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask, | internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask) : | ||||||
|                                                        private val taskExecutor: TaskExecutor) : |  | ||||||
|         GroupFactory { |         GroupFactory { | ||||||
| 
 | 
 | ||||||
|     override fun create(groupId: String): Group { |     override fun create(groupId: String): Group { | ||||||
|         return DefaultGroup( |         return DefaultGroup( | ||||||
|                 groupId = groupId, |                 groupId = groupId, | ||||||
|                 taskExecutor = taskExecutor, |  | ||||||
|                 getGroupDataTask = getGroupDataTask |                 getGroupDataTask = getGroupDataTask | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers | |||||||
| import org.matrix.android.sdk.internal.util.ensureProtocol | import org.matrix.android.sdk.internal.util.ensureProtocol | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
| import okhttp3.OkHttpClient | import okhttp3.OkHttpClient | ||||||
|  | import org.matrix.android.sdk.api.extensions.orFalse | ||||||
| import timber.log.Timber | import timber.log.Timber | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| import javax.net.ssl.HttpsURLConnection | import javax.net.ssl.HttpsURLConnection | ||||||
| @ -243,7 +244,20 @@ internal class DefaultIdentityService @Inject constructor( | |||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     override fun getUserConsent(): Boolean { | ||||||
|  |         return identityStore.getIdentityData()?.userConsent.orFalse() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun setUserConsent(newValue: Boolean) { | ||||||
|  |         identityStore.setUserConsent(newValue) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable { |     override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable { | ||||||
|  |         if (!getUserConsent()) { | ||||||
|  |             callback.onFailure(IdentityServiceError.UserConsentNotProvided) | ||||||
|  |             return NoOpCancellable | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (threePids.isEmpty()) { |         if (threePids.isEmpty()) { | ||||||
|             callback.onSuccess(emptyList()) |             callback.onSuccess(emptyList()) | ||||||
|             return NoOpCancellable |             return NoOpCancellable | ||||||
| @ -255,6 +269,9 @@ internal class DefaultIdentityService @Inject constructor( | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable { |     override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable { | ||||||
|  |         // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent | ||||||
|  |         // to the home server, and not emails and phone numbers from the contact book of the user | ||||||
|  | 
 | ||||||
|         if (threePids.isEmpty()) { |         if (threePids.isEmpty()) { | ||||||
|             callback.onSuccess(emptyMap()) |             callback.onSuccess(emptyMap()) | ||||||
|             return NoOpCancellable |             return NoOpCancellable | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.identity.db.IdentityRealmModule | |||||||
| import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStore | import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStore | ||||||
| import io.realm.RealmConfiguration | import io.realm.RealmConfiguration | ||||||
| import okhttp3.OkHttpClient | import okhttp3.OkHttpClient | ||||||
|  | import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStoreMigration | ||||||
| import java.io.File | import java.io.File | ||||||
| 
 | 
 | ||||||
| @Module | @Module | ||||||
| @ -59,6 +60,7 @@ internal abstract class IdentityModule { | |||||||
|         @SessionScope |         @SessionScope | ||||||
|         fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, |         fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, | ||||||
|                                                @SessionFilesDirectory directory: File, |                                                @SessionFilesDirectory directory: File, | ||||||
|  |                                                migration: RealmIdentityStoreMigration, | ||||||
|                                                @UserMd5 userMd5: String): RealmConfiguration { |                                                @UserMd5 userMd5: String): RealmConfiguration { | ||||||
|             return RealmConfiguration.Builder() |             return RealmConfiguration.Builder() | ||||||
|                     .directory(directory) |                     .directory(directory) | ||||||
| @ -66,6 +68,9 @@ internal abstract class IdentityModule { | |||||||
|                     .apply { |                     .apply { | ||||||
|                         realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) |                         realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) | ||||||
|                     } |                     } | ||||||
|  |                     .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION) | ||||||
|  |                     .migration(migration) | ||||||
|  |                     .allowWritesOnUiThread(true) | ||||||
|                     .modules(IdentityRealmModule()) |                     .modules(IdentityRealmModule()) | ||||||
|                     .build() |                     .build() | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -20,5 +20,6 @@ internal data class IdentityData( | |||||||
|         val identityServerUrl: String?, |         val identityServerUrl: String?, | ||||||
|         val token: String?, |         val token: String?, | ||||||
|         val hashLookupPepper: String?, |         val hashLookupPepper: String?, | ||||||
|         val hashLookupAlgorithm: List<String> |         val hashLookupAlgorithm: List<String>, | ||||||
|  |         val userConsent: Boolean | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -27,6 +27,8 @@ internal interface IdentityStore { | |||||||
| 
 | 
 | ||||||
|     fun setToken(token: String?) |     fun setToken(token: String?) | ||||||
| 
 | 
 | ||||||
|  |     fun setUserConsent(consent: Boolean) | ||||||
|  | 
 | ||||||
|     fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) |     fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -23,7 +23,8 @@ internal open class IdentityDataEntity( | |||||||
|         var identityServerUrl: String? = null, |         var identityServerUrl: String? = null, | ||||||
|         var token: String? = null, |         var token: String? = null, | ||||||
|         var hashLookupPepper: String? = null, |         var hashLookupPepper: String? = null, | ||||||
|         var hashLookupAlgorithm: RealmList<String> = RealmList() |         var hashLookupAlgorithm: RealmList<String> = RealmList(), | ||||||
|  |         var userConsent: Boolean = false | ||||||
| ) : RealmObject() { | ) : RealmObject() { | ||||||
| 
 | 
 | ||||||
|     companion object |     companion object | ||||||
|  | |||||||
| @ -52,6 +52,13 @@ internal fun IdentityDataEntity.Companion.setToken(realm: Realm, | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | internal fun IdentityDataEntity.Companion.setUserConsent(realm: Realm, | ||||||
|  |                                                          newConsent: Boolean) { | ||||||
|  |     get(realm)?.apply { | ||||||
|  |         userConsent = newConsent | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm, | internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm, | ||||||
|                                                          pepper: String, |                                                          pepper: String, | ||||||
|                                                          algorithms: List<String>) { |                                                          algorithms: List<String>) { | ||||||
|  | |||||||
| @ -26,7 +26,8 @@ internal object IdentityMapper { | |||||||
|                 identityServerUrl = entity.identityServerUrl, |                 identityServerUrl = entity.identityServerUrl, | ||||||
|                 token = entity.token, |                 token = entity.token, | ||||||
|                 hashLookupPepper = entity.hashLookupPepper, |                 hashLookupPepper = entity.hashLookupPepper, | ||||||
|                 hashLookupAlgorithm = entity.hashLookupAlgorithm.toList() |                 hashLookupAlgorithm = entity.hashLookupAlgorithm.toList(), | ||||||
|  |                 userConsent = entity.userConsent | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -55,6 +55,14 @@ internal class RealmIdentityStore @Inject constructor( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     override fun setUserConsent(consent: Boolean) { | ||||||
|  |         Realm.getInstance(realmConfiguration).use { | ||||||
|  |             it.executeTransaction { realm -> | ||||||
|  |                 IdentityDataEntity.setUserConsent(realm, consent) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) { |     override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) { | ||||||
|         Realm.getInstance(realmConfiguration).use { |         Realm.getInstance(realmConfiguration).use { | ||||||
|             it.executeTransaction { realm -> |             it.executeTransaction { realm -> | ||||||
|  | |||||||
| @ -0,0 +1,43 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2020 The Matrix.org Foundation C.I.C. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package org.matrix.android.sdk.internal.session.identity.db | ||||||
|  | 
 | ||||||
|  | import io.realm.DynamicRealm | ||||||
|  | import io.realm.RealmMigration | ||||||
|  | import timber.log.Timber | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration { | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         const val IDENTITY_STORE_SCHEMA_VERSION = 1L | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { | ||||||
|  |         Timber.v("Migrating Realm Identity from $oldVersion to $newVersion") | ||||||
|  | 
 | ||||||
|  |         if (oldVersion <= 0) migrateTo1(realm) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun migrateTo1(realm: DynamicRealm) { | ||||||
|  |         Timber.d("Step 0 -> 1") | ||||||
|  |         Timber.d("Add field userConsent (Boolean) and set the value to false") | ||||||
|  | 
 | ||||||
|  |         realm.schema.get("IdentityDataEntity") | ||||||
|  |                 ?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -16,7 +16,6 @@ | |||||||
| package org.matrix.android.sdk.internal.session.notification | package org.matrix.android.sdk.internal.session.notification | ||||||
| 
 | 
 | ||||||
| import com.zhuinden.monarchy.Monarchy | import com.zhuinden.monarchy.Monarchy | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.pushrules.PushRuleService | import org.matrix.android.sdk.api.pushrules.PushRuleService | ||||||
| import org.matrix.android.sdk.api.pushrules.RuleKind | import org.matrix.android.sdk.api.pushrules.RuleKind | ||||||
| import org.matrix.android.sdk.api.pushrules.RuleSetKey | import org.matrix.android.sdk.api.pushrules.RuleSetKey | ||||||
| @ -24,7 +23,6 @@ import org.matrix.android.sdk.api.pushrules.getActions | |||||||
| import org.matrix.android.sdk.api.pushrules.rest.PushRule | import org.matrix.android.sdk.api.pushrules.rest.PushRule | ||||||
| import org.matrix.android.sdk.api.pushrules.rest.RuleSet | import org.matrix.android.sdk.api.pushrules.rest.RuleSet | ||||||
| import org.matrix.android.sdk.api.session.events.model.Event | import org.matrix.android.sdk.api.session.events.model.Event | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper | import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper | ||||||
| import org.matrix.android.sdk.internal.database.model.PushRulesEntity | import org.matrix.android.sdk.internal.database.model.PushRulesEntity | ||||||
| import org.matrix.android.sdk.internal.database.query.where | import org.matrix.android.sdk.internal.database.query.where | ||||||
| @ -103,37 +101,21 @@ internal class DefaultPushRuleService @Inject constructor( | |||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean) { | ||||||
|         // The rules will be updated, and will come back from the next sync response |         // The rules will be updated, and will come back from the next sync response | ||||||
|         return updatePushRuleEnableStatusTask |         updatePushRuleEnableStatusTask.execute(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) | ||||||
|                 .configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) { |  | ||||||
|                     this.callback = callback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) { | ||||||
|         return addPushRuleTask |         addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule)) | ||||||
|                 .configureWith(AddPushRuleTask.Params(kind, pushRule)) { |  | ||||||
|                     this.callback = callback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) { | ||||||
|         return updatePushRuleActionsTask |         updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) | ||||||
|                 .configureWith(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) { |  | ||||||
|                     this.callback = callback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) { | ||||||
|         return removePushRuleTask |         removePushRuleTask.execute(RemovePushRuleTask.Params(kind, pushRule)) | ||||||
|                 .configureWith(RemovePushRuleTask.Params(kind, pushRule)) { |  | ||||||
|                     this.callback = callback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) { |     override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) { | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity | |||||||
| import org.matrix.android.sdk.internal.database.model.UserThreePidEntity | import org.matrix.android.sdk.internal.database.model.UserThreePidEntity | ||||||
| import org.matrix.android.sdk.internal.di.SessionDatabase | import org.matrix.android.sdk.internal.di.SessionDatabase | ||||||
| import org.matrix.android.sdk.internal.session.content.FileUploader | import org.matrix.android.sdk.internal.session.content.FileUploader | ||||||
|  | import org.matrix.android.sdk.internal.session.user.UserStore | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor | import org.matrix.android.sdk.internal.task.TaskExecutor | ||||||
| import org.matrix.android.sdk.internal.task.configureWith | import org.matrix.android.sdk.internal.task.configureWith | ||||||
| import org.matrix.android.sdk.internal.task.launchToCallback | import org.matrix.android.sdk.internal.task.launchToCallback | ||||||
| @ -49,6 +50,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto | |||||||
|                                                          private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask, |                                                          private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask, | ||||||
|                                                          private val deleteThreePidTask: DeleteThreePidTask, |                                                          private val deleteThreePidTask: DeleteThreePidTask, | ||||||
|                                                          private val pendingThreePidMapper: PendingThreePidMapper, |                                                          private val pendingThreePidMapper: PendingThreePidMapper, | ||||||
|  |                                                          private val userStore: UserStore, | ||||||
|                                                          private val fileUploader: FileUploader) : ProfileService { |                                                          private val fileUploader: FileUploader) : ProfileService { | ||||||
| 
 | 
 | ||||||
|     override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable { |     override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable { | ||||||
| @ -70,17 +72,17 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable { |     override fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable { | ||||||
|         return setDisplayNameTask |         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.io, matrixCallback) { | ||||||
|                 .configureWith(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName)) { |             setDisplayNameTask.execute(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName)) | ||||||
|                     callback = matrixCallback |             userStore.updateDisplayName(userId, newDisplayName) | ||||||
|                 } |         } | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable { |     override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable { | ||||||
|         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) { |         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) { | ||||||
|             val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg") |             val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg") | ||||||
|             setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) |             setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) | ||||||
|  |             userStore.updateAvatar(userId, response.contentUri) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -101,13 +101,13 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, | |||||||
|         return cryptoService.shouldEncryptForInvitedMembers(roomId) |         return cryptoService.shouldEncryptForInvitedMembers(roomId) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>) { |     override suspend fun enableEncryption(algorithm: String) { | ||||||
|         when { |         when { | ||||||
|             isEncrypted()                          -> { |             isEncrypted()                          -> { | ||||||
|                 callback.onFailure(IllegalStateException("Encryption is already enabled for this room")) |                 throw IllegalStateException("Encryption is already enabled for this room") | ||||||
|             } |             } | ||||||
|             algorithm != MXCRYPTO_ALGORITHM_MEGOLM -> { |             algorithm != MXCRYPTO_ALGORITHM_MEGOLM -> { | ||||||
|                 callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")) |                 throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported") | ||||||
|             } |             } | ||||||
|             else                                   -> { |             else                                   -> { | ||||||
|                 val params = SendStateTask.Params( |                 val params = SendStateTask.Params( | ||||||
| @ -118,11 +118,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, | |||||||
|                                 "algorithm" to algorithm |                                 "algorithm" to algorithm | ||||||
|                         )) |                         )) | ||||||
| 
 | 
 | ||||||
|                 sendStateTask |                 sendStateTask.execute(params) | ||||||
|                         .configureWith(params) { |  | ||||||
|                             this.callback = callback |  | ||||||
|                         } |  | ||||||
|                         .executeBy(taskExecutor) |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -17,27 +17,37 @@ | |||||||
| package org.matrix.android.sdk.internal.session.room | package org.matrix.android.sdk.internal.session.room | ||||||
| 
 | 
 | ||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
|  | import androidx.lifecycle.Transformations | ||||||
|  | import com.zhuinden.monarchy.Monarchy | ||||||
| import org.matrix.android.sdk.api.MatrixCallback | import org.matrix.android.sdk.api.MatrixCallback | ||||||
| import org.matrix.android.sdk.api.session.room.Room | import org.matrix.android.sdk.api.session.room.Room | ||||||
| import org.matrix.android.sdk.api.session.room.RoomService | import org.matrix.android.sdk.api.session.room.RoomService | ||||||
| import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams | import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams | ||||||
| import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState | import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState | ||||||
|  | import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary | ||||||
| import org.matrix.android.sdk.api.session.room.model.RoomSummary | import org.matrix.android.sdk.api.session.room.model.RoomSummary | ||||||
| import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams | import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams | ||||||
| import org.matrix.android.sdk.api.util.Cancelable | import org.matrix.android.sdk.api.util.Cancelable | ||||||
| import org.matrix.android.sdk.api.util.Optional | import org.matrix.android.sdk.api.util.Optional | ||||||
|  | import org.matrix.android.sdk.api.util.toOptional | ||||||
|  | import org.matrix.android.sdk.internal.database.mapper.asDomain | ||||||
|  | import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields | ||||||
|  | import org.matrix.android.sdk.internal.di.SessionDatabase | ||||||
| import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask | import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask | ||||||
| import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask | import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask | ||||||
| import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource | import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource | ||||||
|  | import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper | ||||||
| import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask | import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask | ||||||
| import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask | import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask | ||||||
| import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource | import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource | ||||||
| import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask | import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor | import org.matrix.android.sdk.internal.task.TaskExecutor | ||||||
| import org.matrix.android.sdk.internal.task.configureWith | import org.matrix.android.sdk.internal.task.configureWith | ||||||
|  | import org.matrix.android.sdk.internal.util.fetchCopied | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| internal class DefaultRoomService @Inject constructor( | internal class DefaultRoomService @Inject constructor( | ||||||
|  |         @SessionDatabase private val monarchy: Monarchy, | ||||||
|         private val createRoomTask: CreateRoomTask, |         private val createRoomTask: CreateRoomTask, | ||||||
|         private val joinRoomTask: JoinRoomTask, |         private val joinRoomTask: JoinRoomTask, | ||||||
|         private val markAllRoomsReadTask: MarkAllRoomsReadTask, |         private val markAllRoomsReadTask: MarkAllRoomsReadTask, | ||||||
| @ -118,4 +128,24 @@ internal class DefaultRoomService @Inject constructor( | |||||||
|     override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> { |     override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> { | ||||||
|         return roomChangeMembershipStateDataSource.getLiveStates() |         return roomChangeMembershipStateDataSource.getLiveStates() | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     override fun getRoomMember(userId: String, roomId: String): RoomMemberSummary? { | ||||||
|  |         val roomMemberEntity = monarchy.fetchCopied { | ||||||
|  |             RoomMemberHelper(it, roomId).getLastRoomMember(userId) | ||||||
|  |         } | ||||||
|  |         return roomMemberEntity?.asDomain() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun getRoomMemberLive(userId: String, roomId: String): LiveData<Optional<RoomMemberSummary>> { | ||||||
|  |         val liveData = monarchy.findAllMappedWithChanges( | ||||||
|  |                 { realm -> | ||||||
|  |                     RoomMemberHelper(realm, roomId).queryRoomMembersEvent() | ||||||
|  |                             .equalTo(RoomMemberSummaryEntityFields.USER_ID, userId) | ||||||
|  |                 }, | ||||||
|  |                 { it.asDomain() } | ||||||
|  |         ) | ||||||
|  |         return Transformations.map(liveData) { results -> | ||||||
|  |             results.firstOrNull().toOptional() | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -26,6 +26,8 @@ import org.matrix.android.sdk.internal.database.query.getOrNull | |||||||
| import org.matrix.android.sdk.internal.di.UserId | import org.matrix.android.sdk.internal.di.UserId | ||||||
| import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper | import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper | ||||||
| import io.realm.Realm | import io.realm.Realm | ||||||
|  | import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity | ||||||
|  | import org.matrix.android.sdk.internal.database.query.where | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) { | internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) { | ||||||
| @ -46,11 +48,14 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId | |||||||
|         val roomMembers = RoomMemberHelper(realm, roomId) |         val roomMembers = RoomMemberHelper(realm, roomId) | ||||||
|         val members = roomMembers.queryActiveRoomMembersEvent().findAll() |         val members = roomMembers.queryActiveRoomMembersEvent().findAll() | ||||||
|         // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) |         // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) | ||||||
|         if (members.size == 1) { |         val isDirectRoom =  RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect ?: false | ||||||
|             res = members.firstOrNull()?.avatarUrl |         if (isDirectRoom) { | ||||||
|         } else if (members.size == 2) { |             if (members.size == 1) { | ||||||
|             val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst() |                 res = members.firstOrNull()?.avatarUrl | ||||||
|             res = firstOtherMember?.avatarUrl |             } else if (members.size == 2) { | ||||||
|  |                 val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst() | ||||||
|  |                 res = firstOtherMember?.avatarUrl | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         return res |         return res | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -17,6 +17,9 @@ | |||||||
| package org.matrix.android.sdk.internal.session.room.alias | package org.matrix.android.sdk.internal.session.room.alias | ||||||
| 
 | 
 | ||||||
| import com.zhuinden.monarchy.Monarchy | import com.zhuinden.monarchy.Monarchy | ||||||
|  | import io.realm.Realm | ||||||
|  | import org.greenrobot.eventbus.EventBus | ||||||
|  | import org.matrix.android.sdk.api.extensions.tryOrNull | ||||||
| import org.matrix.android.sdk.api.util.Optional | import org.matrix.android.sdk.api.util.Optional | ||||||
| import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity | import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity | ||||||
| import org.matrix.android.sdk.internal.database.query.findByAlias | import org.matrix.android.sdk.internal.database.query.findByAlias | ||||||
| @ -24,8 +27,6 @@ import org.matrix.android.sdk.internal.di.SessionDatabase | |||||||
| import org.matrix.android.sdk.internal.network.executeRequest | import org.matrix.android.sdk.internal.network.executeRequest | ||||||
| import org.matrix.android.sdk.internal.session.room.RoomAPI | import org.matrix.android.sdk.internal.session.room.RoomAPI | ||||||
| import org.matrix.android.sdk.internal.task.Task | import org.matrix.android.sdk.internal.task.Task | ||||||
| import io.realm.Realm |  | ||||||
| import org.greenrobot.eventbus.EventBus |  | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> { | internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> { | ||||||
| @ -50,9 +51,11 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor( | |||||||
|         } else if (!params.searchOnServer) { |         } else if (!params.searchOnServer) { | ||||||
|             Optional.from<String>(null) |             Optional.from<String>(null) | ||||||
|         } else { |         } else { | ||||||
|             roomId = executeRequest<RoomAliasDescription>(eventBus) { |             roomId = tryOrNull("## Failed to get roomId from alias") { | ||||||
|                 apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) |                 executeRequest<RoomAliasDescription>(eventBus) { | ||||||
|             }.roomId |                     apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) | ||||||
|  |                 } | ||||||
|  |             }?.roomId | ||||||
|             Optional.from(roomId) |             Optional.from(roomId) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -74,8 +74,8 @@ internal data class CreateRoomBody( | |||||||
|         val invite3pids: List<ThreePidInviteBody>?, |         val invite3pids: List<ThreePidInviteBody>?, | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|          * Extra keys to be added to the content of the m.room.create. |          * Extra keys, such as m.federate, to be added to the content of the m.room.create event. | ||||||
|          * The server will clobber the following keys: creator. |          * The server will clobber the following keys: creator, room_version. | ||||||
|          * Future versions of the specification may allow the server to clobber other keys. |          * Future versions of the specification may allow the server to clobber other keys. | ||||||
|          */ |          */ | ||||||
|         @Json(name = "creation_content") |         @Json(name = "creation_content") | ||||||
|  | |||||||
| @ -81,7 +81,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( | |||||||
|                 topic = params.topic, |                 topic = params.topic, | ||||||
|                 invitedUserIds = params.invitedUserIds, |                 invitedUserIds = params.invitedUserIds, | ||||||
|                 invite3pids = invite3pids, |                 invite3pids = invite3pids, | ||||||
|                 creationContent = params.creationContent, |                 creationContent = params.creationContent.takeIf { it.isNotEmpty() }, | ||||||
|                 initialStates = initialStates, |                 initialStates = initialStates, | ||||||
|                 preset = params.preset, |                 preset = params.preset, | ||||||
|                 isDirect = params.isDirect, |                 isDirect = params.isDirect, | ||||||
|  | |||||||
| @ -31,8 +31,10 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields | |||||||
| import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity | import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity | ||||||
| import org.matrix.android.sdk.internal.database.query.where | import org.matrix.android.sdk.internal.database.query.where | ||||||
| import org.matrix.android.sdk.internal.di.SessionDatabase | import org.matrix.android.sdk.internal.di.SessionDatabase | ||||||
|  | import org.matrix.android.sdk.internal.di.UserId | ||||||
| import org.matrix.android.sdk.internal.network.executeRequest | import org.matrix.android.sdk.internal.network.executeRequest | ||||||
| import org.matrix.android.sdk.internal.session.room.RoomAPI | import org.matrix.android.sdk.internal.session.room.RoomAPI | ||||||
|  | import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription | ||||||
| import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask | import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask | ||||||
| import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper | import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper | ||||||
| import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask | import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask | ||||||
| @ -45,6 +47,7 @@ internal interface CreateRoomTask : Task<CreateRoomParams, String> | |||||||
| 
 | 
 | ||||||
| internal class DefaultCreateRoomTask @Inject constructor( | internal class DefaultCreateRoomTask @Inject constructor( | ||||||
|         private val roomAPI: RoomAPI, |         private val roomAPI: RoomAPI, | ||||||
|  |         @UserId private val userId: String, | ||||||
|         @SessionDatabase private val monarchy: Monarchy, |         @SessionDatabase private val monarchy: Monarchy, | ||||||
|         private val directChatsHelper: DirectChatsHelper, |         private val directChatsHelper: DirectChatsHelper, | ||||||
|         private val updateUserAccountDataTask: UpdateUserAccountDataTask, |         private val updateUserAccountDataTask: UpdateUserAccountDataTask, | ||||||
| @ -61,6 +64,31 @@ internal class DefaultCreateRoomTask @Inject constructor( | |||||||
|                     ?: throw IllegalStateException("You can't create a direct room without an invitedUser") |                     ?: throw IllegalStateException("You can't create a direct room without an invitedUser") | ||||||
|         } else null |         } else null | ||||||
| 
 | 
 | ||||||
|  |         if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) { | ||||||
|  |             if (params.roomAliasName.isNullOrEmpty()) { | ||||||
|  |                 throw CreateRoomFailure.RoomAliasError.AliasEmpty | ||||||
|  |             } | ||||||
|  |             // Check alias availability | ||||||
|  |             val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":") | ||||||
|  |             try { | ||||||
|  |                 executeRequest<RoomAliasDescription>(eventBus) { | ||||||
|  |                     apiCall = roomAPI.getRoomIdByAlias(fullAlias) | ||||||
|  |                 } | ||||||
|  |             } catch (throwable: Throwable) { | ||||||
|  |                 if (throwable is Failure.ServerError && throwable.httpCode == 404) { | ||||||
|  |                     // This is a 404, so the alias is available: nominal case | ||||||
|  |                     null | ||||||
|  |                 } else { | ||||||
|  |                     // Other error, propagate it | ||||||
|  |                     throw throwable | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |                     ?.let { | ||||||
|  |                         // Alias already exists: error case | ||||||
|  |                         throw CreateRoomFailure.RoomAliasError.AliasNotAvailable | ||||||
|  |                     } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         val createRoomBody = createRoomBodyBuilder.build(params) |         val createRoomBody = createRoomBodyBuilder.build(params) | ||||||
| 
 | 
 | ||||||
|         val createRoomResponse = try { |         val createRoomResponse = try { | ||||||
| @ -68,14 +96,18 @@ internal class DefaultCreateRoomTask @Inject constructor( | |||||||
|                 apiCall = roomAPI.createRoom(createRoomBody) |                 apiCall = roomAPI.createRoom(createRoomBody) | ||||||
|             } |             } | ||||||
|         } catch (throwable: Throwable) { |         } catch (throwable: Throwable) { | ||||||
|             if (throwable is Failure.ServerError |             if (throwable is Failure.ServerError) { | ||||||
|                     && throwable.httpCode == 403 |                 if (throwable.httpCode == 403 | ||||||
|                     && throwable.error.code == MatrixError.M_FORBIDDEN |                         && throwable.error.code == MatrixError.M_FORBIDDEN | ||||||
|                     && throwable.error.message.startsWith("Federation denied with")) { |                         && throwable.error.message.startsWith("Federation denied with")) { | ||||||
|                 throw CreateRoomFailure.CreatedWithFederationFailure(throwable.error) |                     throw CreateRoomFailure.CreatedWithFederationFailure(throwable.error) | ||||||
|             } else { |                 } else if (throwable.httpCode == 400 | ||||||
|                 throw throwable |                         && throwable.error.code == MatrixError.M_UNKNOWN | ||||||
|  |                         && throwable.error.message == "Invalid characters in room alias") { | ||||||
|  |                     throw CreateRoomFailure.RoomAliasError.AliasInvalid | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |             throw throwable | ||||||
|         } |         } | ||||||
|         val roomId = createRoomResponse.roomId |         val roomId = createRoomResponse.roomId | ||||||
|         // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) |         // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) | ||||||
|  | |||||||
| @ -19,18 +19,14 @@ package org.matrix.android.sdk.internal.session.room.draft | |||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
| import com.squareup.inject.assisted.Assisted | import com.squareup.inject.assisted.Assisted | ||||||
| import com.squareup.inject.assisted.AssistedInject | import com.squareup.inject.assisted.AssistedInject | ||||||
| import org.matrix.android.sdk.api.MatrixCallback | import kotlinx.coroutines.withContext | ||||||
| import org.matrix.android.sdk.api.session.room.send.DraftService | import org.matrix.android.sdk.api.session.room.send.DraftService | ||||||
| import org.matrix.android.sdk.api.session.room.send.UserDraft | import org.matrix.android.sdk.api.session.room.send.UserDraft | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.api.util.Optional | import org.matrix.android.sdk.api.util.Optional | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor |  | ||||||
| import org.matrix.android.sdk.internal.task.launchToCallback |  | ||||||
| import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers | import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers | ||||||
| 
 | 
 | ||||||
| internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String, | internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String, | ||||||
|                                                                private val draftRepository: DraftRepository, |                                                                private val draftRepository: DraftRepository, | ||||||
|                                                                private val taskExecutor: TaskExecutor, |  | ||||||
|                                                                private val coroutineDispatchers: MatrixCoroutineDispatchers |                                                                private val coroutineDispatchers: MatrixCoroutineDispatchers | ||||||
| ) : DraftService { | ) : DraftService { | ||||||
| 
 | 
 | ||||||
| @ -43,14 +39,14 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private | |||||||
|      * The draft stack can contain several drafts. Depending of the draft to save, it will update the top draft, or create a new draft, |      * The draft stack can contain several drafts. Depending of the draft to save, it will update the top draft, or create a new draft, | ||||||
|      * or even move an existing draft to the top of the list |      * or even move an existing draft to the top of the list | ||||||
|      */ |      */ | ||||||
|     override fun saveDraft(draft: UserDraft, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun saveDraft(draft: UserDraft) { | ||||||
|         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { |         withContext(coroutineDispatchers.main) { | ||||||
|             draftRepository.saveDraft(roomId, draft) |             draftRepository.saveDraft(roomId, draft) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun deleteDraft() { | ||||||
|         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { |         withContext(coroutineDispatchers.main) { | ||||||
|             draftRepository.deleteDraft(roomId) |             draftRepository.deleteDraft(roomId) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -93,6 +93,8 @@ internal class RoomDisplayNameResolver @Inject constructor( | |||||||
|             } |             } | ||||||
|         } else if (roomEntity?.membership == Membership.JOIN) { |         } else if (roomEntity?.membership == Membership.JOIN) { | ||||||
|             val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() |             val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() | ||||||
|  |             val invitedCount = roomSummary?.invitedMembersCount ?: 0 | ||||||
|  |             val joinedCount = roomSummary?.joinedMembersCount ?: 0 | ||||||
|             val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) { |             val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) { | ||||||
|                 roomSummary.heroes.mapNotNull { userId -> |                 roomSummary.heroes.mapNotNull { userId -> | ||||||
|                     roomMembers.getLastRoomMember(userId)?.takeIf { |                     roomMembers.getLastRoomMember(userId)?.takeIf { | ||||||
| @ -102,22 +104,49 @@ internal class RoomDisplayNameResolver @Inject constructor( | |||||||
|             } else { |             } else { | ||||||
|                 activeMembers.where() |                 activeMembers.where() | ||||||
|                         .notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) |                         .notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) | ||||||
|                         .limit(3) |                         .limit(5) | ||||||
|                         .findAll() |                         .findAll() | ||||||
|                         .createSnapshot() |                         .createSnapshot() | ||||||
|             } |             } | ||||||
|             val otherMembersCount = otherMembersSubset.count() |             val otherMembersCount = otherMembersSubset.count() | ||||||
|             name = when (otherMembersCount) { |             name = when (otherMembersCount) { | ||||||
|                 0    -> stringProvider.getString(R.string.room_displayname_empty_room) |                 0    -> { | ||||||
|  |                     stringProvider.getString(R.string.room_displayname_empty_room) | ||||||
|  |                     // TODO (was xx and yyy) ... | ||||||
|  |                 } | ||||||
|                 1    -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) |                 1    -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) | ||||||
|                 2    -> stringProvider.getString(R.string.room_displayname_two_members, |                 2    -> { | ||||||
|                         resolveRoomMemberName(otherMembersSubset[0], roomMembers), |                     stringProvider.getString(R.string.room_displayname_two_members, | ||||||
|                         resolveRoomMemberName(otherMembersSubset[1], roomMembers) |                             resolveRoomMemberName(otherMembersSubset[0], roomMembers), | ||||||
|                 ) |                             resolveRoomMemberName(otherMembersSubset[1], roomMembers) | ||||||
|                 else -> stringProvider.getQuantityString(R.plurals.room_displayname_three_and_more_members, |                     ) | ||||||
|                         roomMembers.getNumberOfJoinedMembers() - 1, |                 } | ||||||
|                         resolveRoomMemberName(otherMembersSubset[0], roomMembers), |                 3    -> { | ||||||
|                         roomMembers.getNumberOfJoinedMembers() - 1) |                     stringProvider.getString(R.string.room_displayname_3_members, | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[0], roomMembers), | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[1], roomMembers), | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[2], roomMembers) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |                 4    -> { | ||||||
|  |                     stringProvider.getString(R.string.room_displayname_4_members, | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[0], roomMembers), | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[1], roomMembers), | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[2], roomMembers), | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[3], roomMembers) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |                 else -> { | ||||||
|  |                     val remainingCount = invitedCount + joinedCount - otherMembersCount + 1 | ||||||
|  |                     stringProvider.getQuantityString( | ||||||
|  |                             R.plurals.room_displayname_four_and_more_members, | ||||||
|  |                             remainingCount, | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[0], roomMembers), | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[1], roomMembers), | ||||||
|  |                             resolveRoomMemberName(otherMembersSubset[2], roomMembers), | ||||||
|  |                             remainingCount | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return name ?: roomId |         return name ?: roomId | ||||||
|  | |||||||
| @ -16,6 +16,8 @@ | |||||||
| 
 | 
 | ||||||
| package org.matrix.android.sdk.internal.session.room.membership | package org.matrix.android.sdk.internal.session.room.membership | ||||||
| 
 | 
 | ||||||
|  | import io.realm.Realm | ||||||
|  | import io.realm.RealmQuery | ||||||
| import org.matrix.android.sdk.api.session.events.model.EventType | import org.matrix.android.sdk.api.session.events.model.EventType | ||||||
| import org.matrix.android.sdk.api.session.room.model.Membership | import org.matrix.android.sdk.api.session.room.model.Membership | ||||||
| import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity | import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity | ||||||
| @ -25,8 +27,6 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie | |||||||
| import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity | import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity | ||||||
| import org.matrix.android.sdk.internal.database.query.getOrNull | import org.matrix.android.sdk.internal.database.query.getOrNull | ||||||
| import org.matrix.android.sdk.internal.database.query.where | import org.matrix.android.sdk.internal.database.query.where | ||||||
| import io.realm.Realm |  | ||||||
| import io.realm.RealmQuery |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * This class is an helper around STATE_ROOM_MEMBER events. |  * This class is an helper around STATE_ROOM_MEMBER events. | ||||||
|  | |||||||
| @ -21,21 +21,16 @@ import androidx.lifecycle.Transformations | |||||||
| import com.squareup.inject.assisted.Assisted | import com.squareup.inject.assisted.Assisted | ||||||
| import com.squareup.inject.assisted.AssistedInject | import com.squareup.inject.assisted.AssistedInject | ||||||
| import com.zhuinden.monarchy.Monarchy | import com.zhuinden.monarchy.Monarchy | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.pushrules.RuleScope | import org.matrix.android.sdk.api.pushrules.RuleScope | ||||||
| import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState | import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState | ||||||
| import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService | import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.internal.database.model.PushRuleEntity | import org.matrix.android.sdk.internal.database.model.PushRuleEntity | ||||||
| import org.matrix.android.sdk.internal.database.query.where | import org.matrix.android.sdk.internal.database.query.where | ||||||
| import org.matrix.android.sdk.internal.di.SessionDatabase | import org.matrix.android.sdk.internal.di.SessionDatabase | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor |  | ||||||
| import org.matrix.android.sdk.internal.task.configureWith |  | ||||||
| 
 | 
 | ||||||
| internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String, | internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String, | ||||||
|                                                                       private val setRoomNotificationStateTask: SetRoomNotificationStateTask, |                                                                       private val setRoomNotificationStateTask: SetRoomNotificationStateTask, | ||||||
|                                                                       @SessionDatabase private val monarchy: Monarchy, |                                                                       @SessionDatabase private val monarchy: Monarchy) | ||||||
|                                                                       private val taskExecutor: TaskExecutor) |  | ||||||
|     : RoomPushRuleService { |     : RoomPushRuleService { | ||||||
| 
 | 
 | ||||||
|     @AssistedInject.Factory |     @AssistedInject.Factory | ||||||
| @ -49,12 +44,8 @@ internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable { |     override suspend fun setRoomNotificationState(roomNotificationState: RoomNotificationState) { | ||||||
|         return setRoomNotificationStateTask |         setRoomNotificationStateTask.execute(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) | ||||||
|                 .configureWith(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) { |  | ||||||
|                     this.callback = matrixCallback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun getPushRuleForRoom(): LiveData<RoomPushRule?> { |     private fun getPushRuleForRoom(): LiveData<RoomPushRule?> { | ||||||
|  | |||||||
| @ -18,14 +18,9 @@ package org.matrix.android.sdk.internal.session.room.reporting | |||||||
| 
 | 
 | ||||||
| import com.squareup.inject.assisted.Assisted | import com.squareup.inject.assisted.Assisted | ||||||
| import com.squareup.inject.assisted.AssistedInject | import com.squareup.inject.assisted.AssistedInject | ||||||
| import org.matrix.android.sdk.api.MatrixCallback |  | ||||||
| import org.matrix.android.sdk.api.session.room.reporting.ReportingService | import org.matrix.android.sdk.api.session.room.reporting.ReportingService | ||||||
| import org.matrix.android.sdk.api.util.Cancelable |  | ||||||
| import org.matrix.android.sdk.internal.task.TaskExecutor |  | ||||||
| import org.matrix.android.sdk.internal.task.configureWith |  | ||||||
| 
 | 
 | ||||||
| internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String, | internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String, | ||||||
|                                                                    private val taskExecutor: TaskExecutor, |  | ||||||
|                                                                    private val reportContentTask: ReportContentTask |                                                                    private val reportContentTask: ReportContentTask | ||||||
| ) : ReportingService { | ) : ReportingService { | ||||||
| 
 | 
 | ||||||
| @ -34,13 +29,8 @@ internal class DefaultReportingService @AssistedInject constructor(@Assisted pri | |||||||
|         fun create(roomId: String): ReportingService |         fun create(roomId: String): ReportingService | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback<Unit>): Cancelable { |     override suspend fun reportContent(eventId: String, score: Int, reason: String) { | ||||||
|         val params = ReportContentTask.Params(roomId, eventId, score, reason) |         val params = ReportContentTask.Params(roomId, eventId, score, reason) | ||||||
| 
 |         reportContentTask.execute(params) | ||||||
|         return reportContentTask |  | ||||||
|                 .configureWith(params) { |  | ||||||
|                     this.callback = callback |  | ||||||
|                 } |  | ||||||
|                 .executeBy(taskExecutor) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.task.TaskExecutor | |||||||
| import org.matrix.android.sdk.internal.task.configureWith | import org.matrix.android.sdk.internal.task.configureWith | ||||||
| import org.matrix.android.sdk.internal.task.launchToCallback | import org.matrix.android.sdk.internal.task.launchToCallback | ||||||
| import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers | import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers | ||||||
|  | import org.matrix.android.sdk.internal.util.awaitCallback | ||||||
| 
 | 
 | ||||||
| internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, | internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, | ||||||
|                                                                private val stateEventDataSource: StateEventDataSource, |                                                                private val stateEventDataSource: StateEventDataSource, | ||||||
| @ -132,23 +133,23 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private | |||||||
|     override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable { |     override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable { | ||||||
|         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { |         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { | ||||||
|             val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") |             val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") | ||||||
|             sendStateEvent( |             awaitCallback<Unit> { | ||||||
|                     eventType = EventType.STATE_ROOM_AVATAR, |                 sendStateEvent( | ||||||
|                     body = mapOf("url" to response.contentUri), |                         eventType = EventType.STATE_ROOM_AVATAR, | ||||||
|                     callback = callback, |                         body = mapOf("url" to response.contentUri), | ||||||
|                     stateKey = null |                         callback = it, | ||||||
|             ) |                         stateKey = null | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun deleteAvatar(callback: MatrixCallback<Unit>): Cancelable { |     override fun deleteAvatar(callback: MatrixCallback<Unit>): Cancelable { | ||||||
|         return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { |         return sendStateEvent( | ||||||
|             sendStateEvent( |                 eventType = EventType.STATE_ROOM_AVATAR, | ||||||
|                     eventType = EventType.STATE_ROOM_AVATAR, |                 body = emptyMap(), | ||||||
|                     body = emptyMap(), |                 callback = callback, | ||||||
|                     callback = callback, |                 stateKey = null | ||||||
|                     stateKey = null |         ) | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user