Merge branch 'development'
|
@ -7,4 +7,4 @@
|
||||||
url = https://github.com/open-keychain/openpgp-api-lib.git
|
url = https://github.com/open-keychain/openpgp-api-lib.git
|
||||||
[submodule "libs/MemorizingTrustManager"]
|
[submodule "libs/MemorizingTrustManager"]
|
||||||
path = libs/MemorizingTrustManager
|
path = libs/MemorizingTrustManager
|
||||||
url = https://github.com/ge0rg/MemorizingTrustManager
|
url = https://github.com/iNPUTmice/MemorizingTrustManager.git
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="eu.siacs.conversations"
|
package="eu.siacs.conversations"
|
||||||
android:versionCode="31"
|
android:versionCode="32"
|
||||||
android:versionName="0.7.3" >
|
android:versionName="0.8-alpha" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="14"
|
android:minSdkVersion="14"
|
||||||
|
@ -22,14 +23,10 @@
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
tools:replace="android:label"
|
||||||
android:theme="@style/ConversationsTheme" >
|
android:theme="@style/ConversationsTheme" >
|
||||||
<service android:name="eu.siacs.conversations.services.XmppConnectionService" />
|
<service android:name="eu.siacs.conversations.services.XmppConnectionService" />
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="eu.siacs.conversations.services.ImageProvider"
|
|
||||||
android:authorities="eu.siacs.conversations.images"
|
|
||||||
android:exported="true" />
|
|
||||||
|
|
||||||
<receiver android:name="eu.siacs.conversations.services.EventReceiver" >
|
<receiver android:name="eu.siacs.conversations.services.EventReceiver" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
|
325
README.md
|
@ -1,19 +1,22 @@
|
||||||
#Conversations
|
# Conversations
|
||||||
Conversations - the very last word in instant messaging
|
|
||||||
|
Conversations: the very last word in instant messaging
|
||||||
|
|
||||||
[![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations)
|
[![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations)
|
||||||
|
|
||||||
![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png)
|
![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png)
|
||||||
|
|
||||||
##Design principles
|
## Design principles
|
||||||
|
|
||||||
* Be as beautiful and easy to use as possible without sacrificing security or
|
* Be as beautiful and easy to use as possible without sacrificing security or
|
||||||
privacy
|
privacy
|
||||||
* Rely on existing, well established protocols (XMPP)
|
* Rely on existing, well established protocols (XMPP)
|
||||||
* Do not require a Google Account or specifically Google Cloud Messaging (GCM)
|
* Do not require a Google Account or specifically Google Cloud Messaging (GCM)
|
||||||
* Require as little permissons as possible
|
* Require as few permissions as possible
|
||||||
|
|
||||||
##Features
|
## Features
|
||||||
* End-to-end encryption with either OTR or openPGP
|
|
||||||
|
* End-to-end encryption with either [OTR](https://otr.cypherpunks.ca/) or [OpenPGP](http://www.openpgp.org/about_openpgp/)
|
||||||
* Sending and receiving images
|
* Sending and receiving images
|
||||||
* Indication when your contact has read your message
|
* Indication when your contact has read your message
|
||||||
* Intuitive UI that follows Android Design guidelines
|
* Intuitive UI that follows Android Design guidelines
|
||||||
|
@ -21,47 +24,56 @@ Conversations - the very last word in instant messaging
|
||||||
* Syncs with desktop client
|
* Syncs with desktop client
|
||||||
* Conferences (with support for bookmarks)
|
* Conferences (with support for bookmarks)
|
||||||
* Address book integration
|
* Address book integration
|
||||||
* Multiple Accounts / unified inbox
|
* Multiple accounts / unified inbox
|
||||||
* Very low impact on battery life
|
* Very low impact on battery life
|
||||||
|
|
||||||
|
|
||||||
###XMPP Features
|
### XMPP Features
|
||||||
Conversations works with every XMPP server out there. However XMPP is an extensible
|
|
||||||
protocol. These extensions are standardized as well in so called XEP’s.
|
Conversations works with every XMPP server out there. However XMPP is an
|
||||||
Conversations supports a couple of those to make the overall user experience better. There is a
|
extensible protocol. These extensions are standardized as well in so called
|
||||||
chance that your current XMPP server does not support these extensions.
|
XEP's. Conversations supports a couple of these to make the overall user
|
||||||
Therefore to get the most out of Conversations you should consider either switching to an
|
experience better. There is a chance that your current XMPP server does not
|
||||||
XMPP server that does or - even better - run your own XMPP server for you and
|
support these extensions; therefore to get the most out of Conversations you
|
||||||
your friends.
|
should consider either switching to an XMPP server that does or — even better —
|
||||||
These XEPs are - as of now:
|
run your own XMPP server for you and your friends. These XEP's are:
|
||||||
* XEP-0065: SOCKS5 Bytestreams - or rather mod_proxy65. Will be used to transfer files if both parties are behind a firewall (NAT).
|
|
||||||
|
* XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Will be used to transfer
|
||||||
|
files if both parties are behind a firewall (NAT).
|
||||||
* XEP-0138: Stream Compression saves bandwidth
|
* XEP-0138: Stream Compression saves bandwidth
|
||||||
* XEP-0163: Personal Eventing Protocol for avatars
|
* XEP-0163: Personal Eventing Protocol for avatars
|
||||||
* XEP-0198: Stream Management allows XMPP to survive small network outages and changes of the underlying TCP connection.
|
* XEP-0198: Stream Management allows XMPP to survive small network outages and
|
||||||
|
changes of the underlying TCP connection.
|
||||||
* XEP-0280: Message Carbons which automatically syncs the messages you send to
|
* XEP-0280: Message Carbons which automatically syncs the messages you send to
|
||||||
your desktop client and thus allows you to switch seamlessly from your mobile
|
your desktop client and thus allows you to switch seamlessly from your mobile
|
||||||
client to your desktop client and back within one conversation.
|
client to your desktop client and back within one conversation.
|
||||||
* XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections
|
* XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections
|
||||||
* XEP-0352: Client State Indication let the server know whether or not
|
* XEP-0352: Client State Indication let the server know whether or not
|
||||||
Conversations is in the background. Allows the server to save bandwidth by
|
Conversations is in the background. Allows the server to save bandwidth by
|
||||||
withholding unimportent packages.
|
withholding unimportant packages.
|
||||||
|
|
||||||
|
## Team
|
||||||
|
|
||||||
|
#### Head of Development
|
||||||
|
|
||||||
##Team
|
|
||||||
####Head of Development
|
|
||||||
* [Daniel Gultsch](https://github.com/inputmice)
|
* [Daniel Gultsch](https://github.com/inputmice)
|
||||||
|
|
||||||
####Code Contributions
|
#### Code Contributions
|
||||||
|
|
||||||
(In order of appearance)
|
(In order of appearance)
|
||||||
|
|
||||||
* [Rene Treffer](https://github.com/rtreffer)
|
* [Rene Treffer](https://github.com/rtreffer)
|
||||||
* [Andreas Straub](https://github.com/strb)
|
* [Andreas Straub](https://github.com/strb)
|
||||||
* [Alethea Butler](https://github.com/alethea)
|
* [Alethea Butler](https://github.com/alethea)
|
||||||
* [M. Dietrich](https://github.com/emdete)
|
* [M. Dietrich](https://github.com/emdete)
|
||||||
* [betheg](https://github.com/betheg)
|
* [betheg](https://github.com/betheg)
|
||||||
|
|
||||||
####Logo
|
#### Logo
|
||||||
|
|
||||||
* [Diego Turtulici](http://efesto.eigenlab.org/~diesys)
|
* [Diego Turtulici](http://efesto.eigenlab.org/~diesys)
|
||||||
|
|
||||||
####Translations
|
#### Translations
|
||||||
|
|
||||||
* [Sergio Cárdenas](https://github.com/kruks23) (Spanish)
|
* [Sergio Cárdenas](https://github.com/kruks23) (Spanish)
|
||||||
* [Benoit Bouvarel](https://github.com/BenoitBouvarel) (French)
|
* [Benoit Bouvarel](https://github.com/BenoitBouvarel) (French)
|
||||||
* [Daniel Gultsch](https://github.com/iNPUTmice) (German)
|
* [Daniel Gultsch](https://github.com/iNPUTmice) (German)
|
||||||
|
@ -71,180 +83,215 @@ These XEPs are - as of now:
|
||||||
* [Anders Sandblad](https://github.com/andersruneson) (Swedish)
|
* [Anders Sandblad](https://github.com/andersruneson) (Swedish)
|
||||||
* [Aizaz AZ](http://www.linkedin.com/in/aizazhaider) (Chinese)
|
* [Aizaz AZ](http://www.linkedin.com/in/aizazhaider) (Chinese)
|
||||||
|
|
||||||
##FAQ
|
## FAQ
|
||||||
###General
|
|
||||||
####How do I install Conversations?
|
### General
|
||||||
|
|
||||||
|
#### How do I install Conversations?
|
||||||
|
|
||||||
Conversations is entirely open source and licensed under GPLv3. So if you are a
|
Conversations is entirely open source and licensed under GPLv3. So if you are a
|
||||||
software developer you can check out the sources from github and use ant to
|
software developer you can check out the sources from GitHub and use ant to
|
||||||
build your apk file.
|
build your apk file.
|
||||||
|
|
||||||
The more convenient way - which not only gives you automatic updates but also
|
The more convenient way — which not only gives you automatic updates but also
|
||||||
supports the further development of Conversations - is to buy the App in the Google
|
supports the further development of Conversations - is to buy the App in the
|
||||||
[Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations).
|
Google [Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations).
|
||||||
####I don't have a Google Account but I would still like to make a contribution
|
|
||||||
I accept donations over PayPal, BitCoin and Flattr. For donations via PayPal you can use the email address donate@siacs.eu or the button below.
|
#### I don't have a Google Account but I would still like to make a contribution
|
||||||
|
|
||||||
|
I accept donations over PayPal, Bitcoin and Flattr. For donations via PayPal you
|
||||||
|
can use the email address `donate@siacs.eu` or the button below.
|
||||||
|
|
||||||
[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CW3SYT3KG5PDL)
|
[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CW3SYT3KG5PDL)
|
||||||
|
|
||||||
**Disclaimer:** I'm not a huge fan of PayPal and their business policies. For larger
|
**Disclaimer:** I'm not a huge fan of PayPal and their business policies. For
|
||||||
contributions please get in touch with me beforehand and we can talk about bank
|
larger contributions please get in touch with me beforehand and we can talk
|
||||||
transfer (SEPA).
|
about bank transfer (SEPA).
|
||||||
|
|
||||||
My Bitcoin Address is: 1NxSU1YxYzJVDpX1rcESAA3NJki7kRgeeu
|
My Bitcoin Address is: `1NxSU1YxYzJVDpX1rcESAA3NJki7kRgeeu`
|
||||||
|
|
||||||
|
|
||||||
[![Flattr this!](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=inputmice&url=https%3A%2F%2Fgithub.com%2Fsiacs%2FConversations)
|
[![Flattr this!](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=inputmice&url=http%3A%2F%2Fconversations.siacs.eu&title=Conversations&tags=github&category=software)
|
||||||
|
|
||||||
####How do I create an account?
|
#### How do I create an account?
|
||||||
XMPP like email for example is a federated protocol which means that there is
|
|
||||||
not one company you can create your 'official xmpp account' with but there are
|
|
||||||
hundreds or even thousands of provider out there. To find one use a web search
|
|
||||||
engine of your choice. Or maybe your university has one. Or you can run your own.
|
|
||||||
Or ask a friend to run one. Once you found one you can use Conversations to
|
|
||||||
create an account. Just select 'register new account on server' within the
|
|
||||||
create account dialog.
|
|
||||||
|
|
||||||
####Conversations doesn't work for me. Where can I get help?
|
XMPP, like email, is a federated protocol which means that there is not one
|
||||||
You can join our conference room on conversations@conference.siacs.eu A lot of
|
company you can create an 'official XMPP account' with. Instead there are
|
||||||
|
hundreds, or even thousands, of provider out there. To find one use a web search
|
||||||
|
engine of your choice. Or maybe your university has one. Or you can run your
|
||||||
|
own. Or ask a friend to run one. Once you've found one, you can use
|
||||||
|
Conversations to create an account. Just select 'register new account on server'
|
||||||
|
within the create account dialog.
|
||||||
|
|
||||||
|
#### Conversations doesn't work for me. Where can I get help?
|
||||||
|
|
||||||
|
You can join our conference room on `conversations@conference.siacs.eu` A lot of
|
||||||
people in there are able to answer basic questions about the usage of
|
people in there are able to answer basic questions about the usage of
|
||||||
Conversations or can provide you with tips on running your own XMPP server. If
|
Conversations or can provide you with tips on running your own XMPP server. If
|
||||||
you found a bug or your app crashes please read the Developer / Report Bugs
|
you found a bug or your app crashes please read the Developer / Report Bugs
|
||||||
section of this document.
|
section of this document.
|
||||||
|
|
||||||
####I need professional support with Conversations or setting up my server
|
#### I need professional support with Conversations or setting up my server
|
||||||
I'm available for hire. Contact me at inputmice@siacs.eu
|
|
||||||
|
|
||||||
####How does the address book integration work?
|
I'm available for hire. Contact me at `inputmice@siacs.eu`.
|
||||||
The address bock integration was designed to protect your privacy. Conversations
|
|
||||||
|
#### How does the address book integration work?
|
||||||
|
|
||||||
|
The address book integration was designed to protect your privacy. Conversations
|
||||||
neither uploads contacts from your address book to your server nor fills your
|
neither uploads contacts from your address book to your server nor fills your
|
||||||
address book with unnecessary contacts from your online roster. If you manually
|
address book with unnecessary contacts from your online roster. If you manually
|
||||||
add a Jabber ID to your phones address book Conversations will use the name and
|
add a Jabber ID to your phones address book Conversations will use the name and
|
||||||
the profile picture of this contact. To make the process of adding Jabber IDs to
|
the profile picture of this contact. To make the process of adding Jabber IDs to
|
||||||
your address book easier you can click on the profile picture in the contact
|
your address book easier you can click on the profile picture in the contact
|
||||||
details within Conversations. This will start an add to address book intent with the jabber ID
|
details within Conversations. This will start an "add to address book" intent
|
||||||
as payload. This doesn’t require Conversations to have write permissions on your
|
with the JID as the payload. This doesn't require Conversations to have write
|
||||||
address book but also doesn’t require you to copy past Jabber ID from one app to
|
permissions on your address book but also doesn't require you to copy/paste a
|
||||||
another.
|
JID from one app to another.
|
||||||
|
|
||||||
####I get 'delivery failed' on my messages
|
#### I get 'delivery failed' on my messages
|
||||||
If you get delivery failed on images it’s probably because the recipient lost
|
|
||||||
network connectivity during recepiton. In that case you can try it again at a
|
If you get delivery failed on images it's probably because the recipient lost
|
||||||
|
network connectivity during reception. In that case you can try it again at a
|
||||||
later time.
|
later time.
|
||||||
|
|
||||||
For text messages the answer to your question is a little bit more complex.
|
For text messages the answer to your question is a little bit more complex.
|
||||||
'delivery failed' on text messages is always something that is being reported by
|
When you see 'delivery failed' on text messages, it is always something that is
|
||||||
the server. The most common reason for this is that the recipient failed to
|
being reported by the server. The most common reason for this is that the
|
||||||
resume a connection. When a client loses connectivity for a short time the client
|
recipient failed to resume a connection. When a client loses connectivity for a
|
||||||
usually has a five minute window to pick up that connection again. When the
|
short time the client usually has a five minute window to pick up that
|
||||||
client fails to do so because the network connectivity is out for longer than
|
connection again. When the client fails to do so because the network
|
||||||
that all messages sent to that client will be returned to the sender resulting
|
connectivity is out for longer than that all messages sent to that client will
|
||||||
in a delivery failed.
|
be returned to the sender resulting in a delivery failed.
|
||||||
|
|
||||||
Other less common reasons are that the message you sent didn’t meet some
|
Other less common reasons are that the message you sent didn't meet some
|
||||||
criterias enforced by the server. (Too large, too many) Another reason could be
|
criteria enforced by the server (too large, too many). Another reason could be
|
||||||
that the recipient is offline and the server doesn’t provide offline storage.
|
that the recipient is offline and the server doesn't provide offline storage.
|
||||||
|
|
||||||
Usually you are able to distinguish between these two groups in the fact that
|
Usually you are able to distinguish between these two groups in the fact that
|
||||||
the first one happens always after some time and the second one happens almost
|
the first one happens always after some time and the second one happens almost
|
||||||
instantly.
|
instantly.
|
||||||
|
|
||||||
####Where can I see the status of my contacts? How can I set a status or priority
|
#### Where can I see the status of my contacts? How can I set a status or priority?
|
||||||
Status are a horrible metric. Setting them manually to a proper value rarely
|
|
||||||
|
Statuses are a horrible metric. Setting them manually to a proper value rarely
|
||||||
works because users are either lazy or just forget about them. Setting them
|
works because users are either lazy or just forget about them. Setting them
|
||||||
automatically does not provide quality results either. Keyboard or mouse
|
automatically does not provide quality results either. Keyboard or mouse
|
||||||
activity as indicator for example fails when the user is just looking at
|
activity as indicator for example fails when the user is just looking at
|
||||||
something (reading an article, watching a movie). Furthermore automatic setting
|
something (reading an article, watching a movie). Furthermore automatic setting
|
||||||
of status always implies an impact on your privacy. (Are you sure you want
|
of status always implies an impact on your privacy (are you sure you want
|
||||||
everybody in your contact list to know that you have been using your computer at
|
everybody in your contact list to know that you have been using your computer at
|
||||||
4am?!)
|
4am‽).
|
||||||
|
|
||||||
In the past status has been used to judge the likelihood of whether or not your
|
In the past status has been used to judge the likelihood of whether or not your
|
||||||
messages are being read. This is no longer necessary. With Chat Markers
|
messages are being read. This is no longer necessary. With Chat Markers
|
||||||
(XEP-0333, supported by Conversations since 0.4) we have the ability to **know**
|
(XEP-0333, supported by Conversations since 0.4) we have the ability to **know**
|
||||||
whether or not your messages are being read.
|
whether or not your messages are being read. Similar things can be said for
|
||||||
Similar things can be said for priorities. In the past priorities have been used
|
priorities. In the past priorities have been used (by servers, not by clients!)
|
||||||
(By servers, not by clients!) to route your messages to one specific client.
|
to route your messages to one specific client. With carbon messages (XEP-0280,
|
||||||
With carbon messages (XEP-0280, supported by Conversations since 0.1) this is no
|
supported by Conversations since 0.1) this is no longer necessary. Using
|
||||||
longer necessary. Using priorities to route OTR messages isn't pratical either
|
priorities to route OTR messages isn't practical either because they are not
|
||||||
because they are not changeable on the fly. Metrics like last active client
|
changeable on the fly. Metrics like last active client (the client which sent
|
||||||
(the client which sent the last message) are much better.
|
the last message) are much better.
|
||||||
|
|
||||||
Unfortunately these modern replacements for legacy XMPP features are not widely
|
Unfortunately these modern replacements for legacy XMPP features are not widely
|
||||||
adopted. However Conversations should be an instant messenger for the future and
|
adopted. However Conversations should be an instant messenger for the future and
|
||||||
instead of making Conversations compatible with the past we should work on
|
instead of making Conversations compatible with the past we should work on
|
||||||
implementing new, improved technologies into other XMPP clients as well.
|
implementing new, improved technologies and getting them into other XMPP clients
|
||||||
|
as well.
|
||||||
|
|
||||||
Making these status and priority optional isn't a solution either because
|
Making these status and priority optional isn't a solution either because
|
||||||
Conversations is trying to get rid of old behaviours and set an example for
|
Conversations is trying to get rid of old behaviours and set an example for
|
||||||
other clients.
|
other clients.
|
||||||
|
|
||||||
####Conversations is missing a certain feature
|
#### Conversations is missing a certain feature
|
||||||
I'm open for new feature suggestions. You can use the issue tracker on github.
|
|
||||||
Please take some time to browse through the issues to see if someone else
|
I'm open for new feature suggestions. You can use the [issue tracker][issues] on
|
||||||
already suggested it. Be assured that I read each and every ticket. If I like it
|
GitHub. Please take some time to browse through the issues to see if someone
|
||||||
I will leave it open until it's implemented. If I don't like it I will close
|
else already suggested it. Be assured that I read each and every ticket. If I
|
||||||
it. (Usually with a short comment). If I don't comment on an feature request
|
like it I will leave it open until it's implemented. If I don't like it I will
|
||||||
that's probably a good sign because this means I agree with you. Commenting with
|
close it (usually with a short comment). If I don't comment on an feature
|
||||||
+1 on either open or closed issues won't change my mind nor will it accelerate the
|
request that's probably a good sign because this means I agree with you.
|
||||||
development.
|
Commenting with +1 on either open or closed issues won't change my mind, nor
|
||||||
|
will it accelerate the development.
|
||||||
|
|
||||||
|
#### You closed my feature request but I want it really really badly
|
||||||
|
|
||||||
####You closed my feature request but I want it really really badly
|
|
||||||
Just write it yourself and send me a pull request. If I like it I will happily
|
Just write it yourself and send me a pull request. If I like it I will happily
|
||||||
merge it if I don't at least you and like minded people get to enjoy it.
|
merge it if I don't at least you and like minded people get to enjoy it.
|
||||||
|
|
||||||
####I need a feature and I need it now!
|
#### I need a feature and I need it now!
|
||||||
I am available for hire. Contact me JID: inputmice@siacs.eu
|
|
||||||
|
|
||||||
###Security
|
I am available for hire. Contact me via XMPP: `inputmice@siacs.eu`
|
||||||
####Why are there two end-to-end encryption methods and which one should I choose?
|
|
||||||
In most cases OTR should be the encryption method of choice. It works out of the box with most contacts as long as they are online.
|
|
||||||
However PGP can be in some cases (carbonated messages to multiple clients) be
|
|
||||||
more flexible.
|
|
||||||
####How do I use openPGP
|
|
||||||
Before you continue reading you should notice that the openPGP support in
|
|
||||||
Conversations is marked as experimental. This is not because it will make the app
|
|
||||||
unstable but because the fundamental concepts of PGP aren't ready for a
|
|
||||||
widespread use. The way PGP works is that you trust Key IDs instead of XMPP- or email addresses. So in theory your contact list should consist of Public-Key-IDs instead of email addresses. But of course no email or xmpp client out there implements these concepts. Plus PGP in the context of instant messaging has a couple of downsides. It is vulnerable to replay attacks, it is rather verbose, and decrypting and encrypting takes longer than OTR. It is however asynchronous and works well with carbonated messages.
|
|
||||||
|
|
||||||
To use openpgp you have to install the opensource app OpenKeychain (www.openkeychain.org) and then long press on the account in manage accounts and choose renew PGP announcement from the contextual menu.
|
### Security
|
||||||
####How does the encryption for conferences work?
|
|
||||||
For conferences the only supported encryption method is OpenPGP. (OTR does not
|
#### Why are there two end-to-end encryption methods and which one should I choose?
|
||||||
work with multiple participants.) Every participant has to announce their
|
|
||||||
OpenPGP key. (See answer above). If you would like to send encrypted messages to
|
In most cases OTR should be the encryption method of choice. It works out of the
|
||||||
|
box with most contacts as long as they are online. However PGP can, in some
|
||||||
|
cases, (message carbons to multiple clients) be more flexible.
|
||||||
|
|
||||||
|
#### How do I use OpenPGP
|
||||||
|
|
||||||
|
Before you continue reading you should note that the OpenPGP support in
|
||||||
|
Conversations is experimental. This is not because it will make the app unstable
|
||||||
|
but because the fundamental concepts of PGP aren't ready for widespread use.
|
||||||
|
The way PGP works is that you trust Key IDs instead of JID's or email addresses.
|
||||||
|
So in theory your contact list should consist of Public-Key-IDs instead of
|
||||||
|
JID's. But of course no email or XMPP client out there implements these
|
||||||
|
concepts. Plus PGP in the context of instant messaging has a couple of
|
||||||
|
downsides: It is vulnerable to replay attacks, it is rather verbose, and
|
||||||
|
decrypting and encrypting takes longer than OTR. It is however asynchronous and
|
||||||
|
works well with message carbons.
|
||||||
|
|
||||||
|
To use OpenPGP you have to install the open source app
|
||||||
|
[OpenKeychain](www.openkeychain.org) and then long press on the account in
|
||||||
|
manage accounts and choose renew PGP announcement from the contextual menu.
|
||||||
|
|
||||||
|
#### How does the encryption for conferences work?
|
||||||
|
|
||||||
|
For conferences the only supported encryption method is OpenPGP (OTR does not
|
||||||
|
work with multiple participants). Every participant has to announce their
|
||||||
|
OpenPGP key (see answer above). If you would like to send encrypted messages to
|
||||||
a conference you have to make sure that you have every participant's public key
|
a conference you have to make sure that you have every participant's public key
|
||||||
in your OpenKeychain. Right now there is no check in Conversations to ensure
|
in your OpenKeychain. Right now there is no check in Conversations to ensure
|
||||||
that. You have to take care of that yourself. Go to the conference details and
|
that. You have to take care of that yourself. Go to the conference details and
|
||||||
touch every key id (The hexadecimal number below a contact). This will send you
|
touch every key id (The hexadecimal number below a contact). This will send you
|
||||||
to OpenKeychain which will assist you on adding the key.
|
to OpenKeychain which will assist you on adding the key. This works best in
|
||||||
This works best in very small conferences with contacts you are already using
|
very small conferences with contacts you are already using OpenPGP with. This
|
||||||
OpenPGP with. This feature is regarded experimental. Conversations is the only
|
feature is regarded experimental. Conversations is the only client that uses
|
||||||
client that uses XEP-0027 with conferences. (The XEP neither specifically allows
|
XEP-0027 with conferences. (The XEP neither specifically allows nor disallows
|
||||||
nor disallows this.)
|
this.)
|
||||||
###Development
|
|
||||||
####How do I build Conversations
|
### Development
|
||||||
|
|
||||||
|
#### How do I build Conversations
|
||||||
|
|
||||||
Make sure to have ANDROID_HOME point to your Android SDK
|
Make sure to have ANDROID_HOME point to your Android SDK
|
||||||
```
|
|
||||||
git clone https://github.com/siacs/Conversations.git
|
git clone https://github.com/siacs/Conversations.git
|
||||||
cd Conversations
|
cd Conversations
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
ant clean
|
ant clean
|
||||||
ant debug
|
ant debug
|
||||||
```
|
|
||||||
####How do I debug Conversations
|
#### How do I debug Conversations
|
||||||
|
|
||||||
If something goes wrong Conversations usually exposes very little information in
|
If something goes wrong Conversations usually exposes very little information in
|
||||||
the UI. (Other than the fact that something didn't work)
|
the UI (other than the fact that something didn't work). However with adb
|
||||||
However with adb (android debug bridge) you squeeze some more information out of
|
(android debug bridge) you squeeze some more information out of Conversations.
|
||||||
Conversations. These information are especially useful if you are experiencing
|
These information are especially useful if you are experiencing trouble with
|
||||||
troubles with your connection or with file transfer.
|
your connection or with file transfer.
|
||||||
````
|
|
||||||
adb -d logcat -v time -s conversations
|
adb -d logcat -v time -s conversations
|
||||||
````
|
|
||||||
####I found a bug
|
#### I found a bug
|
||||||
Please report it to our issue tracker. If your app crashes please provide a
|
|
||||||
stack trace. If you are experiencing missbehaviour please provide detailed
|
Please report it to our [issue tracker][issues]. If your app crashes please
|
||||||
steps to reproduce.
|
provide a stack trace. If you are experiencing misbehaviour please provide
|
||||||
Always mention whether you are running the latest Play Store version or the
|
detailed steps to reproduce. Always mention whether you are running the latest
|
||||||
current HEAD.
|
Play Store version or the current HEAD. If you are having problems connecting to
|
||||||
If you are having problems connecting to your XMPP server your file transfer
|
your XMPP server your file transfer doesn’t work as expected please always
|
||||||
doesn’t work as expected please always include a logcat debug output with your
|
include a logcat debug output with your issue (see above).
|
||||||
issue. (See above)
|
|
||||||
|
[issues]: https://github.com/siacs/Conversations/issues
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="128"
|
|
||||||
height="128"
|
|
||||||
id="svg4066"
|
|
||||||
version="1.1"
|
|
||||||
inkscape:version="0.48.5 r10040"
|
|
||||||
sodipodi:docname="ic_action_copy.svg">
|
|
||||||
<defs
|
|
||||||
id="defs4068" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#000000"
|
|
||||||
borderopacity="0.54117647"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="2.67"
|
|
||||||
inkscape:cx="51.750573"
|
|
||||||
inkscape:cy="57.547291"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
borderlayer="false"
|
|
||||||
inkscape:window-width="1035"
|
|
||||||
inkscape:window-height="853"
|
|
||||||
inkscape:window-x="369"
|
|
||||||
inkscape:window-y="3"
|
|
||||||
inkscape:window-maximized="0" />
|
|
||||||
<metadata
|
|
||||||
id="metadata4071">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-924.36218)">
|
|
||||||
<rect
|
|
||||||
ry="0"
|
|
||||||
height="91.708199"
|
|
||||||
width="71.625328"
|
|
||||||
stroke-miterlimit="4"
|
|
||||||
y="952.36743"
|
|
||||||
x="42.730034"
|
|
||||||
id="rect10"
|
|
||||||
style="fill:none;stroke:#000000;stroke-width:8.6679945;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.54117647;stroke-dasharray:none"
|
|
||||||
inkscape:transform-center-x="-21.391573"
|
|
||||||
inkscape:transform-center-y="28.294015" />
|
|
||||||
<path
|
|
||||||
style="fill:#000000;fill-opacity:0.5411765;fill-rule:evenodd;stroke:#000000;stroke-width:0.41999999999999998;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117649999999995;stroke-miterlimit:4;stroke-dasharray:none"
|
|
||||||
d="m 20.552281,933.36985 0,0.0209 -0.128276,0 -0.399078,99.83215 0.213792,0 0,0.1463 13.212333,-0.021 0.05701,-8.1392 -4.076297,0.011 0.327814,-84.87039 58.436429,0 0.0285,3.427 11.10293,0 -0.0855,-9.25707 -0.057,0 0,-1.1493 -78.63263,0 z"
|
|
||||||
id="rect12-6"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
<rect
|
|
||||||
height="4.7259107"
|
|
||||||
width="37.242958"
|
|
||||||
y="967.49921"
|
|
||||||
x="50.137043"
|
|
||||||
id="rect12"
|
|
||||||
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.23799089px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647" />
|
|
||||||
<rect
|
|
||||||
style="fill:#000000;fill-opacity:0.54117647000000002;fill-rule:evenodd;stroke:#000000;stroke-width:0.274;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647000000002;stroke-miterlimit:4;stroke-dasharray:none"
|
|
||||||
id="rect4272"
|
|
||||||
x="50.137043"
|
|
||||||
y="982.49921"
|
|
||||||
width="49.452484"
|
|
||||||
height="4.7259107" />
|
|
||||||
<rect
|
|
||||||
height="4.7259107"
|
|
||||||
width="43.542446"
|
|
||||||
y="997.49921"
|
|
||||||
x="50.137043"
|
|
||||||
id="rect4274"
|
|
||||||
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.2573325px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647" />
|
|
||||||
<rect
|
|
||||||
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.25050664px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647"
|
|
||||||
id="rect4276"
|
|
||||||
x="50.137043"
|
|
||||||
y="1012.4992"
|
|
||||||
width="41.263123"
|
|
||||||
height="4.7259107" />
|
|
||||||
<rect
|
|
||||||
height="4.7259107"
|
|
||||||
width="49.397911"
|
|
||||||
y="1027.4993"
|
|
||||||
x="50.137043"
|
|
||||||
id="rect4278"
|
|
||||||
style="fill:#000000;fill-opacity:0.54117647;fill-rule:evenodd;stroke:#000000;stroke-width:0.27408957px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.54117647" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 52 KiB |
|
@ -1,6 +1,16 @@
|
||||||
#!/bin/env ruby
|
#!/bin/env ruby
|
||||||
resolutions={'mdpi'=> 1, 'hdpi' => 1.5, 'xhdpi' => 2, 'xxhdpi' => 3}
|
resolutions={
|
||||||
images = { 'conversations.svg' => ['ic_launcher', 48], 'conversations_baloon.svg' => ['ic_activity', 32], 'conversations_mono.svg' => ['ic_notification', 24], 'ic_received_indicator.svg' => ['ic_received_indicator', 12], 'ic_action_copy.svg' => ['ic_action_copy', 32] }
|
'mdpi'=> 1,
|
||||||
|
'hdpi' => 1.5,
|
||||||
|
'xhdpi' => 2,
|
||||||
|
'xxhdpi' => 3,
|
||||||
|
}
|
||||||
|
images = {
|
||||||
|
'conversations.svg' => ['ic_launcher', 48],
|
||||||
|
'conversations_baloon.svg' => ['ic_activity', 32],
|
||||||
|
'conversations_mono.svg' => ['ic_notification', 24],
|
||||||
|
'ic_received_indicator.svg' => ['ic_received_indicator', 12],
|
||||||
|
}
|
||||||
images.each do |source, result|
|
images.each do |source, result|
|
||||||
resolutions.each do |name, factor|
|
resolutions.each do |name, factor|
|
||||||
size = factor * result[1]
|
size = factor * result[1]
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 452f70208f0dd5f9e56376944e96f5c10704245e
|
Subproject commit fad835037adc1bd313bb56b694426fca4eb67346
|
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 381 B |
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 288 B |
Before Width: | Height: | Size: 763 B After Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 470 B |
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<solid android:color="@color/primarybackground" />
|
||||||
|
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
|
||||||
|
<stroke
|
||||||
|
android:width="0.5dp"
|
||||||
|
android:color="@color/divider" >
|
||||||
|
</stroke>
|
||||||
|
|
||||||
|
<padding
|
||||||
|
android:bottom="0dp"
|
||||||
|
android:left="0dp"
|
||||||
|
android:right="0dp"
|
||||||
|
android:top="0dp" />
|
||||||
|
|
||||||
|
</shape>
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle" >
|
|
||||||
|
|
||||||
<size
|
|
||||||
android:height="1.5dp"
|
|
||||||
android:width="2000dp" />
|
|
||||||
|
|
||||||
<solid android:color="@color/divider" />
|
|
||||||
|
|
||||||
</shape>
|
|
|
@ -1,126 +1,112 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:background="@color/primarybackground" >
|
android:background="@color/secondarybackground" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/sectionHeader"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/action_contact_details"
|
|
||||||
android:textColor="@color/primarytext" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="88dp"
|
android:layout_margin="8dp"
|
||||||
android:padding="8dp" >
|
android:background="@drawable/infocard_border"
|
||||||
|
android:padding="16dp" >
|
||||||
|
|
||||||
<QuickContactBadge
|
<QuickContactBadge
|
||||||
android:id="@+id/details_contact_badge"
|
android:id="@+id/details_contact_badge"
|
||||||
android:layout_width="72dp"
|
android:layout_width="72dp"
|
||||||
android:layout_height="72dp"
|
android:layout_height="72dp"
|
||||||
android:layout_centerVertical="true"
|
android:layout_alignParentTop="true"
|
||||||
android:scaleType="centerCrop" />
|
android:scaleType="centerCrop" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/details_jidbox"
|
android:id="@+id/details_jidbox"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_marginLeft="16dp"
|
||||||
android:layout_toRightOf="@+id/details_contact_badge"
|
android:layout_toRightOf="@+id/details_contact_badge"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical" >
|
||||||
android:paddingLeft="8dp" >
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/details_contactjid"
|
android:id="@+id/details_contactjid"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="8dp"
|
android:text="@string/account_settings_example_jabber_id"
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="@color/primarytext"
|
|
||||||
android:textSize="?attr/TextSizeBody" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/details_contactstatus"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingLeft="16dp"
|
|
||||||
android:textColor="@color/primarytext"
|
android:textColor="@color/primarytext"
|
||||||
android:textSize="?attr/TextSizeHeadline"
|
android:textSize="?attr/TextSizeHeadline"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/details_lastseen"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="8dp"
|
android:orientation="horizontal" >
|
||||||
android:singleLine="true"
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/details_contactstatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text=" · "
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/details_lastseen"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/details_send_presence"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/send_presence_updates"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/details_receive_presence"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/receive_presence_updates"
|
||||||
android:textColor="@color/primarytext"
|
android:textColor="@color/primarytext"
|
||||||
android:textSize="?attr/TextSizeBody" />
|
android:textSize="?attr/TextSizeBody" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/details_account"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/details_jidbox"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="@string/using_account"
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeInfo" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/sectionHeader"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/your_account"
|
|
||||||
android:textColor="@color/primarytext" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/details_account"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:textColor="@color/primarytext"
|
|
||||||
android:textSize="?attr/TextSizeBody" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/sectionHeader"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/subscriptions"
|
|
||||||
android:textColor="@color/primarytext" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/details_send_presence"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/send_presence_updates"
|
|
||||||
android:textColor="@color/primarytext"
|
|
||||||
android:textSize="?attr/TextSizeBody" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/details_receive_presence"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/receive_presence_updates"
|
|
||||||
android:textColor="@color/primarytext"
|
|
||||||
android:textSize="?attr/TextSizeBody" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/sectionHeader"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:text="@string/keys" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/details_contact_keys"
|
android:id="@+id/details_contact_keys"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:background="@drawable/infocard_border"
|
||||||
android:divider="?android:dividerHorizontal"
|
android:divider="?android:dividerHorizontal"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp"
|
||||||
android:showDividers="middle" >
|
android:showDividers="middle" >
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primarybackground" >
|
android:background="@color/secondarybackground" >
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
|
@ -19,8 +19,10 @@
|
||||||
android:id="@+id/editor"
|
android:id="@+id/editor"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:background="@drawable/infocard_border"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="8dp" >
|
android:padding="16dp" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -34,7 +36,10 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/account_settings_example_jabber_id"
|
android:hint="@string/account_settings_example_jabber_id"
|
||||||
android:inputType="textEmailAddress" />
|
android:inputType="textEmailAddress"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textColorHint="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -49,7 +54,10 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/password"
|
android:hint="@string/password"
|
||||||
android:inputType="textPassword" />
|
android:inputType="textPassword"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textColorHint="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/account_register_new"
|
android:id="@+id/account_register_new"
|
||||||
|
@ -76,31 +84,25 @@
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:hint="@string/confirm_password"
|
android:hint="@string/confirm_password"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:visibility="gone" />
|
android:visibility="gone"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textColorHint="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/stats"
|
android:id="@+id/stats"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_margin="8dp"
|
||||||
|
android:background="@drawable/infocard_border"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:visibility="gone" >
|
android:visibility="gone" >
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:text="@string/additional_information"
|
|
||||||
android:textColor="@color/secondarytext"
|
|
||||||
android:textSize="?attr/TextSizeHeadline"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TableLayout
|
<TableLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:stretchColumns="1" >
|
android:stretchColumns="1" >
|
||||||
|
|
||||||
<TableRow
|
<TableRow
|
||||||
|
@ -110,13 +112,17 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_info_session_established" />
|
android:text="@string/server_info_session_established"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/session_est"
|
android:id="@+id/session_est"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="right" />
|
android:layout_gravity="right"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow
|
<TableRow
|
||||||
|
@ -127,13 +133,16 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_info_pep"
|
android:text="@string/server_info_pep"
|
||||||
android:textColor="@color/primarytext" />
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/server_info_pep"
|
android:id="@+id/server_info_pep"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="right" />
|
android:layout_gravity="right"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow
|
<TableRow
|
||||||
|
@ -143,13 +152,17 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_info_stream_management" />
|
android:text="@string/server_info_stream_management"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/server_info_sm"
|
android:id="@+id/server_info_sm"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="right" />
|
android:layout_gravity="right"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow
|
<TableRow
|
||||||
|
@ -159,59 +172,64 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_info_carbon_messages" />
|
android:text="@string/server_info_carbon_messages"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/server_info_carbons"
|
android:id="@+id/server_info_carbons"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="right" />
|
android:layout_gravity="right"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/otr_fingerprint_headline"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:text="@string/otr_fingerprint"
|
|
||||||
android:textColor="@color/secondarytext"
|
|
||||||
android:textSize="?attr/TextSizeHeadline"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="8dp">
|
android:id="@+id/otr_fingerprint_box"
|
||||||
|
android:layout_marginTop="32dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_toLeftOf="@+id/action_copy_to_clipboard"
|
android:layout_toLeftOf="@+id/action_copy_to_clipboard"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical">
|
||||||
android:layout_centerVertical="true">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/otr_fingerprint"
|
android:id="@+id/otr_fingerprint"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
android:textSize="?attr/TextSizeBody"
|
android:textSize="?attr/TextSizeBody"
|
||||||
android:typeface="monospace" />
|
android:typeface="monospace" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeInfo"
|
||||||
|
android:text="@string/otr_fingerprint"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/action_copy_to_clipboard"
|
android:id="@+id/action_copy_to_clipboard"
|
||||||
android:layout_width="32dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="32dp"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:selectableItemBackground"
|
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:padding="4dp"
|
android:background="?android:selectableItemBackground"
|
||||||
android:scaleType="fitXY"
|
android:padding="8dp"
|
||||||
android:src="@drawable/ic_action_copy"
|
android:src="@drawable/ic_action_copy"
|
||||||
android:visibility="invisible" />
|
android:visibility="visible" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -251,4 +269,4 @@
|
||||||
android:textColor="@color/secondarytext" />
|
android:textColor="@color/secondarytext" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
|
@ -1,46 +1,35 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:background="@color/primarybackground" >
|
android:background="@color/secondarybackground" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
style="@style/sectionHeader"
|
android:layout_width="fill_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_margin="8dp"
|
||||||
android:padding="8dp"
|
android:background="@drawable/infocard_border"
|
||||||
android:text="@string/muc_details_conference" />
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/muc_jabberid"
|
android:id="@+id/muc_jabberid"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:text="@string/account_settings_example_jabber_id"
|
android:text="@string/account_settings_example_jabber_id"
|
||||||
android:textColor="@color/primarytext"
|
android:textColor="@color/primarytext"
|
||||||
android:textSize="?attr/TextSizeBody" />
|
android:textSize="?attr/TextSizeHeadline"
|
||||||
|
android:textStyle="bold"
|
||||||
<TextView
|
android:layout_marginBottom="16dp"/>
|
||||||
style="@style/sectionHeader"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:paddingRight="8dp"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:text="@string/you" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:background="?android:attr/activatedBackgroundIndicator"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:paddingBottom="8dp" >
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/your_photo"
|
android:id="@+id/your_photo"
|
||||||
|
@ -85,22 +74,26 @@
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:src="@drawable/ic_action_edit_dark" />
|
android:src="@drawable/ic_action_edit_dark" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
<TextView
|
||||||
<LinearLayout
|
android:id="@+id/details_account"
|
||||||
android:id="@+id/muc_more_details"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:orientation="vertical" >
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/muc_participants_header"
|
|
||||||
style="@style/sectionHeader"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="8dp"
|
android:layout_gravity="right"
|
||||||
android:paddingRight="8dp"
|
android:layout_marginTop="32dp"
|
||||||
android:paddingTop="8dp"
|
android:text="@string/using_account"
|
||||||
android:text="@string/muc_details_other_members" />
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeInfo" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/muc_more_details"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:background="@drawable/infocard_border"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp" >
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/muc_members"
|
android:id="@+id/muc_members"
|
||||||
|
@ -111,7 +104,6 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:showDividers="middle" >
|
android:showDividers="middle" >
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/invite"
|
android:id="@+id/invite"
|
||||||
|
@ -123,4 +115,5 @@
|
||||||
android:text="@string/invite_contact" />
|
android:text="@string/invite_contact" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
|
@ -16,14 +16,15 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/primarytext"
|
android:textColor="@color/primarytext"
|
||||||
android:textSize="?attr/TextSizeHeadline"
|
android:textSize="?attr/TextSizeBody"
|
||||||
android:typeface="monospace" />
|
android:typeface="monospace" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/key_type"
|
android:id="@+id/key_type"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/secondarytext" />
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeInfo"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_toRightOf="@+id/message_photo"
|
android:layout_toRightOf="@+id/message_photo"
|
||||||
android:background="@drawable/message_border"
|
android:background="@drawable/message_border"
|
||||||
android:minHeight="48dp" >
|
android:minHeight="48dp"
|
||||||
|
android:longClickable="true">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -43,7 +44,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:autoLink="web"
|
android:autoLink="web"
|
||||||
android:textColor="@color/primarytext"
|
android:textColor="@color/primarytext"
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="?attr/TextSizeBody" />
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_toLeftOf="@+id/message_photo"
|
android:layout_toLeftOf="@+id/message_photo"
|
||||||
android:background="@drawable/message_border"
|
android:background="@drawable/message_border"
|
||||||
android:minHeight="48dp" >
|
android:minHeight="48dp"
|
||||||
|
android:longClickable="true">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -43,8 +44,15 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:autoLink="web"
|
android:autoLink="web"
|
||||||
android:textColor="@color/primarytext"
|
android:textColor="@color/primarytext"
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="?attr/TextSizeBody" />
|
android:textSize="?attr/TextSizeBody" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/download_button"
|
||||||
|
style="?android:attr/buttonStyleSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/download_image"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/copy_text"
|
||||||
|
android:title="@string/copy_text"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/share_image"
|
||||||
|
android:title="@string/share_image"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/copy_url"
|
||||||
|
android:title="@string/copy_original_url"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/send_again"
|
||||||
|
android:title="@string/send_again"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/download_image"
|
||||||
|
android:title="@string/download_image"/>
|
||||||
|
|
||||||
|
</menu>
|
|
@ -22,7 +22,7 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="mute_options_descriptions">
|
<string-array name="mute_options_descriptions">
|
||||||
<item>30 Minuten</item>
|
<item>30 Minuten</item>
|
||||||
<item>eine Stunde</item>
|
<item>1 Stunde</item>
|
||||||
<item>2 Stunden</item>
|
<item>2 Stunden</item>
|
||||||
<item>8 Stunden</item>
|
<item>8 Stunden</item>
|
||||||
<item>bis auf Widerruf</item>
|
<item>bis auf Widerruf</item>
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
<string name="send_now">Jetzt abschicken</string>
|
<string name="send_now">Jetzt abschicken</string>
|
||||||
<string name="send_never">Nie mehr nachfragen</string>
|
<string name="send_never">Nie mehr nachfragen</string>
|
||||||
<string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Konto</string>
|
<string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Konto</string>
|
||||||
<string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Konto</string>
|
<string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Konten</string>
|
||||||
<string name="touch_to_fix">Drücke hier, um das Konto zu verwalten</string>
|
<string name="touch_to_fix">Drücke hier, um das Konto zu verwalten</string>
|
||||||
<string name="attach_file">Datei anfügen</string>
|
<string name="attach_file">Datei anfügen</string>
|
||||||
<string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string>
|
<string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string>
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
<string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string>
|
<string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string>
|
||||||
<string name="delete_messages">Nachrichten löschen</string>
|
<string name="delete_messages">Nachrichten löschen</string>
|
||||||
<string name="also_end_conversation">Diese Unterhaltung danach beenden</string>
|
<string name="also_end_conversation">Diese Unterhaltung danach beenden</string>
|
||||||
<string name="choose_presence">Choose presence to contact</string>
|
<string name="choose_presence">Ressource des Kontakts auswählen</string>
|
||||||
<string name="send_plain_text_message">Unverschlüsselt schreiben</string>
|
<string name="send_plain_text_message">Unverschlüsselt schreiben</string>
|
||||||
<string name="send_otr_message">OTR-verschlüsselt schreiben</string>
|
<string name="send_otr_message">OTR-verschlüsselt schreiben</string>
|
||||||
<string name="send_pgp_message">OpenPGP-verschlüsselt schreiben</string>
|
<string name="send_pgp_message">OpenPGP-verschlüsselt schreiben</string>
|
||||||
|
@ -81,9 +81,9 @@
|
||||||
<string name="offering">angeboten…</string>
|
<string name="offering">angeboten…</string>
|
||||||
<string name="waiting">warten…</string>
|
<string name="waiting">warten…</string>
|
||||||
<string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string>
|
<string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string>
|
||||||
<string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge bitte OpenPGP einrichten.</small></string>
|
<string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge OpenPGP einrichten.</small></string>
|
||||||
<string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string>
|
<string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string>
|
||||||
<string name="contacts_have_no_pgp_keys">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge bitte OpenPGP einrichten.</small></string>
|
<string name="contacts_have_no_pgp_keys">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil deine Kontakte ihre Schlüssel nicht preisgeben.\n\n<small>Bitte sag deinen Kontakten, sie mögen OpenPGP einrichten.</small></string>
|
||||||
<string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier, um sie anzuzeigen und zu entschlüsseln.</i></string>
|
<string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier, um sie anzuzeigen und zu entschlüsseln.</i></string>
|
||||||
<string name="encrypted_image_received"><i>Verschlüsseltes Bild erhalten. Drücke hier, um es anzuzeigen und zu entschlüsseln.</i></string>
|
<string name="encrypted_image_received"><i>Verschlüsseltes Bild erhalten. Drücke hier, um es anzuzeigen und zu entschlüsseln.</i></string>
|
||||||
<string name="image_file"><i>Bild erhalten. Drücke hier, um es anzuzeigen.</i></string>
|
<string name="image_file"><i>Bild erhalten. Drücke hier, um es anzuzeigen.</i></string>
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
<string name="pref_sound">Klingelton</string>
|
<string name="pref_sound">Klingelton</string>
|
||||||
<string name="pref_sound_summary">Spiele Klingelton, wenn eine neue Nachricht ankommt</string>
|
<string name="pref_sound_summary">Spiele Klingelton, wenn eine neue Nachricht ankommt</string>
|
||||||
<string name="pref_conference_notifications">Konferenz-Benachrichtigungen</string>
|
<string name="pref_conference_notifications">Konferenz-Benachrichtigungen</string>
|
||||||
<string name="pref_conference_notifications_summary">Benachrichtige mich bei jeder Konferenznachricht und nicht nur, wenn ich angesprochen werde.</string>
|
<string name="pref_conference_notifications_summary">Benachrichtige mich bei jeder Konferenznachricht und nicht nur, wenn ich angesprochen werde</string>
|
||||||
<string name="pref_notification_grace_period">Gnadenfrist</string>
|
<string name="pref_notification_grace_period">Gnadenfrist</string>
|
||||||
<string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach Erhalt einer Nachricht, die von einem anderen deiner Clients kommt.</string>
|
<string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach Erhalt einer Nachricht, die von einem anderen deiner Clients kommt.</string>
|
||||||
<string name="pref_advanced_options">Erweiterte Optionen</string>
|
<string name="pref_advanced_options">Erweiterte Optionen</string>
|
||||||
|
@ -233,7 +233,7 @@
|
||||||
<string name="skip">Überspringen</string>
|
<string name="skip">Überspringen</string>
|
||||||
<string name="pref_ui_options">Benutzeroberfläche</string>
|
<string name="pref_ui_options">Benutzeroberfläche</string>
|
||||||
<string name="pref_use_indicate_received">Anfrage für Nachrichten Empfang</string>
|
<string name="pref_use_indicate_received">Anfrage für Nachrichten Empfang</string>
|
||||||
<string name="pref_use_indicate_received_summary">Empfangene Nachrichten werden mit einem grünen Häckchen markiert. Bitte beachte das dies nicht unbedingt in allen Fällen funktioniert.</string>
|
<string name="pref_use_indicate_received_summary">Empfangene Nachrichten werden mit einem grünen Häckchen markiert. Bitte beachte, dass dies nicht in allen Fällen funktioniert.</string>
|
||||||
<string name="disable_notifications">Benachrichtigungen deaktivieren</string>
|
<string name="disable_notifications">Benachrichtigungen deaktivieren</string>
|
||||||
<string name="disable_notifications_for_this_conversation">Benachrichtigungen für diese Unterhaltung deaktivieren</string>
|
<string name="disable_notifications_for_this_conversation">Benachrichtigungen für diese Unterhaltung deaktivieren</string>
|
||||||
<string name="notifications_disabled">Benachrichtigungen sind deaktiviert</string>
|
<string name="notifications_disabled">Benachrichtigungen sind deaktiviert</string>
|
||||||
|
@ -252,9 +252,11 @@
|
||||||
<string name="pref_force_encryption_summary">Nachrichten immer verschlüsseln (außer für Konferenzen)</string>
|
<string name="pref_force_encryption_summary">Nachrichten immer verschlüsseln (außer für Konferenzen)</string>
|
||||||
<string name="pref_dont_save_encrypted">Verschlüsselte Nachrichten nicht speichern</string>
|
<string name="pref_dont_save_encrypted">Verschlüsselte Nachrichten nicht speichern</string>
|
||||||
<string name="pref_dont_save_encrypted_summary">Achtung: Kann zu Nachrichtenverlust führen</string>
|
<string name="pref_dont_save_encrypted_summary">Achtung: Kann zu Nachrichtenverlust führen</string>
|
||||||
|
<string name="pref_enable_legacy_ssl">Alte SSL-Version aktivieren</string>
|
||||||
|
<string name="pref_enable_legacy_ssl_summary">Aktiviert SSLv3-Unterstützung für alte Server. Achtung: SSLv3 ist unsicher.</string>
|
||||||
<string name="pref_expert_options">Einstellungen für Experten</string>
|
<string name="pref_expert_options">Einstellungen für Experten</string>
|
||||||
<string name="pref_expert_options_summary">Hier bitte vorsichtig sein</string>
|
<string name="pref_expert_options_summary">Hier bitte vorsichtig sein</string>
|
||||||
<string name="pref_use_larger_font">Schriftgröße erhöhen</string>
|
<string name="pref_use_larger_font">Schrift vergrößern</string>
|
||||||
<string name="pref_use_larger_font_summary">Überall in der App eine größere Schrift verwenden</string>
|
<string name="pref_use_larger_font_summary">Überall in der App eine größere Schrift verwenden</string>
|
||||||
<string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string>
|
<string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string>
|
||||||
<string name="pref_use_send_button_to_indicate_status_summary">Absende-Knopf einfärben, um den Online-Status des Kontakts zu signalisieren</string>
|
<string name="pref_use_send_button_to_indicate_status_summary">Absende-Knopf einfärben, um den Online-Status des Kontakts zu signalisieren</string>
|
||||||
|
@ -265,5 +267,19 @@
|
||||||
<string name="conference_banned">Du wurdest aus dem Konferenzraum verbannt</string>
|
<string name="conference_banned">Du wurdest aus dem Konferenzraum verbannt</string>
|
||||||
<string name="conference_members_only">Der Konferenzraum ist nur für Mitglieder</string>
|
<string name="conference_members_only">Der Konferenzraum ist nur für Mitglieder</string>
|
||||||
<string name="conference_kicked">Du wurdest aus dem Konferenzraum geworfen</string>
|
<string name="conference_kicked">Du wurdest aus dem Konferenzraum geworfen</string>
|
||||||
|
<string name="using_account">Verwende Konto %s</string>
|
||||||
|
<string name="checking_image">Prüfe Bild auf HTTP-Host</string>
|
||||||
|
<string name="image_file_deleted">Bilddatei wurde gelöscht</string>
|
||||||
|
<string name="not_connected_try_again">Nicht verbunden, bitte später versuchen</string>
|
||||||
|
<string name="check_image_filesize">Bildgröße prüfen</string>
|
||||||
|
<string name="message_options">Nachrichtenoptionen</string>
|
||||||
|
<string name="copy_text">Text kopieren</string>
|
||||||
|
<string name="share_image">Bild teilen</string>
|
||||||
|
<string name="copy_original_url">Original-URL kopieren</string>
|
||||||
|
<string name="send_again">Erneut senden</string>
|
||||||
|
<string name="image_url">Bild-URL</string>
|
||||||
|
<string name="message_text">Nachrichtentext</string>
|
||||||
|
<string name="url_copied_to_clipboard">URL in Zwischenablage kopiert</string>
|
||||||
|
<string name="message_copied_to_clipboard">Nachricht in Zwischenablage kopiert</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -250,6 +250,8 @@
|
||||||
<string name="pref_force_encryption_summary">Siempre enviar mensajes encriptados (excepto para conferencias)</string>
|
<string name="pref_force_encryption_summary">Siempre enviar mensajes encriptados (excepto para conferencias)</string>
|
||||||
<string name="pref_dont_save_encrypted">No guardar mensajes encriptados</string>
|
<string name="pref_dont_save_encrypted">No guardar mensajes encriptados</string>
|
||||||
<string name="pref_dont_save_encrypted_summary">Aviso: Esto podría llevar a pérdida de mensajes</string>
|
<string name="pref_dont_save_encrypted_summary">Aviso: Esto podría llevar a pérdida de mensajes</string>
|
||||||
|
<string name="pref_enable_legacy_ssl">Habilitar SSL heredado</string>
|
||||||
|
<string name="pref_enable_legacy_ssl_summary">Habilita soporte SSLv3 para servidores heredados. Advertencia: SSLv3 se considera no seguro.</string>
|
||||||
<string name="pref_expert_options">Ajustes avanzados</string>
|
<string name="pref_expert_options">Ajustes avanzados</string>
|
||||||
<string name="pref_expert_options_summary">Por favor, cuidado con estas opciones</string>
|
<string name="pref_expert_options_summary">Por favor, cuidado con estas opciones</string>
|
||||||
<string name="pref_use_larger_font">Incrementar tamaño de fuente</string>
|
<string name="pref_use_larger_font">Incrementar tamaño de fuente</string>
|
||||||
|
@ -265,5 +267,19 @@
|
||||||
<string name="conference_banned">Tu entrada a esta conferencia ha sido prohibida</string>
|
<string name="conference_banned">Tu entrada a esta conferencia ha sido prohibida</string>
|
||||||
<string name="conference_members_only">Esta conferencia es solo para miembros</string>
|
<string name="conference_members_only">Esta conferencia es solo para miembros</string>
|
||||||
<string name="conference_kicked">Has sido expulsado de esta conferencia</string>
|
<string name="conference_kicked">Has sido expulsado de esta conferencia</string>
|
||||||
|
<string name="using_account">Usando cuenta %s</string>
|
||||||
|
<string name="checking_image">Comprobando imagen en servidor HTTP</string>
|
||||||
|
<string name="image_file_deleted">El archivo de imagen ha sido eliminado</string>
|
||||||
|
<string name="not_connected_try_again">No estás concectado. Inténtalo más tarde</string>
|
||||||
|
<string name="check_image_filesize">Comprobar el tamaño del archivo de imagen</string>
|
||||||
|
<string name="message_options">Opciones de mensaje</string>
|
||||||
|
<string name="copy_text">Copiar texto</string>
|
||||||
|
<string name="share_image">Compartir imagen</string>
|
||||||
|
<string name="copy_original_url">Copiar URL original</string>
|
||||||
|
<string name="send_again">Volver a enviar</string>
|
||||||
|
<string name="image_url">URL Imagen</string>
|
||||||
|
<string name="message_text">Mensaje de texto</string>
|
||||||
|
<string name="url_copied_to_clipboard">URL copiada al portapapeles</string>
|
||||||
|
<string name="message_copied_to_clipboard">Mensaje copiado al portapapeles</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -205,7 +205,7 @@
|
||||||
<string name="conference_address">Konferentziaren helbidea</string>
|
<string name="conference_address">Konferentziaren helbidea</string>
|
||||||
<string name="conference_address_example">gela@conference.example.com</string>
|
<string name="conference_address_example">gela@conference.example.com</string>
|
||||||
<string name="save_as_bookmark">Gorde laster-marka bezala</string>
|
<string name="save_as_bookmark">Gorde laster-marka bezala</string>
|
||||||
<string name="delete_bookmark">Laster-marka ezbatu</string>
|
<string name="delete_bookmark">Laster-marka ezabatu</string>
|
||||||
<string name="bookmark_already_exists">Laster-marka hau existitzen da dagoeneko</string>
|
<string name="bookmark_already_exists">Laster-marka hau existitzen da dagoeneko</string>
|
||||||
<string name="you">Zu</string>
|
<string name="you">Zu</string>
|
||||||
<string name="action_edit_subject">Konferentziaren gaia editatu</string>
|
<string name="action_edit_subject">Konferentziaren gaia editatu</string>
|
||||||
|
@ -250,11 +250,27 @@
|
||||||
<string name="pref_force_encryption_summary">Mezuak beti enkriptatuta bidali (konferentzietan izan ezik)</string>
|
<string name="pref_force_encryption_summary">Mezuak beti enkriptatuta bidali (konferentzietan izan ezik)</string>
|
||||||
<string name="pref_dont_save_encrypted">Ez gorde enkriptatutako mezuak</string>
|
<string name="pref_dont_save_encrypted">Ez gorde enkriptatutako mezuak</string>
|
||||||
<string name="pref_dont_save_encrypted_summary">Adi: Honek mezuen galera ekar lezake</string>
|
<string name="pref_dont_save_encrypted_summary">Adi: Honek mezuen galera ekar lezake</string>
|
||||||
|
<string name="pref_enable_legacy_ssl">Oinordetutako SSL gaitu</string>
|
||||||
|
<string name="pref_enable_legacy_ssl_summary">SSLv3 gaitzen du oinordetutako zerbitzarietarako. Adi: SSLv3 ez segurutzat hartzen da.</string>
|
||||||
<string name="pref_expert_options">Adituentzako aukerak</string>
|
<string name="pref_expert_options">Adituentzako aukerak</string>
|
||||||
<string name="pref_expert_options_summary">Mesedez kontuz ibili hauekin</string>
|
<string name="pref_expert_options_summary">Mesedez kontuz ibili hauekin</string>
|
||||||
<string name="pref_use_larger_font">Letraren tamaina handitu</string>
|
<string name="pref_use_larger_font">Letraren tamaina handitu</string>
|
||||||
<string name="pref_use_larger_font_summary">Letra tamaina handiagoa erabili aplikazio osoan zehar</string>
|
<string name="pref_use_larger_font_summary">Letra tamaina handiagoa erabili aplikazio osoan zehar</string>
|
||||||
<string name="pref_use_send_button_to_indicate_status">Bidaltze botoiak egoera adierazten du</string>
|
<string name="pref_use_send_button_to_indicate_status">Bidaltze botoiak egoera adierazten du</string>
|
||||||
|
<string name="pref_use_indicate_received">Mezuen jasotzea eskatu</string>
|
||||||
|
<string name="pref_use_indicate_received_summary">Jasotako mezuak marka berde batekin markatuko dira. Baliteke kasu guztietan ez funtzionatzea.</string>
|
||||||
<string name="pref_use_send_button_to_indicate_status_summary">Bidaltze botoia koloreztatu kontaktu baten egoera adierazteko</string>
|
<string name="pref_use_send_button_to_indicate_status_summary">Bidaltze botoia koloreztatu kontaktu baten egoera adierazteko</string>
|
||||||
|
<string name="pref_expert_options_other">Besteak</string>
|
||||||
|
<string name="pref_conference_name">Konferentziaren izena</string>
|
||||||
|
<string name="pref_conference_name_summary">Erabili gelaren gaia konferentziak identifikatzeko eta ez JIDa</string>
|
||||||
|
<string name="toast_message_otr_fingerprint">OTR hatz-marka arbelara kopiatu da</string>
|
||||||
|
<string name="conference_banned">Konferentzia honetara sartzea debekatuta duzu</string>
|
||||||
|
<string name="conference_members_only">Konferentzia hau kideentzat da soilik</string>
|
||||||
|
<string name="conference_kicked">Konferentzia honetatik kanporatua izan zara</string>
|
||||||
|
<string name="using_account">%s kontua erabiltzen</string>
|
||||||
|
<string name="checking_image">Irudia egiaztatzen HTTP ostalarian</string>
|
||||||
|
<string name="image_file_deleted">Irudia ezabatu egin da</string>
|
||||||
|
<string name="not_connected_try_again">Ez zaude konektatuta. Saiatu beranduago berriz</string>
|
||||||
|
<string name="check_image_filesize">Irudiaren tamaina egiaztatu</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
<string name="encrypted_message_received"><i>Message chiffré reçu. Appuyez pour le déchiffrer.</i></string>
|
<string name="encrypted_message_received"><i>Message chiffré reçu. Appuyez pour le déchiffrer.</i></string>
|
||||||
<string name="encrypted_image_received"><i>Image chiffrée reçue. Appuyez pour la déchiffrer.</i></string>
|
<string name="encrypted_image_received"><i>Image chiffrée reçue. Appuyez pour la déchiffrer.</i></string>
|
||||||
<string name="image_file"><i>Image reçue. Appuyez pour visualiser.</i></string>
|
<string name="image_file"><i>Image reçue. Appuyez pour visualiser.</i></string>
|
||||||
|
<string name="pref_general">Général</string>
|
||||||
<string name="pref_xmpp_resource">Ressource XMPP</string>
|
<string name="pref_xmpp_resource">Ressource XMPP</string>
|
||||||
<string name="pref_xmpp_resource_summary">Nom permettant d\'identifier ce client XMPP</string>
|
<string name="pref_xmpp_resource_summary">Nom permettant d\'identifier ce client XMPP</string>
|
||||||
<string name="pref_accept_files">Accepter les fichiers</string>
|
<string name="pref_accept_files">Accepter les fichiers</string>
|
||||||
|
@ -107,6 +108,7 @@
|
||||||
<string name="pref_never_send_crash_summary">En envoyant des logs vous aidez au développement de Conversations.</string>
|
<string name="pref_never_send_crash_summary">En envoyant des logs vous aidez au développement de Conversations.</string>
|
||||||
<string name="pref_confirm_messages">Confirmation de lecture</string>
|
<string name="pref_confirm_messages">Confirmation de lecture</string>
|
||||||
<string name="pref_confirm_messages_summary">Informer l\'expéditeur d\'un message de sa bonne réception.</string>
|
<string name="pref_confirm_messages_summary">Informer l\'expéditeur d\'un message de sa bonne réception.</string>
|
||||||
|
<string name="pref_ui_options">Options d\'affichage</string>
|
||||||
<string name="openpgp_error">Une erreur s\'est produite via OpenKeychain</string>
|
<string name="openpgp_error">Une erreur s\'est produite via OpenKeychain</string>
|
||||||
<string name="error_decrypting_file">Erreur d\'E/S lors du déchiffrement du fichier</string>
|
<string name="error_decrypting_file">Erreur d\'E/S lors du déchiffrement du fichier</string>
|
||||||
<string name="accept">Accepter</string>
|
<string name="accept">Accepter</string>
|
||||||
|
@ -145,6 +147,8 @@
|
||||||
<string name="mgmt_account_edit">Modifier le compte</string>
|
<string name="mgmt_account_edit">Modifier le compte</string>
|
||||||
<string name="mgmt_account_delete">Supprimer</string>
|
<string name="mgmt_account_delete">Supprimer</string>
|
||||||
<string name="mgmt_account_disable">Désactiver temporairement</string>
|
<string name="mgmt_account_disable">Désactiver temporairement</string>
|
||||||
|
<string name="mgmt_account_publish_avatar">Publier un avatar</string>
|
||||||
|
<string name="mgmt_account_publish_pgp">Publier la clef publique OpenPGP</string>
|
||||||
<string name="mgmt_account_enable">Activer</string>
|
<string name="mgmt_account_enable">Activer</string>
|
||||||
<string name="mgmt_account_are_you_sure">Êtes-vous sûr?</string>
|
<string name="mgmt_account_are_you_sure">Êtes-vous sûr?</string>
|
||||||
<string name="mgmt_account_delete_confirm_text">En supprimant votre compte, votre historique de conversations sera perdu!</string>
|
<string name="mgmt_account_delete_confirm_text">En supprimant votre compte, votre historique de conversations sera perdu!</string>
|
||||||
|
@ -169,6 +173,9 @@
|
||||||
<string name="muc_details_other_members">Autres membres</string>
|
<string name="muc_details_other_members">Autres membres</string>
|
||||||
<string name="server_info_carbon_messages">Copies carbone</string>
|
<string name="server_info_carbon_messages">Copies carbone</string>
|
||||||
<string name="server_info_stream_management">Gestion des flux</string>
|
<string name="server_info_stream_management">Gestion des flux</string>
|
||||||
|
<string name="server_info_pep">XEP-0163: PEP (Avatars)</string>
|
||||||
|
<string name="server_info_available">disponible</string>
|
||||||
|
<string name="server_info_unavailable">indisponible</string>
|
||||||
<string name="missing_public_keys">Aucune annonce de clef publique</string>
|
<string name="missing_public_keys">Aucune annonce de clef publique</string>
|
||||||
<string name="last_seen_now">en ligne à l\'instant</string>
|
<string name="last_seen_now">en ligne à l\'instant</string>
|
||||||
<string name="last_seen_min">en ligne il y a 1 minute</string>
|
<string name="last_seen_min">en ligne il y a 1 minute</string>
|
||||||
|
@ -207,6 +214,60 @@
|
||||||
<string name="contact_added_you">Votre correspondant vous a ajouté dans sa liste de contacts</string>
|
<string name="contact_added_you">Votre correspondant vous a ajouté dans sa liste de contacts</string>
|
||||||
<string name="add_back">Ajouter également</string>
|
<string name="add_back">Ajouter également</string>
|
||||||
<string name="contact_has_read_up_to_this_point">%s a lu les messages précédents.</string>
|
<string name="contact_has_read_up_to_this_point">%s a lu les messages précédents.</string>
|
||||||
<string name="pref_ui_options">Options d\'affichage</string>
|
<string name="publish">Publier</string>
|
||||||
|
<string name="touch_to_choose_picture">Toucher l\'avatar pour choisir une image depuis la galerie.</string>
|
||||||
|
<string name="publish_avatar_explanation">Nota Bene: Les personnes ayant activé les mises jour de présence verront cette image.</string>
|
||||||
|
<string name="publishing">Mise à jour…</string>
|
||||||
|
<string name="error_publish_avatar_server_reject">Le serveur a rejeté votre envoi d\'image</string>
|
||||||
|
<string name="error_publish_avatar_converting">Une erreur s\'est produite pendant la conversion de votre image.</string>
|
||||||
|
<string name="error_saving_avatar">Impossible de stocker l\'image sur le disque</string>
|
||||||
|
<string name="or_long_press_for_default">(Un appui long réinitialise le paramètre par defaut)</string>
|
||||||
|
<string name="error_publish_avatar_no_server_support">Votre serveur n\'autorise pas l\'envoi d\'avatars</string>
|
||||||
|
<string name="private_message">chuchoté</string>
|
||||||
|
<string name="private_message_to">pour %s</string>
|
||||||
|
<string name="send_private_message_to">Envoyer un message privé à %s</string>
|
||||||
|
<string name="connect">Se connecter</string>
|
||||||
|
<string name="account_already_exists">Ce compte existe déjà</string>
|
||||||
|
<string name="next">suivant</string>
|
||||||
|
<string name="server_info_session_established">Session établie</string>
|
||||||
|
<string name="additional_information">Informations supplémentaires</string>
|
||||||
|
<string name="skip">Passer</string>
|
||||||
|
<string name="disable_notifications">Désactiver les notifications</string>
|
||||||
|
<string name="disable_notifications_for_this_conversation">Désactiver les notifications pour cette conversation</string>
|
||||||
|
<string name="notifications_disabled">Notifications are Désactivées</string>
|
||||||
|
<string name="enable">Activer</string>
|
||||||
|
<string name="conference_requires_password">La conférence necessite un mot de passe</string>
|
||||||
|
<string name="enter_password">Entrer le mot de passe</string>
|
||||||
|
<string name="missing_presence_updates">Mise à jour de présence non connue</string>
|
||||||
|
<string name="request_presence_updates">Merci de demander à votre contact de fournir les mises à jour de présence.\n\n<small>Cela permettra de savoir quel matériel utilise votre contact.</small></string>
|
||||||
|
<string name="request_now">Demander maintenant</string>
|
||||||
|
<string name="delete_fingerprint">Supprimer l\'empreinte</string>
|
||||||
|
<string name="sure_delete_fingerprint">Etes-vous sûr de vouloir supprimer l\'empreinte?</string>
|
||||||
|
<string name="ignore">Ignorer</string>
|
||||||
|
<string name="without_mutual_presence_updates"><b>Attention:</b> Ceci peut poser problème si l\'un des deux correspondants n\'a pas activé les mises à jour de présence.\n\n<small>Go to contact details to verify your presence subscriptions.</small></string>
|
||||||
|
<string name="pref_encryption_settings">Paramètres de chiffrement</string>
|
||||||
|
<string name="pref_force_encryption">Forcer le chiffrement de bout en bout</string>
|
||||||
|
<string name="pref_force_encryption_summary">Toujours envoyer des messages chiffrés (sauf pour les conférences)</string>
|
||||||
|
<string name="pref_dont_save_encrypted">Ne pas sauvegarder les messages chiffrés</string>
|
||||||
|
<string name="pref_dont_save_encrypted_summary">Attention: Celà peut mener à une perte de messages</string>
|
||||||
|
<string name="pref_expert_options">Options avancées</string>
|
||||||
|
<string name="pref_expert_options_summary">A utiliser avec précautions</string>
|
||||||
|
<string name="pref_use_larger_font">Augmenter la taille du texte</string>
|
||||||
|
<string name="pref_use_larger_font_summary">Augmenter la taille du texte partout dans l\'application</string>
|
||||||
|
<string name="pref_use_send_button_to_indicate_status">Le bouton Envoyer permet d\'indiquer le statut</string>
|
||||||
|
<string name="pref_use_indicate_received">Accusé de reception</string>
|
||||||
|
<string name="pref_use_indicate_received_summary">Les messages recus seront marqués d\'une coche verte si disponible</string>
|
||||||
|
<string name="pref_use_send_button_to_indicate_status_summary">Adapter la couleur du bouton Envoyer pour indiquer le statut</string>
|
||||||
|
<string name="pref_expert_options_other">Autres</string>
|
||||||
|
<string name="pref_conference_name">Nom de la conférence </string>
|
||||||
|
<string name="pref_conference_name_summary">Identifier la conférence par son nom plutot que par son JID</string>
|
||||||
|
<string name="toast_message_otr_fingerprint">Empreinte OTR copiée dans le presse-papier!</string>
|
||||||
|
<string name="conference_banned">Vous êtes interdit de cette conférence</string>
|
||||||
|
<string name="conference_members_only">Cette conférence est réservée aux membres</string>
|
||||||
|
<string name="conference_kicked">Vous avez été éjecté de cette conférence</string>
|
||||||
|
<string name="using_account">utiliser le compte %s</string>
|
||||||
|
<string name="checking_image">Vérification de l\'image</string>
|
||||||
|
<string name="image_file_deleted">L\'image a été suprimée</string>
|
||||||
|
<string name="not_connected_try_again">Vous n\'êtes pas connecté. Merci de retenter plus tard.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<style name="sectionHeader" parent="android:Widget.Holo.Light.TextView">
|
|
||||||
<item name="android:drawableBottom">@drawable/section_header</item>
|
|
||||||
<item name="android:drawablePadding">4dp</item>
|
|
||||||
<item name="android:layout_marginTop">8dp</item>
|
|
||||||
<item name="android:textSize">14sp</item>
|
|
||||||
<item name="android:textAllCaps">true</item>
|
|
||||||
<item name="android:textColor">#5b5b5b</item>
|
|
||||||
<item name="android:textStyle">bold</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Divider">
|
|
||||||
<item name="android:layout_width">match_parent</item>
|
|
||||||
<item name="android:layout_height">1.5dp</item>
|
|
||||||
<item name="android:background">#b7b7b7</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
|
@ -22,7 +22,7 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="mute_options_descriptions">
|
<string-array name="mute_options_descriptions">
|
||||||
<item>30 minutes</item>
|
<item>30 minutes</item>
|
||||||
<item>one hour</item>
|
<item>1 hour</item>
|
||||||
<item>2 hours</item>
|
<item>2 hours</item>
|
||||||
<item>8 hours</item>
|
<item>8 hours</item>
|
||||||
<item>until further notice</item>
|
<item>until further notice</item>
|
||||||
|
@ -36,4 +36,4 @@
|
||||||
<item>-1</item>
|
<item>-1</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -250,13 +250,15 @@
|
||||||
<string name="pref_force_encryption_summary">Always send messages encrypted (except for conferences)</string>
|
<string name="pref_force_encryption_summary">Always send messages encrypted (except for conferences)</string>
|
||||||
<string name="pref_dont_save_encrypted">Don’t save encrypted messages</string>
|
<string name="pref_dont_save_encrypted">Don’t save encrypted messages</string>
|
||||||
<string name="pref_dont_save_encrypted_summary">Warning: This could lead to message loss</string>
|
<string name="pref_dont_save_encrypted_summary">Warning: This could lead to message loss</string>
|
||||||
|
<string name="pref_enable_legacy_ssl">Enable legacy SSL</string>
|
||||||
|
<string name="pref_enable_legacy_ssl_summary">Enables SSLv3 support for legacy servers. Warning: SSLv3 is considered insecure.</string>
|
||||||
<string name="pref_expert_options">Expert options</string>
|
<string name="pref_expert_options">Expert options</string>
|
||||||
<string name="pref_expert_options_summary">Please be very careful with those</string>
|
<string name="pref_expert_options_summary">Please be careful with these</string>
|
||||||
<string name="pref_use_larger_font">Increase font size</string>
|
<string name="pref_use_larger_font">Increase font size</string>
|
||||||
<string name="pref_use_larger_font_summary">Use larger font sizes across the entire app</string>
|
<string name="pref_use_larger_font_summary">Use larger font sizes across the entire app</string>
|
||||||
<string name="pref_use_send_button_to_indicate_status">Send button indicates status</string>
|
<string name="pref_use_send_button_to_indicate_status">Send button indicates status</string>
|
||||||
<string name="pref_use_indicate_received">Request message receipts</string>
|
<string name="pref_use_indicate_received">Request message receipts</string>
|
||||||
<string name="pref_use_indicate_received_summary">Received masseges will be marked with a green tick. Be aware that this might no work in every case.</string>
|
<string name="pref_use_indicate_received_summary">Received messages will be marked with a green tick if supported</string>
|
||||||
<string name="pref_use_send_button_to_indicate_status_summary">Colorize send button to indicate contact status</string>
|
<string name="pref_use_send_button_to_indicate_status_summary">Colorize send button to indicate contact status</string>
|
||||||
<string name="pref_expert_options_other">Other</string>
|
<string name="pref_expert_options_other">Other</string>
|
||||||
<string name="pref_conference_name">Conference name</string>
|
<string name="pref_conference_name">Conference name</string>
|
||||||
|
@ -265,5 +267,20 @@
|
||||||
<string name="conference_banned">You are banned from this conference</string>
|
<string name="conference_banned">You are banned from this conference</string>
|
||||||
<string name="conference_members_only">This conference is members only</string>
|
<string name="conference_members_only">This conference is members only</string>
|
||||||
<string name="conference_kicked">You have been kicked from this conference</string>
|
<string name="conference_kicked">You have been kicked from this conference</string>
|
||||||
|
<string name="using_account">using account %s</string>
|
||||||
|
<string name="checking_image">Checking image on HTTP host</string>
|
||||||
|
<string name="image_file_deleted">The image file has been deleted</string>
|
||||||
|
<string name="not_connected_try_again">You are not connected. Try again later</string>
|
||||||
|
<string name="check_image_filesize">Check image file size</string>
|
||||||
|
<string name="message_options">Message options</string>
|
||||||
|
<string name="copy_text">Copy text</string>
|
||||||
|
<string name="share_image">Share image</string>
|
||||||
|
<string name="copy_original_url">Copy original URL</string>
|
||||||
|
<string name="send_again">Send again</string>
|
||||||
|
<string name="image_url">Image URL</string>
|
||||||
|
<string name="message_text">Message text</string>
|
||||||
|
<string name="url_copied_to_clipboard">URL copied to clipboard</string>
|
||||||
|
<string name="message_copied_to_clipboard">Message copied to clipboard</string>
|
||||||
|
<string name="image_transmission_failed">Image transmission failed</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -1,15 +1,4 @@
|
||||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<style name="sectionHeader" parent="android:Widget.Holo.Light.TextView">
|
|
||||||
<item name="android:drawableBottom">@drawable/section_header</item>
|
|
||||||
<item name="android:drawablePadding">4dp</item>
|
|
||||||
<item name="android:layout_marginTop">8dp</item>
|
|
||||||
<item name="android:textSize">14sp</item>
|
|
||||||
<item name="android:textAllCaps">true</item>
|
|
||||||
<item name="android:textColor">@color/primarytext</item>
|
|
||||||
<item name="android:textStyle">bold</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Divider">
|
<style name="Divider">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
<item name="android:layout_height">1.5dp</item>
|
<item name="android:layout_height">1.5dp</item>
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
android:title="@string/pref_sound" />
|
android:title="@string/pref_sound" />
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
|
android:defaultValue="true"
|
||||||
android:dependency="show_notification"
|
android:dependency="show_notification"
|
||||||
android:key="always_notify_in_conference"
|
android:key="always_notify_in_conference"
|
||||||
android:summary="@string/pref_conference_notifications_summary"
|
android:summary="@string/pref_conference_notifications_summary"
|
||||||
|
@ -88,6 +89,11 @@
|
||||||
android:key="dont_save_encrypted"
|
android:key="dont_save_encrypted"
|
||||||
android:summary="@string/pref_dont_save_encrypted_summary"
|
android:summary="@string/pref_dont_save_encrypted_summary"
|
||||||
android:title="@string/pref_dont_save_encrypted" />
|
android:title="@string/pref_dont_save_encrypted" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="enable_legacy_ssl"
|
||||||
|
android:summary="@string/pref_enable_legacy_ssl_summary"
|
||||||
|
android:title="@string/pref_enable_legacy_ssl" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/pref_expert_options_other" >
|
<PreferenceCategory android:title="@string/pref_expert_options_other" >
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
|
@ -105,4 +111,4 @@
|
||||||
android:title="@string/pref_never_send_crash" />
|
android:title="@string/pref_never_send_crash" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
|
@ -10,7 +10,8 @@ public final class Config {
|
||||||
public static final int PING_MIN_INTERVAL = 30;
|
public static final int PING_MIN_INTERVAL = 30;
|
||||||
public static final int PING_TIMEOUT = 10;
|
public static final int PING_TIMEOUT = 10;
|
||||||
public static final int CONNECT_TIMEOUT = 90;
|
public static final int CONNECT_TIMEOUT = 90;
|
||||||
public static final int CARBON_GRACE_PERIOD = 120;
|
public static final int CARBON_GRACE_PERIOD = 60;
|
||||||
|
public static final int MINI_GRACE_PERIOD = 750;
|
||||||
|
|
||||||
public static final int AVATAR_SIZE = 192;
|
public static final int AVATAR_SIZE = 192;
|
||||||
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;
|
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;
|
||||||
|
|
|
@ -8,25 +8,24 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpError;
|
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
|
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.UiCallback;
|
import eu.siacs.conversations.ui.UiCallback;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleFile;
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.util.Log;
|
import android.net.Uri;
|
||||||
|
|
||||||
public class PgpEngine {
|
public class PgpEngine {
|
||||||
private OpenPgpApi api;
|
private OpenPgpApi api;
|
||||||
|
@ -39,7 +38,6 @@ public class PgpEngine {
|
||||||
|
|
||||||
public void decrypt(final Message message,
|
public void decrypt(final Message message,
|
||||||
final UiCallback<Message> callback) {
|
final UiCallback<Message> callback) {
|
||||||
Log.d(Config.LOGTAG, "decrypting message " + message.getUuid());
|
|
||||||
Intent params = new Intent();
|
Intent params = new Intent();
|
||||||
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
|
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
|
||||||
|
@ -60,6 +58,10 @@ public class PgpEngine {
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
message.setBody(os.toString());
|
message.setBody(os.toString());
|
||||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||||
|
if (message.trusted() && message.bodyContainsDownloadable()) {
|
||||||
|
mXmppConnectionService.getHttpConnectionManager()
|
||||||
|
.createNewConnection(message);
|
||||||
|
}
|
||||||
callback.success(message);
|
callback.success(message);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -74,9 +76,6 @@ public class PgpEngine {
|
||||||
message);
|
message);
|
||||||
return;
|
return;
|
||||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||||
OpenPgpError error = result
|
|
||||||
.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
|
||||||
Log.d(Config.LOGTAG, error.getMessage());
|
|
||||||
callback.error(R.string.openpgp_error, message);
|
callback.error(R.string.openpgp_error, message);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
@ -86,10 +85,10 @@ public class PgpEngine {
|
||||||
});
|
});
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||||
try {
|
try {
|
||||||
final JingleFile inputFile = this.mXmppConnectionService
|
final DownloadableFile inputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getJingleFile(message, false);
|
.getFileBackend().getFile(message, false);
|
||||||
final JingleFile outputFile = this.mXmppConnectionService
|
final DownloadableFile outputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getJingleFile(message, true);
|
.getFileBackend().getFile(message, true);
|
||||||
outputFile.createNewFile();
|
outputFile.createNewFile();
|
||||||
InputStream is = new FileInputStream(inputFile);
|
InputStream is = new FileInputStream(inputFile);
|
||||||
OutputStream os = new FileOutputStream(outputFile);
|
OutputStream os = new FileOutputStream(outputFile);
|
||||||
|
@ -100,19 +99,32 @@ public class PgpEngine {
|
||||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||||
|
URL url = message.getImageParams().url;
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeFile(
|
BitmapFactory.decodeFile(
|
||||||
outputFile.getAbsolutePath(), options);
|
outputFile.getAbsolutePath(), options);
|
||||||
int imageHeight = options.outHeight;
|
int imageHeight = options.outHeight;
|
||||||
int imageWidth = options.outWidth;
|
int imageWidth = options.outWidth;
|
||||||
message.setBody(Long.toString(outputFile.getSize())
|
if (url == null) {
|
||||||
+ ',' + imageWidth + ',' + imageHeight);
|
message.setBody(Long.toString(outputFile
|
||||||
|
.getSize())
|
||||||
|
+ '|'
|
||||||
|
+ imageWidth
|
||||||
|
+ '|'
|
||||||
|
+ imageHeight);
|
||||||
|
} else {
|
||||||
|
message.setBody(url.toString() + "|"
|
||||||
|
+ Long.toString(outputFile.getSize())
|
||||||
|
+ '|' + imageWidth + '|' + imageHeight);
|
||||||
|
}
|
||||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||||
PgpEngine.this.mXmppConnectionService
|
PgpEngine.this.mXmppConnectionService
|
||||||
.updateMessage(message);
|
.updateMessage(message);
|
||||||
PgpEngine.this.mXmppConnectionService
|
inputFile.delete();
|
||||||
.updateConversationUi();
|
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||||
|
intent.setData(Uri.fromFile(outputFile));
|
||||||
|
mXmppConnectionService.sendBroadcast(intent);
|
||||||
callback.success(message);
|
callback.success(message);
|
||||||
return;
|
return;
|
||||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||||
|
@ -197,10 +209,10 @@ public class PgpEngine {
|
||||||
});
|
});
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||||
try {
|
try {
|
||||||
JingleFile inputFile = this.mXmppConnectionService
|
DownloadableFile inputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getJingleFile(message, true);
|
.getFileBackend().getFile(message, true);
|
||||||
JingleFile outputFile = this.mXmppConnectionService
|
DownloadableFile outputFile = this.mXmppConnectionService
|
||||||
.getFileBackend().getJingleFile(message, false);
|
.getFileBackend().getFile(message, false);
|
||||||
outputFile.createNewFile();
|
outputFile.createNewFile();
|
||||||
InputStream is = new FileInputStream(inputFile);
|
InputStream is = new FileInputStream(inputFile);
|
||||||
OutputStream os = new FileOutputStream(outputFile);
|
OutputStream os = new FileOutputStream(outputFile);
|
||||||
|
@ -226,9 +238,11 @@ public class PgpEngine {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Log.d(Config.LOGTAG, "file not found: " + e.getMessage());
|
callback.error(R.string.openpgp_error, message);
|
||||||
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(Config.LOGTAG, "io exception during file encrypt");
|
callback.error(R.string.openpgp_error, message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,11 +286,6 @@ public class PgpEngine {
|
||||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||||
return 0;
|
return 0;
|
||||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||||
Log.d(Config.LOGTAG,
|
|
||||||
"openpgp error: "
|
|
||||||
+ ((OpenPgpError) result
|
|
||||||
.getParcelableExtra(OpenPgpApi.RESULT_ERROR))
|
|
||||||
.getMessage());
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -11,16 +11,14 @@ import net.java.otr4j.crypto.OtrCryptoException;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.OtrEngine;
|
import eu.siacs.conversations.crypto.OtrEngine;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
|
||||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
public class Account extends AbstractEntity {
|
public class Account extends AbstractEntity {
|
||||||
|
|
||||||
|
@ -64,16 +62,14 @@ public class Account extends AbstractEntity {
|
||||||
|
|
||||||
protected boolean online = false;
|
protected boolean online = false;
|
||||||
|
|
||||||
transient OtrEngine otrEngine = null;
|
private OtrEngine otrEngine = null;
|
||||||
transient XmppConnection xmppConnection = null;
|
private XmppConnection xmppConnection = null;
|
||||||
transient protected Presences presences = new Presences();
|
private Presences presences = new Presences();
|
||||||
|
private long mEndGracePeriod = 0L;
|
||||||
private String otrFingerprint;
|
private String otrFingerprint;
|
||||||
|
|
||||||
private Roster roster = null;
|
private Roster roster = null;
|
||||||
|
|
||||||
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>();
|
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>();
|
||||||
|
|
||||||
public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<Conversation>();
|
public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<Conversation>();
|
||||||
public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<Conversation>();
|
public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<Conversation>();
|
||||||
|
|
||||||
|
@ -160,8 +156,12 @@ public class Account extends AbstractEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasErrorStatus() {
|
public boolean hasErrorStatus() {
|
||||||
return getStatus() > STATUS_NO_INTERNET
|
if (getXmppConnection() == null) {
|
||||||
&& (getXmppConnection().getAttempt() >= 2);
|
return false;
|
||||||
|
} else {
|
||||||
|
return getStatus() > STATUS_NO_INTERNET
|
||||||
|
&& (getXmppConnection().getAttempt() >= 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResource(String resource) {
|
public void setResource(String resource) {
|
||||||
|
@ -341,20 +341,6 @@ public class Account extends AbstractEntity {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap getImage(Context context, int size) {
|
|
||||||
if (this.avatar != null) {
|
|
||||||
Bitmap bm = FileBackend.getAvatar(this.avatar, size, context);
|
|
||||||
if (bm == null) {
|
|
||||||
return UIHelper.getContactPicture(getJid(), size, context,
|
|
||||||
false);
|
|
||||||
} else {
|
|
||||||
return bm;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return UIHelper.getContactPicture(getJid(), size, context, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setAvatar(String filename) {
|
public boolean setAvatar(String filename) {
|
||||||
if (this.avatar != null && this.avatar.equals(filename)) {
|
if (this.avatar != null && this.avatar.equals(filename)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -397,4 +383,17 @@ public class Account extends AbstractEntity {
|
||||||
return R.string.account_status_unknown;
|
return R.string.account_status_unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void activateGracePeriod() {
|
||||||
|
this.mEndGracePeriod = SystemClock.elapsedRealtime()
|
||||||
|
+ (Config.CARBON_GRACE_PERIOD * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivateGracePeriod() {
|
||||||
|
this.mEndGracePeriod = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean inGracePeriod() {
|
||||||
|
return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,6 @@ package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
|
||||||
public class Bookmark extends Element implements ListItem {
|
public class Bookmark extends Element implements ListItem {
|
||||||
|
@ -120,21 +117,14 @@ public class Bookmark extends Element implements ListItem {
|
||||||
return this.account;
|
return this.account;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bitmap getImage(int dpSize, Context context) {
|
|
||||||
if (this.mJoinedConversation == null) {
|
|
||||||
return UIHelper.getContactPicture(getDisplayName(), dpSize,
|
|
||||||
context, false);
|
|
||||||
} else {
|
|
||||||
return UIHelper.getContactPicture(this.mJoinedConversation, dpSize,
|
|
||||||
context, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConversation(Conversation conversation) {
|
public void setConversation(Conversation conversation) {
|
||||||
this.mJoinedConversation = conversation;
|
this.mJoinedConversation = conversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Conversation getConversation() {
|
||||||
|
return this.mJoinedConversation;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.getAttribute("name");
|
return this.getAttribute("name");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,9 @@ import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
public class Contact implements ListItem {
|
public class Contact implements ListItem {
|
||||||
public static final String TABLENAME = "contacts";
|
public static final String TABLENAME = "contacts";
|
||||||
|
@ -330,20 +326,6 @@ public class Contact implements ListItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bitmap getImage(int size, Context context) {
|
|
||||||
if (this.avatar != null) {
|
|
||||||
Bitmap bm = FileBackend.getAvatar(avatar, size, context);
|
|
||||||
if (bm == null) {
|
|
||||||
return UIHelper.getContactPicture(this, size, context, false);
|
|
||||||
} else {
|
|
||||||
return bm;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return UIHelper.getContactPicture(this, size, context, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setAvatar(String filename) {
|
public boolean setAvatar(String filename) {
|
||||||
if (this.avatar != null && this.avatar.equals(filename)) {
|
if (this.avatar != null && this.avatar.equals(filename)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -353,6 +335,10 @@ public class Contact implements ListItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAvatar() {
|
||||||
|
return this.avatar;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean deleteOtrFingerprint(String fingerprint) {
|
public boolean deleteOtrFingerprint(String fingerprint) {
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
|
@ -374,4 +360,8 @@ public class Contact implements ListItem {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean trusted() {
|
||||||
|
return getOption(Options.FROM) && getOption(Options.TO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package eu.siacs.conversations.entities;
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
import java.security.interfaces.DSAPublicKey;
|
import java.security.interfaces.DSAPublicKey;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
|
||||||
|
|
||||||
import net.java.otr4j.OtrException;
|
import net.java.otr4j.OtrException;
|
||||||
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
||||||
|
@ -17,9 +16,7 @@ import net.java.otr4j.session.SessionID;
|
||||||
import net.java.otr4j.session.SessionImpl;
|
import net.java.otr4j.session.SessionImpl;
|
||||||
import net.java.otr4j.session.SessionStatus;
|
import net.java.otr4j.session.SessionStatus;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
public class Conversation extends AbstractEntity {
|
public class Conversation extends AbstractEntity {
|
||||||
|
@ -57,8 +54,8 @@ public class Conversation extends AbstractEntity {
|
||||||
|
|
||||||
private String nextPresence;
|
private String nextPresence;
|
||||||
|
|
||||||
private transient CopyOnWriteArrayList<Message> messages = null;
|
protected ArrayList<Message> messages = new ArrayList<Message>();
|
||||||
private transient Account account = null;
|
protected Account account = null;
|
||||||
|
|
||||||
private transient SessionImpl otrSession;
|
private transient SessionImpl otrSession;
|
||||||
|
|
||||||
|
@ -68,12 +65,10 @@ public class Conversation extends AbstractEntity {
|
||||||
|
|
||||||
private transient MucOptions mucOptions = null;
|
private transient MucOptions mucOptions = null;
|
||||||
|
|
||||||
//private transient String latestMarkableMessageId;
|
// private transient String latestMarkableMessageId;
|
||||||
|
|
||||||
private byte[] symmetricKey;
|
private byte[] symmetricKey;
|
||||||
|
|
||||||
private boolean otrSessionNeedsStarting = false;
|
|
||||||
|
|
||||||
private Bookmark bookmark;
|
private Bookmark bookmark;
|
||||||
|
|
||||||
public Conversation(String name, Account account, String contactJid,
|
public Conversation(String name, Account account, String contactJid,
|
||||||
|
@ -106,17 +101,6 @@ public class Conversation extends AbstractEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Message> getMessages() {
|
public List<Message> getMessages() {
|
||||||
if (messages == null) {
|
|
||||||
this.messages = new CopyOnWriteArrayList<Message>(); // prevent null
|
|
||||||
// pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate with Conversation (this)
|
|
||||||
|
|
||||||
for (Message msg : messages) {
|
|
||||||
msg.setConversation(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,8 +126,9 @@ public class Conversation extends AbstractEntity {
|
||||||
if (this.messages == null) {
|
if (this.messages == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for(int i = this.messages.size() - 1; i >= 0; --i) {
|
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
||||||
if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED && this.messages.get(i).markable) {
|
if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED
|
||||||
|
&& this.messages.get(i).markable) {
|
||||||
if (this.messages.get(i).isRead()) {
|
if (this.messages.get(i).isRead()) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -166,7 +151,7 @@ public class Conversation extends AbstractEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMessages(CopyOnWriteArrayList<Message> msgs) {
|
public void setMessages(ArrayList<Message> msgs) {
|
||||||
this.messages = msgs;
|
this.messages = msgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,10 +248,7 @@ public class Conversation extends AbstractEntity {
|
||||||
try {
|
try {
|
||||||
if (sendStart) {
|
if (sendStart) {
|
||||||
this.otrSession.startSession();
|
this.otrSession.startSession();
|
||||||
this.otrSessionNeedsStarting = false;
|
|
||||||
return this.otrSession;
|
return this.otrSession;
|
||||||
} else {
|
|
||||||
this.otrSessionNeedsStarting = true;
|
|
||||||
}
|
}
|
||||||
return this.otrSession;
|
return this.otrSession;
|
||||||
} catch (OtrException e) {
|
} catch (OtrException e) {
|
||||||
|
@ -282,12 +264,12 @@ public class Conversation extends AbstractEntity {
|
||||||
|
|
||||||
public void resetOtrSession() {
|
public void resetOtrSession() {
|
||||||
this.otrFingerprint = null;
|
this.otrFingerprint = null;
|
||||||
this.otrSessionNeedsStarting = false;
|
|
||||||
this.otrSession = null;
|
this.otrSession = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startOtrIfNeeded() {
|
public void startOtrIfNeeded() {
|
||||||
if (this.otrSession != null && this.otrSessionNeedsStarting) {
|
if (this.otrSession != null
|
||||||
|
&& this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
|
||||||
try {
|
try {
|
||||||
this.otrSession.startSession();
|
this.otrSession.startSession();
|
||||||
} catch (OtrException e) {
|
} catch (OtrException e) {
|
||||||
|
@ -296,18 +278,23 @@ public class Conversation extends AbstractEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endOtrIfNeeded() {
|
public boolean endOtrIfNeeded() {
|
||||||
if (this.otrSession != null) {
|
if (this.otrSession != null) {
|
||||||
if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
||||||
try {
|
try {
|
||||||
this.otrSession.endSession();
|
this.otrSession.endSession();
|
||||||
this.resetOtrSession();
|
this.resetOtrSession();
|
||||||
|
return true;
|
||||||
} catch (OtrException e) {
|
} catch (OtrException e) {
|
||||||
this.resetOtrSession();
|
this.resetOtrSession();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.resetOtrSession();
|
this.resetOtrSession();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,9 +326,8 @@ public class Conversation extends AbstractEntity {
|
||||||
|
|
||||||
public synchronized MucOptions getMucOptions() {
|
public synchronized MucOptions getMucOptions() {
|
||||||
if (this.mucOptions == null) {
|
if (this.mucOptions == null) {
|
||||||
this.mucOptions = new MucOptions(this.getAccount());
|
this.mucOptions = new MucOptions(this);
|
||||||
}
|
}
|
||||||
this.mucOptions.setConversation(this);
|
|
||||||
return this.mucOptions;
|
return this.mucOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,14 +424,6 @@ public class Conversation extends AbstractEntity {
|
||||||
return this.bookmark;
|
return this.bookmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap getImage(Context context, int size) {
|
|
||||||
if (mode == MODE_SINGLE) {
|
|
||||||
return getContact().getImage(size, context);
|
|
||||||
} else {
|
|
||||||
return UIHelper.getContactPicture(this, size, context, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasDuplicateMessage(Message message) {
|
public boolean hasDuplicateMessage(Message message) {
|
||||||
for (int i = this.getMessages().size() - 1; i >= 0; --i) {
|
for (int i = this.getMessages().size() - 1; i >= 0; --i) {
|
||||||
if (this.messages.get(i).equals(message)) {
|
if (this.messages.get(i).equals(message)) {
|
||||||
|
@ -506,4 +484,17 @@ public class Conversation extends AbstractEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void add(Message message) {
|
||||||
|
message.setConversation(this);
|
||||||
|
synchronized (this.messages) {
|
||||||
|
this.messages.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(int index, List<Message> messages) {
|
||||||
|
synchronized (this.messages) {
|
||||||
|
this.messages.addAll(index, messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
package eu.siacs.conversations.entities;
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
public interface Downloadable {
|
public interface Downloadable {
|
||||||
public void start();
|
|
||||||
|
public final String[] VALID_EXTENSIONS = { "webp", "jpeg", "jpg", "png" };
|
||||||
|
public final String[] VALID_CRYPTO_EXTENSIONS = { "pgp", "gpg", "otr" };
|
||||||
|
|
||||||
|
public static final int STATUS_UNKNOWN = 0x200;
|
||||||
|
public static final int STATUS_CHECKING = 0x201;
|
||||||
|
public static final int STATUS_FAILED = 0x202;
|
||||||
|
public static final int STATUS_OFFER = 0x203;
|
||||||
|
public static final int STATUS_DOWNLOADING = 0x204;
|
||||||
|
public static final int STATUS_DELETED = 0x205;
|
||||||
|
public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206;
|
||||||
|
|
||||||
|
public boolean start();
|
||||||
|
|
||||||
|
public int getStatus();
|
||||||
|
|
||||||
|
public long getFileSize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class DownloadableFile extends File {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2247012619505115863L;
|
||||||
|
|
||||||
|
private long expectedSize = 0;
|
||||||
|
private String sha1sum;
|
||||||
|
private Key aeskey;
|
||||||
|
|
||||||
|
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
||||||
|
|
||||||
|
public DownloadableFile(String path) {
|
||||||
|
super(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSize() {
|
||||||
|
return super.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpectedSize() {
|
||||||
|
if (this.aeskey != null) {
|
||||||
|
if (this.expectedSize == 0) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return (this.expectedSize / 16 + 1) * 16;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.expectedSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpectedSize(long size) {
|
||||||
|
this.expectedSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSha1Sum() {
|
||||||
|
return this.sha1sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSha1Sum(String sum) {
|
||||||
|
this.sha1sum = sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(byte[] key) {
|
||||||
|
if (key.length == 48) {
|
||||||
|
byte[] secretKey = new byte[32];
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
System.arraycopy(key, 0, iv, 0, 16);
|
||||||
|
System.arraycopy(key, 16, secretKey, 0, 32);
|
||||||
|
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||||
|
this.iv = iv;
|
||||||
|
} else if (key.length >= 32) {
|
||||||
|
byte[] secretKey = new byte[32];
|
||||||
|
System.arraycopy(key, 0, secretKey, 0, 32);
|
||||||
|
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||||
|
} else if (key.length >= 16) {
|
||||||
|
byte[] secretKey = new byte[16];
|
||||||
|
System.arraycopy(key, 0, secretKey, 0, 16);
|
||||||
|
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getKey() {
|
||||||
|
return this.aeskey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream createInputStream() {
|
||||||
|
if (this.getKey() == null) {
|
||||||
|
try {
|
||||||
|
return new FileInputStream(this);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips);
|
||||||
|
Log.d(Config.LOGTAG, "opening encrypted input stream");
|
||||||
|
return new CipherInputStream(new FileInputStream(this), cipher);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream createOutputStream() {
|
||||||
|
if (this.getKey() == null) {
|
||||||
|
try {
|
||||||
|
return new FileOutputStream(this);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
IvParameterSpec ips = new IvParameterSpec(this.iv);
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips);
|
||||||
|
Log.d(Config.LOGTAG, "opening encrypted output stream");
|
||||||
|
return new CipherOutputStream(new FileOutputStream(this),
|
||||||
|
cipher);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||||
|
return null;
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,7 @@
|
||||||
package eu.siacs.conversations.entities;
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
public interface ListItem extends Comparable<ListItem> {
|
public interface ListItem extends Comparable<ListItem> {
|
||||||
public String getDisplayName();
|
public String getDisplayName();
|
||||||
|
|
||||||
public String getJid();
|
public String getJid();
|
||||||
|
|
||||||
public Bitmap getImage(int dpSize, Context context);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
package eu.siacs.conversations.entities;
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
||||||
public class Message extends AbstractEntity {
|
public class Message extends AbstractEntity {
|
||||||
|
|
||||||
public static final String TABLENAME = "messages";
|
public static final String TABLENAME = "messages";
|
||||||
|
|
||||||
public static final int STATUS_RECEPTION_FAILED = -3;
|
|
||||||
public static final int STATUS_RECEIVED_OFFER = -2;
|
|
||||||
public static final int STATUS_RECEIVING = -1;
|
|
||||||
public static final int STATUS_RECEIVED = 0;
|
public static final int STATUS_RECEIVED = 0;
|
||||||
public static final int STATUS_UNSEND = 1;
|
public static final int STATUS_UNSEND = 1;
|
||||||
public static final int STATUS_SEND = 2;
|
public static final int STATUS_SEND = 2;
|
||||||
public static final int STATUS_SEND_FAILED = 3;
|
public static final int STATUS_SEND_FAILED = 3;
|
||||||
public static final int STATUS_SEND_REJECTED = 4;
|
|
||||||
public static final int STATUS_WAITING = 5;
|
public static final int STATUS_WAITING = 5;
|
||||||
public static final int STATUS_OFFERED = 6;
|
public static final int STATUS_OFFERED = 6;
|
||||||
public static final int STATUS_SEND_RECEIVED = 7;
|
public static final int STATUS_SEND_RECEIVED = 7;
|
||||||
|
@ -61,6 +59,9 @@ public class Message extends AbstractEntity {
|
||||||
protected Downloadable downloadable = null;
|
protected Downloadable downloadable = null;
|
||||||
public boolean markable = false;
|
public boolean markable = false;
|
||||||
|
|
||||||
|
private Message mNextMessage = null;
|
||||||
|
private Message mPreviousMessage = null;
|
||||||
|
|
||||||
private Message() {
|
private Message() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -131,14 +132,8 @@ public class Message extends AbstractEntity {
|
||||||
if (this.trueCounterpart == null) {
|
if (this.trueCounterpart == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
Account account = this.conversation.getAccount();
|
return this.conversation.getAccount().getRoster()
|
||||||
Contact contact = account.getRoster().getContact(
|
.getContactFromRoster(this.trueCounterpart);
|
||||||
this.trueCounterpart);
|
|
||||||
if (contact.showInRoster()) {
|
|
||||||
return contact;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,22 +142,6 @@ public class Message extends AbstractEntity {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getReadableBody(Context context) {
|
|
||||||
if ((encryption == ENCRYPTION_PGP) && (type == TYPE_TEXT)) {
|
|
||||||
return context.getText(R.string.encrypted_message_received)
|
|
||||||
.toString();
|
|
||||||
} else if ((encryption == ENCRYPTION_OTR) && (type == TYPE_IMAGE)) {
|
|
||||||
return context.getText(R.string.encrypted_image_received)
|
|
||||||
.toString();
|
|
||||||
} else if (encryption == ENCRYPTION_DECRYPTION_FAILED) {
|
|
||||||
return context.getText(R.string.decryption_failed).toString();
|
|
||||||
} else if (type == TYPE_IMAGE) {
|
|
||||||
return context.getText(R.string.image_file).toString();
|
|
||||||
} else {
|
|
||||||
return body.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimeSent() {
|
public long getTimeSent() {
|
||||||
return timeSent;
|
return timeSent;
|
||||||
}
|
}
|
||||||
|
@ -301,21 +280,34 @@ public class Message extends AbstractEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message next() {
|
public Message next() {
|
||||||
int index = this.conversation.getMessages().indexOf(this);
|
if (this.mNextMessage == null) {
|
||||||
if (index < 0 || index >= this.conversation.getMessages().size() - 1) {
|
synchronized (this.conversation.messages) {
|
||||||
return null;
|
int index = this.conversation.messages.indexOf(this);
|
||||||
} else {
|
if (index < 0
|
||||||
return this.conversation.getMessages().get(index + 1);
|
|| index >= this.conversation.getMessages().size() - 1) {
|
||||||
|
this.mNextMessage = null;
|
||||||
|
} else {
|
||||||
|
this.mNextMessage = this.conversation.messages
|
||||||
|
.get(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return this.mNextMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message prev() {
|
public Message prev() {
|
||||||
int index = this.conversation.getMessages().indexOf(this);
|
if (this.mPreviousMessage == null) {
|
||||||
if (index <= 0 || index > this.conversation.getMessages().size()) {
|
synchronized (this.conversation.messages) {
|
||||||
return null;
|
int index = this.conversation.messages.indexOf(this);
|
||||||
} else {
|
if (index <= 0 || index > this.conversation.messages.size()) {
|
||||||
return this.conversation.getMessages().get(index - 1);
|
this.mPreviousMessage = null;
|
||||||
|
} else {
|
||||||
|
this.mPreviousMessage = this.conversation.messages
|
||||||
|
.get(index - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return this.mPreviousMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean mergable(Message message) {
|
public boolean mergable(Message message) {
|
||||||
|
@ -323,6 +315,8 @@ public class Message extends AbstractEntity {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (message.getType() == Message.TYPE_TEXT
|
return (message.getType() == Message.TYPE_TEXT
|
||||||
|
&& this.getDownloadable() == null
|
||||||
|
&& message.getDownloadable() == null
|
||||||
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
||||||
&& this.getType() == message.getType()
|
&& this.getType() == message.getType()
|
||||||
&& this.getEncryption() == message.getEncryption()
|
&& this.getEncryption() == message.getEncryption()
|
||||||
|
@ -332,7 +326,9 @@ public class Message extends AbstractEntity {
|
||||||
.getStatus() == Message.STATUS_SEND_RECEIVED) && (message
|
.getStatus() == Message.STATUS_SEND_RECEIVED) && (message
|
||||||
.getStatus() == Message.STATUS_UNSEND
|
.getStatus() == Message.STATUS_UNSEND
|
||||||
|| message.getStatus() == Message.STATUS_SEND || message
|
|| message.getStatus() == Message.STATUS_SEND || message
|
||||||
.getStatus() == Message.STATUS_SEND_DISPLAYED)))));
|
.getStatus() == Message.STATUS_SEND_DISPLAYED))))
|
||||||
|
&& !message.bodyContainsDownloadable()
|
||||||
|
&& !this.bodyContainsDownloadable());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMergedBody() {
|
public String getMergedBody() {
|
||||||
|
@ -369,4 +365,148 @@ public class Message extends AbstractEntity {
|
||||||
return prev.mergable(this);
|
return prev.mergable(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean trusted() {
|
||||||
|
Contact contact = this.getContact();
|
||||||
|
return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean bodyContainsDownloadable() {
|
||||||
|
try {
|
||||||
|
URL url = new URL(this.getBody());
|
||||||
|
if (!url.getProtocol().equalsIgnoreCase("http")
|
||||||
|
&& !url.getProtocol().equalsIgnoreCase("https")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (url.getPath() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String[] pathParts = url.getPath().split("/");
|
||||||
|
String filename;
|
||||||
|
if (pathParts.length > 0) {
|
||||||
|
filename = pathParts[pathParts.length - 1];
|
||||||
|
} else {
|
||||||
|
filename = pathParts[0];
|
||||||
|
}
|
||||||
|
String[] extensionParts = filename.split("\\.");
|
||||||
|
if (extensionParts.length == 2
|
||||||
|
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
|
||||||
|
extensionParts[extensionParts.length - 1])) {
|
||||||
|
return true;
|
||||||
|
} else if (extensionParts.length == 3
|
||||||
|
&& Arrays
|
||||||
|
.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
|
||||||
|
.contains(extensionParts[extensionParts.length - 1])
|
||||||
|
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
|
||||||
|
extensionParts[extensionParts.length - 2])) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageParams getImageParams() {
|
||||||
|
ImageParams params = getLegacyImageParams();
|
||||||
|
if (params!=null) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
params = new ImageParams();
|
||||||
|
if (this.downloadable != null) {
|
||||||
|
params.size = this.downloadable.getFileSize();
|
||||||
|
}
|
||||||
|
if (body == null) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
String parts[] = body.split("\\|");
|
||||||
|
if (parts.length == 1) {
|
||||||
|
try {
|
||||||
|
params.size = Long.parseLong(parts[0]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
params.origin = parts[0];
|
||||||
|
try {
|
||||||
|
params.url = new URL(parts[0]);
|
||||||
|
} catch (MalformedURLException e1) {
|
||||||
|
params.url = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (parts.length == 3) {
|
||||||
|
try {
|
||||||
|
params.size = Long.parseLong(parts[0]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
params.size = 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
params.width = Integer.parseInt(parts[1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
params.width = 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
params.height = Integer.parseInt(parts[2]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
params.height = 0;
|
||||||
|
}
|
||||||
|
} else if (parts.length == 4) {
|
||||||
|
params.origin = parts[0];
|
||||||
|
try {
|
||||||
|
params.url = new URL(parts[0]);
|
||||||
|
} catch (MalformedURLException e1) {
|
||||||
|
params.url = null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
params.size = Long.parseLong(parts[1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
params.size = 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
params.width = Integer.parseInt(parts[2]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
params.width = 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
params.height = Integer.parseInt(parts[3]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
params.height = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageParams getLegacyImageParams() {
|
||||||
|
ImageParams params = new ImageParams();
|
||||||
|
if (body == null) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
String parts[] = body.split(",");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
try {
|
||||||
|
params.size = Long.parseLong(parts[0]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
params.width = Integer.parseInt(parts[1]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
params.height = Integer.parseInt(parts[2]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageParams {
|
||||||
|
public URL url;
|
||||||
|
public long size = 0;
|
||||||
|
public int width = 0;
|
||||||
|
public int height = 0;
|
||||||
|
public String origin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,10 @@ public class MucOptions {
|
||||||
public long getPgpKeyId() {
|
public long getPgpKeyId() {
|
||||||
return this.pgpKeyId;
|
return this.pgpKeyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Contact getContact() {
|
||||||
|
return account.getRoster().getContactFromRoster(getJid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Account account;
|
private Account account;
|
||||||
|
@ -116,8 +120,9 @@ public class MucOptions {
|
||||||
private String joinnick;
|
private String joinnick;
|
||||||
private String password = null;
|
private String password = null;
|
||||||
|
|
||||||
public MucOptions(Account account) {
|
public MucOptions(Conversation conversation) {
|
||||||
this.account = account;
|
this.account = conversation.getAccount();
|
||||||
|
this.conversation = conversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteUser(String name) {
|
public void deleteUser(String name) {
|
||||||
|
@ -253,10 +258,6 @@ public class MucOptions {
|
||||||
this.joinnick = nick;
|
this.joinnick = nick;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConversation(Conversation conversation) {
|
|
||||||
this.conversation = conversation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean online() {
|
public boolean online() {
|
||||||
return this.isOnline;
|
return this.isOnline;
|
||||||
}
|
}
|
||||||
|
@ -361,4 +362,8 @@ public class MucOptions {
|
||||||
conversation
|
conversation
|
||||||
.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
|
.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Conversation getConversation() {
|
||||||
|
return this.conversation;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,10 @@ public class Roster {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Contact getContactAsShownInRoster(String jid) {
|
public Contact getContactFromRoster(String jid) {
|
||||||
|
if (jid == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
String cleanJid = jid.split("/", 2)[0];
|
String cleanJid = jid.split("/", 2)[0];
|
||||||
Contact contact = contacts.get(cleanJid);
|
Contact contact = contacts.get(cleanJid);
|
||||||
if (contact != null && contact.showInRoster()) {
|
if (contact != null && contact.showInRoster()) {
|
||||||
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
package eu.siacs.conversations.http;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
|
||||||
|
public class HttpConnection implements Downloadable {
|
||||||
|
|
||||||
|
private HttpConnectionManager mHttpConnectionManager;
|
||||||
|
private XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
private URL mUrl;
|
||||||
|
private Message message;
|
||||||
|
private DownloadableFile file;
|
||||||
|
private int mStatus = Downloadable.STATUS_UNKNOWN;
|
||||||
|
private boolean acceptedAutomatically = false;
|
||||||
|
|
||||||
|
public HttpConnection(HttpConnectionManager manager) {
|
||||||
|
this.mHttpConnectionManager = manager;
|
||||||
|
this.mXmppConnectionService = manager.getXmppConnectionService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean start() {
|
||||||
|
if (mXmppConnectionService.hasInternetConnection()) {
|
||||||
|
if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
|
||||||
|
checkFileSize(true);
|
||||||
|
} else {
|
||||||
|
new Thread(new FileDownloader(true)).start();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(Message message) {
|
||||||
|
this.message = message;
|
||||||
|
this.message.setDownloadable(this);
|
||||||
|
try {
|
||||||
|
mUrl = new URL(message.getBody());
|
||||||
|
String path = mUrl.getPath();
|
||||||
|
if (path != null && (path.endsWith(".pgp") || path.endsWith(".gpg"))) {
|
||||||
|
this.message.setEncryption(Message.ENCRYPTION_PGP);
|
||||||
|
} else if (message.getEncryption() != Message.ENCRYPTION_OTR) {
|
||||||
|
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||||
|
}
|
||||||
|
this.file = mXmppConnectionService.getFileBackend().getFile(
|
||||||
|
message, false);
|
||||||
|
String reference = mUrl.getRef();
|
||||||
|
if (reference != null && reference.length() == 96) {
|
||||||
|
this.file.setKey(CryptoHelper.hexToBytes(reference));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.message.getEncryption() == Message.ENCRYPTION_OTR
|
||||||
|
&& this.file.getKey() == null) {
|
||||||
|
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||||
|
}
|
||||||
|
checkFileSize(false);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFileSize(boolean interactive) {
|
||||||
|
new Thread(new FileSizeChecker(interactive)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
mHttpConnectionManager.finishConnection(this);
|
||||||
|
message.setDownloadable(null);
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finish() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||||
|
intent.setData(Uri.fromFile(file));
|
||||||
|
mXmppConnectionService.sendBroadcast(intent);
|
||||||
|
message.setDownloadable(null);
|
||||||
|
mHttpConnectionManager.finishConnection(this);
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
if (acceptedAutomatically) {
|
||||||
|
mXmppConnectionService.getNotificationService().push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeStatus(int status) {
|
||||||
|
this.mStatus = status;
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTrustManager(HttpsURLConnection connection,
|
||||||
|
boolean interactive) {
|
||||||
|
X509TrustManager trustManager;
|
||||||
|
HostnameVerifier hostnameVerifier;
|
||||||
|
if (interactive) {
|
||||||
|
trustManager = mXmppConnectionService.getMemorizingTrustManager();
|
||||||
|
hostnameVerifier = mXmppConnectionService
|
||||||
|
.getMemorizingTrustManager().wrapHostnameVerifier(
|
||||||
|
new StrictHostnameVerifier());
|
||||||
|
} else {
|
||||||
|
trustManager = mXmppConnectionService.getMemorizingTrustManager()
|
||||||
|
.getNonInteractive();
|
||||||
|
hostnameVerifier = mXmppConnectionService
|
||||||
|
.getMemorizingTrustManager()
|
||||||
|
.wrapHostnameVerifierNonInteractive(
|
||||||
|
new StrictHostnameVerifier());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
SSLContext sc = SSLContext.getInstance("TLS");
|
||||||
|
sc.init(null, new X509TrustManager[] { trustManager },
|
||||||
|
mXmppConnectionService.getRNG());
|
||||||
|
connection.setSSLSocketFactory(sc.getSocketFactory());
|
||||||
|
connection.setHostnameVerifier(hostnameVerifier);
|
||||||
|
} catch (KeyManagementException e) {
|
||||||
|
return;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileSizeChecker implements Runnable {
|
||||||
|
|
||||||
|
private boolean interactive = false;
|
||||||
|
|
||||||
|
public FileSizeChecker(boolean interactive) {
|
||||||
|
this.interactive = interactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long size;
|
||||||
|
try {
|
||||||
|
size = retrieveFileSize();
|
||||||
|
} catch (SSLHandshakeException e) {
|
||||||
|
changeStatus(STATUS_OFFER_CHECK_FILESIZE);
|
||||||
|
HttpConnection.this.acceptedAutomatically = false;
|
||||||
|
HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file.setExpectedSize(size);
|
||||||
|
if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
|
||||||
|
HttpConnection.this.acceptedAutomatically = true;
|
||||||
|
new Thread(new FileDownloader(interactive)).start();
|
||||||
|
} else {
|
||||||
|
changeStatus(STATUS_OFFER);
|
||||||
|
HttpConnection.this.acceptedAutomatically = false;
|
||||||
|
HttpConnection.this.mXmppConnectionService.getNotificationService().push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long retrieveFileSize() throws IOException,
|
||||||
|
SSLHandshakeException {
|
||||||
|
changeStatus(STATUS_CHECKING);
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) mUrl
|
||||||
|
.openConnection();
|
||||||
|
connection.setRequestMethod("HEAD");
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
setupTrustManager((HttpsURLConnection) connection, interactive);
|
||||||
|
}
|
||||||
|
connection.connect();
|
||||||
|
String contentLength = connection.getHeaderField("Content-Length");
|
||||||
|
if (contentLength == null) {
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(contentLength, 10);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileDownloader implements Runnable {
|
||||||
|
|
||||||
|
private boolean interactive = false;
|
||||||
|
|
||||||
|
public FileDownloader(boolean interactive) {
|
||||||
|
this.interactive = interactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
changeStatus(STATUS_DOWNLOADING);
|
||||||
|
download();
|
||||||
|
updateImageBounds();
|
||||||
|
finish();
|
||||||
|
} catch (SSLHandshakeException e) {
|
||||||
|
changeStatus(STATUS_OFFER);
|
||||||
|
} catch (IOException e) {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void download() throws SSLHandshakeException, IOException {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) mUrl
|
||||||
|
.openConnection();
|
||||||
|
if (connection instanceof HttpsURLConnection) {
|
||||||
|
setupTrustManager((HttpsURLConnection) connection, interactive);
|
||||||
|
}
|
||||||
|
connection.connect();
|
||||||
|
BufferedInputStream is = new BufferedInputStream(
|
||||||
|
connection.getInputStream());
|
||||||
|
OutputStream os = file.createOutputStream();
|
||||||
|
int count = -1;
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
while ((count = is.read(buffer)) != -1) {
|
||||||
|
os.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
os.flush();
|
||||||
|
os.close();
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateImageBounds() {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||||
|
int imageHeight = options.outHeight;
|
||||||
|
int imageWidth = options.outWidth;
|
||||||
|
message.setBody(mUrl.toString() + "|" + file.getSize() + '|'
|
||||||
|
+ imageWidth + '|' + imageHeight);
|
||||||
|
message.setType(Message.TYPE_IMAGE);
|
||||||
|
mXmppConnectionService.updateMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStatus() {
|
||||||
|
return this.mStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFileSize() {
|
||||||
|
if (this.file != null) {
|
||||||
|
return this.file.getExpectedSize();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package eu.siacs.conversations.http;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
|
||||||
|
public class HttpConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
|
public HttpConnectionManager(XmppConnectionService service) {
|
||||||
|
super(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<HttpConnection> connections = new CopyOnWriteArrayList<HttpConnection>();
|
||||||
|
|
||||||
|
public HttpConnection createNewConnection(Message message) {
|
||||||
|
HttpConnection connection = new HttpConnection(this);
|
||||||
|
connection.init(message);
|
||||||
|
this.connections.add(connection);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishConnection(HttpConnection connection) {
|
||||||
|
this.connections.remove(connection);
|
||||||
|
}
|
||||||
|
}
|
|
@ -256,7 +256,6 @@ public class MessageParser extends AbstractParser implements
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return finishedMessage;
|
return finishedMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,10 +347,18 @@ public class MessageParser extends AbstractParser implements
|
||||||
mXmppConnectionService.databaseBackend
|
mXmppConnectionService.databaseBackend
|
||||||
.updateAccount(account);
|
.updateAccount(account);
|
||||||
}
|
}
|
||||||
|
mXmppConnectionService.getAvatarService().clear(
|
||||||
|
account);
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
mXmppConnectionService.updateAccountUi();
|
||||||
} else {
|
} else {
|
||||||
Contact contact = account.getRoster().getContact(
|
Contact contact = account.getRoster().getContact(
|
||||||
from);
|
from);
|
||||||
contact.setAvatar(avatar.getFilename());
|
contact.setAvatar(avatar.getFilename());
|
||||||
|
mXmppConnectionService.getAvatarService().clear(
|
||||||
|
contact);
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
|
mXmppConnectionService.updateRosterUi();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mXmppConnectionService.fetchAvatar(account, avatar);
|
mXmppConnectionService.fetchAvatar(account, avatar);
|
||||||
|
@ -417,8 +424,7 @@ public class MessageParser extends AbstractParser implements
|
||||||
message = this.parseCarbonMessage(packet, account);
|
message = this.parseCarbonMessage(packet, account);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
if (message.getStatus() == Message.STATUS_SEND) {
|
if (message.getStatus() == Message.STATUS_SEND) {
|
||||||
mXmppConnectionService.getNotificationService()
|
account.activateGracePeriod();
|
||||||
.activateGracePeriod();
|
|
||||||
notify = false;
|
notify = false;
|
||||||
mXmppConnectionService.markRead(
|
mXmppConnectionService.markRead(
|
||||||
message.getConversation(), false);
|
message.getConversation(), false);
|
||||||
|
@ -440,8 +446,7 @@ public class MessageParser extends AbstractParser implements
|
||||||
} else {
|
} else {
|
||||||
mXmppConnectionService.markRead(message.getConversation(),
|
mXmppConnectionService.markRead(message.getConversation(),
|
||||||
false);
|
false);
|
||||||
mXmppConnectionService.getNotificationService()
|
account.activateGracePeriod();
|
||||||
.activateGracePeriod();
|
|
||||||
notify = false;
|
notify = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,13 +476,26 @@ public class MessageParser extends AbstractParser implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Conversation conversation = message.getConversation();
|
Conversation conversation = message.getConversation();
|
||||||
conversation.getMessages().add(message);
|
conversation.add(message);
|
||||||
|
|
||||||
|
if (message.getStatus() == Message.STATUS_RECEIVED
|
||||||
|
&& conversation.getOtrSession() != null
|
||||||
|
&& !conversation.getOtrSession().getSessionID().getUserID()
|
||||||
|
.equals(message.getPresence())) {
|
||||||
|
conversation.endOtrIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
if (packet.getType() != MessagePacket.TYPE_ERROR) {
|
if (packet.getType() != MessagePacket.TYPE_ERROR) {
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_NONE
|
if (message.getEncryption() == Message.ENCRYPTION_NONE
|
||||||
|| mXmppConnectionService.saveEncryptedMessages()) {
|
|| mXmppConnectionService.saveEncryptedMessages()) {
|
||||||
mXmppConnectionService.databaseBackend.createMessage(message);
|
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (message.trusted() && message.bodyContainsDownloadable()) {
|
||||||
|
this.mXmppConnectionService.getHttpConnectionManager()
|
||||||
|
.createNewConnection(message);
|
||||||
|
notify = false;
|
||||||
|
}
|
||||||
notify = notify && !conversation.isMuted();
|
notify = notify && !conversation.isMuted();
|
||||||
if (notify) {
|
if (notify) {
|
||||||
mXmppConnectionService.getNotificationService().push(message);
|
mXmppConnectionService.getNotificationService().push(message);
|
||||||
|
|
|
@ -29,6 +29,7 @@ public class PresenceParser extends AbstractParser implements
|
||||||
if (before != muc.getMucOptions().online()) {
|
if (before != muc.getMucOptions().online()) {
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
}
|
}
|
||||||
|
mXmppConnectionService.getAvatarService().clear(muc);
|
||||||
}
|
}
|
||||||
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
|
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
|
||||||
Conversation muc = mXmppConnectionService.find(account, packet
|
Conversation muc = mXmppConnectionService.find(account, packet
|
||||||
|
@ -39,6 +40,7 @@ public class PresenceParser extends AbstractParser implements
|
||||||
if (before != muc.getMucOptions().online()) {
|
if (before != muc.getMucOptions().online()) {
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
}
|
}
|
||||||
|
mXmppConnectionService.getAvatarService().clear(muc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,8 +60,7 @@ public class PresenceParser extends AbstractParser implements
|
||||||
Presences.parseShow(packet.findChild("show")));
|
Presences.parseShow(packet.findChild("show")));
|
||||||
} else if (type.equals("unavailable")) {
|
} else if (type.equals("unavailable")) {
|
||||||
account.removePresence(fromParts[1]);
|
account.removePresence(fromParts[1]);
|
||||||
mXmppConnectionService.getNotificationService()
|
account.deactivateGracePeriod();
|
||||||
.deactivateGracePeriod();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Roster;
|
import eu.siacs.conversations.entities.Roster;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteCantOpenDatabaseException;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
@ -151,14 +152,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CopyOnWriteArrayList<Message> getMessages(
|
public ArrayList<Message> getMessages(Conversation conversations, int limit) {
|
||||||
Conversation conversations, int limit) {
|
|
||||||
return getMessages(conversations, limit, -1);
|
return getMessages(conversations, limit, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CopyOnWriteArrayList<Message> getMessages(Conversation conversation,
|
public ArrayList<Message> getMessages(Conversation conversation, int limit,
|
||||||
int limit, long timestamp) {
|
long timestamp) {
|
||||||
CopyOnWriteArrayList<Message> list = new CopyOnWriteArrayList<Message>();
|
ArrayList<Message> list = new ArrayList<Message>();
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
Cursor cursor;
|
Cursor cursor;
|
||||||
if (timestamp == -1) {
|
if (timestamp == -1) {
|
||||||
|
@ -177,7 +177,9 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
if (cursor.getCount() > 0) {
|
if (cursor.getCount() > 0) {
|
||||||
cursor.moveToLast();
|
cursor.moveToLast();
|
||||||
do {
|
do {
|
||||||
list.add(Message.fromCursor(cursor));
|
Message message = Message.fromCursor(cursor);
|
||||||
|
message.setConversation(conversation);
|
||||||
|
list.add(message);
|
||||||
} while (cursor.moveToPrevious());
|
} while (cursor.moveToPrevious());
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
@ -231,10 +233,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
|
Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
|
||||||
+ Account.TABLENAME + " where not options & (1 <<1)", null);
|
+ Account.TABLENAME + " where not options & (1 <<1)", null);
|
||||||
cursor.moveToFirst();
|
try {
|
||||||
int count = cursor.getInt(0);
|
cursor.moveToFirst();
|
||||||
cursor.close();
|
int count = cursor.getInt(0);
|
||||||
return (count > 0);
|
cursor.close();
|
||||||
|
return (count > 0);
|
||||||
|
} catch (SQLiteCantOpenDatabaseException e) {
|
||||||
|
return true; // better safe than sorry
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -326,4 +332,22 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
return Account.fromCursor(cursor);
|
return Account.fromCursor(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Message> getImageMessages(Conversation conversation) {
|
||||||
|
ArrayList<Message> list = new ArrayList<Message>();
|
||||||
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
|
Cursor cursor;
|
||||||
|
String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) };
|
||||||
|
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||||
|
+ "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null);
|
||||||
|
if (cursor.getCount() > 0) {
|
||||||
|
cursor.moveToLast();
|
||||||
|
do {
|
||||||
|
Message message = Message.fromCursor(cursor);
|
||||||
|
message.setConversation(conversation);
|
||||||
|
list.add(message);
|
||||||
|
} while (cursor.moveToPrevious());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,89 +14,47 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.media.ExifInterface;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Base64OutputStream;
|
import android.util.Base64OutputStream;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.LruCache;
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.ImageProvider;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.ExifHelper;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleFile;
|
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
|
|
||||||
public class FileBackend {
|
public class FileBackend {
|
||||||
|
|
||||||
private static int IMAGE_SIZE = 1920;
|
private static int IMAGE_SIZE = 1920;
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private LruCache<String, Bitmap> thumbnailCache;
|
|
||||||
|
|
||||||
private SimpleDateFormat imageDateFormat = new SimpleDateFormat(
|
private SimpleDateFormat imageDateFormat = new SimpleDateFormat(
|
||||||
"yyyyMMdd_HHmmssSSS", Locale.US);
|
"yyyyMMdd_HHmmssSSS", Locale.US);
|
||||||
|
|
||||||
public FileBackend(Context context) {
|
private XmppConnectionService mXmppConnectionService;
|
||||||
this.context = context;
|
|
||||||
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
|
||||||
int cacheSize = maxMemory / 8;
|
|
||||||
thumbnailCache = new LruCache<String, Bitmap>(cacheSize) {
|
|
||||||
@Override
|
|
||||||
protected int sizeOf(String key, Bitmap bitmap) {
|
|
||||||
return bitmap.getByteCount() / 1024;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
public FileBackend(XmppConnectionService service) {
|
||||||
|
this.mXmppConnectionService = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LruCache<String, Bitmap> getThumbnailCache() {
|
public DownloadableFile getFile(Message message) {
|
||||||
return thumbnailCache;
|
return getFile(message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JingleFile getJingleFileLegacy(Message message) {
|
public DownloadableFile getFile(Message message, boolean decrypted) {
|
||||||
return getJingleFileLegacy(message, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JingleFile getJingleFileLegacy(Message message, boolean decrypted) {
|
|
||||||
Conversation conversation = message.getConversation();
|
|
||||||
String prefix = context.getFilesDir().getAbsolutePath();
|
|
||||||
String path = prefix + "/" + conversation.getAccount().getJid() + "/"
|
|
||||||
+ conversation.getContactJid();
|
|
||||||
String filename;
|
|
||||||
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
|
|
||||||
filename = message.getUuid() + ".webp";
|
|
||||||
} else {
|
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
||||||
filename = message.getUuid() + ".webp";
|
|
||||||
} else {
|
|
||||||
filename = message.getUuid() + ".webp.pgp";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new JingleFile(path + "/" + filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JingleFile getJingleFile(Message message) {
|
|
||||||
return getJingleFile(message, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JingleFile getJingleFile(Message message, boolean decrypted) {
|
|
||||||
StringBuilder filename = new StringBuilder();
|
StringBuilder filename = new StringBuilder();
|
||||||
filename.append(Environment.getExternalStoragePublicDirectory(
|
filename.append(getConversationsDirectory());
|
||||||
Environment.DIRECTORY_PICTURES).getAbsolutePath());
|
|
||||||
filename.append("/Conversations/");
|
|
||||||
filename.append(message.getUuid());
|
filename.append(message.getUuid());
|
||||||
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
|
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
|
||||||
filename.append(".webp");
|
filename.append(".webp");
|
||||||
|
@ -107,7 +65,13 @@ public class FileBackend {
|
||||||
filename.append(".webp.pgp");
|
filename.append(".webp.pgp");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new JingleFile(filename.toString());
|
return new DownloadableFile(filename.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getConversationsDirectory() {
|
||||||
|
return Environment.getExternalStoragePublicDirectory(
|
||||||
|
Environment.DIRECTORY_PICTURES).getAbsolutePath()
|
||||||
|
+ "/Conversations/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap resize(Bitmap originalBitmap, int size) {
|
public Bitmap resize(Bitmap originalBitmap, int size) {
|
||||||
|
@ -139,17 +103,17 @@ public class FileBackend {
|
||||||
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
|
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JingleFile copyImageToPrivateStorage(Message message, Uri image)
|
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
|
||||||
throws ImageCopyException {
|
throws ImageCopyException {
|
||||||
return this.copyImageToPrivateStorage(message, image, 0);
|
return this.copyImageToPrivateStorage(message, image, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JingleFile copyImageToPrivateStorage(Message message, Uri image,
|
private DownloadableFile copyImageToPrivateStorage(Message message,
|
||||||
int sampleSize) throws ImageCopyException {
|
Uri image, int sampleSize) throws ImageCopyException {
|
||||||
try {
|
try {
|
||||||
InputStream is = context.getContentResolver()
|
InputStream is = mXmppConnectionService.getContentResolver()
|
||||||
.openInputStream(image);
|
.openInputStream(image);
|
||||||
JingleFile file = getJingleFile(message);
|
DownloadableFile file = getFile(message);
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
Bitmap originalBitmap;
|
Bitmap originalBitmap;
|
||||||
|
@ -202,7 +166,7 @@ public class FileBackend {
|
||||||
private int getRotation(Uri image) {
|
private int getRotation(Uri image) {
|
||||||
if ("content".equals(image.getScheme())) {
|
if ("content".equals(image.getScheme())) {
|
||||||
try {
|
try {
|
||||||
Cursor cursor = context
|
Cursor cursor = mXmppConnectionService
|
||||||
.getContentResolver()
|
.getContentResolver()
|
||||||
.query(image,
|
.query(image,
|
||||||
new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
|
new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
|
||||||
|
@ -216,40 +180,26 @@ public class FileBackend {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ExifInterface exif;
|
|
||||||
try {
|
try {
|
||||||
exif = new ExifInterface(image.toString());
|
InputStream is = mXmppConnectionService.getContentResolver()
|
||||||
if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
|
.openInputStream(image);
|
||||||
.equalsIgnoreCase("6")) {
|
return ExifHelper.getOrientation(is);
|
||||||
return 90;
|
} catch (FileNotFoundException e) {
|
||||||
} else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
|
return 0;
|
||||||
.equalsIgnoreCase("8")) {
|
|
||||||
return 270;
|
|
||||||
} else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
|
|
||||||
.equalsIgnoreCase("3")) {
|
|
||||||
return 180;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap getImageFromMessage(Message message) {
|
public Bitmap getImageFromMessage(Message message) {
|
||||||
return BitmapFactory.decodeFile(getJingleFile(message)
|
return BitmapFactory.decodeFile(getFile(message).getAbsolutePath());
|
||||||
.getAbsolutePath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
|
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
Bitmap thumbnail = thumbnailCache.get(message.getUuid());
|
Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(
|
||||||
|
message.getUuid());
|
||||||
if ((thumbnail == null) && (!cacheOnly)) {
|
if ((thumbnail == null) && (!cacheOnly)) {
|
||||||
File file = getJingleFile(message);
|
File file = getFile(message);
|
||||||
if (!file.exists()) {
|
|
||||||
file = getJingleFileLegacy(message);
|
|
||||||
}
|
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inSampleSize = calcSampleSize(file, size);
|
options.inSampleSize = calcSampleSize(file, size);
|
||||||
Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),
|
Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),
|
||||||
|
@ -258,32 +208,12 @@ public class FileBackend {
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
thumbnail = resize(fullsize, size);
|
thumbnail = resize(fullsize, size);
|
||||||
this.thumbnailCache.put(message.getUuid(), thumbnail);
|
this.mXmppConnectionService.getBitmapCache().put(message.getUuid(),
|
||||||
|
thumbnail);
|
||||||
}
|
}
|
||||||
return thumbnail;
|
return thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeFiles(Conversation conversation) {
|
|
||||||
String prefix = context.getFilesDir().getAbsolutePath();
|
|
||||||
String path = prefix + "/" + conversation.getAccount().getJid() + "/"
|
|
||||||
+ conversation.getContactJid();
|
|
||||||
File file = new File(path);
|
|
||||||
try {
|
|
||||||
this.deleteFile(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.d(Config.LOGTAG,
|
|
||||||
"error deleting file: " + file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteFile(File f) throws IOException {
|
|
||||||
if (f.isDirectory()) {
|
|
||||||
for (File c : f.listFiles())
|
|
||||||
deleteFile(c);
|
|
||||||
}
|
|
||||||
f.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri getTakePhotoUri() {
|
public Uri getTakePhotoUri() {
|
||||||
StringBuilder pathBuilder = new StringBuilder();
|
StringBuilder pathBuilder = new StringBuilder();
|
||||||
pathBuilder.append(Environment
|
pathBuilder.append(Environment
|
||||||
|
@ -328,7 +258,7 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAvatarCached(Avatar avatar) {
|
public boolean isAvatarCached(Avatar avatar) {
|
||||||
File file = new File(getAvatarPath(context, avatar.getFilename()));
|
File file = new File(getAvatarPath(avatar.getFilename()));
|
||||||
return file.exists();
|
return file.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +266,7 @@ public class FileBackend {
|
||||||
if (isAvatarCached(avatar)) {
|
if (isAvatarCached(avatar)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String filename = getAvatarPath(context, avatar.getFilename());
|
String filename = getAvatarPath(avatar.getFilename());
|
||||||
File file = new File(filename + ".tmp");
|
File file = new File(filename + ".tmp");
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
try {
|
try {
|
||||||
|
@ -368,15 +298,20 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAvatarPath(Context context, String avatar) {
|
public String getAvatarPath(String avatar) {
|
||||||
return context.getFilesDir().getAbsolutePath() + "/avatars/" + avatar;
|
return mXmppConnectionService.getFilesDir().getAbsolutePath()
|
||||||
|
+ "/avatars/" + avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getAvatarUri(String avatar) {
|
||||||
|
return Uri.parse("file:" + getAvatarPath(avatar));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap cropCenterSquare(Uri image, int size) {
|
public Bitmap cropCenterSquare(Uri image, int size) {
|
||||||
try {
|
try {
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inSampleSize = calcSampleSize(image, size);
|
options.inSampleSize = calcSampleSize(image, size);
|
||||||
InputStream is = context.getContentResolver()
|
InputStream is = mXmppConnectionService.getContentResolver()
|
||||||
.openInputStream(image);
|
.openInputStream(image);
|
||||||
Bitmap input = BitmapFactory.decodeStream(is, null, options);
|
Bitmap input = BitmapFactory.decodeStream(is, null, options);
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
|
@ -393,7 +328,40 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap cropCenterSquare(Bitmap input, int size) {
|
public Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
|
||||||
|
try {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inSampleSize = calcSampleSize(image,
|
||||||
|
Math.max(newHeight, newWidth));
|
||||||
|
InputStream is = mXmppConnectionService.getContentResolver()
|
||||||
|
.openInputStream(image);
|
||||||
|
Bitmap source = BitmapFactory.decodeStream(is, null, options);
|
||||||
|
|
||||||
|
int sourceWidth = source.getWidth();
|
||||||
|
int sourceHeight = source.getHeight();
|
||||||
|
float xScale = (float) newWidth / sourceWidth;
|
||||||
|
float yScale = (float) newHeight / sourceHeight;
|
||||||
|
float scale = Math.max(xScale, yScale);
|
||||||
|
float scaledWidth = scale * sourceWidth;
|
||||||
|
float scaledHeight = scale * sourceHeight;
|
||||||
|
float left = (newWidth - scaledWidth) / 2;
|
||||||
|
float top = (newHeight - scaledHeight) / 2;
|
||||||
|
|
||||||
|
RectF targetRect = new RectF(left, top, left + scaledWidth, top
|
||||||
|
+ scaledHeight);
|
||||||
|
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight,
|
||||||
|
source.getConfig());
|
||||||
|
Canvas canvas = new Canvas(dest);
|
||||||
|
canvas.drawBitmap(source, null, targetRect, null);
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap cropCenterSquare(Bitmap input, int size) {
|
||||||
int w = input.getWidth();
|
int w = input.getWidth();
|
||||||
int h = input.getHeight();
|
int h = input.getHeight();
|
||||||
|
|
||||||
|
@ -415,7 +383,7 @@ public class FileBackend {
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeStream(context.getContentResolver()
|
BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver()
|
||||||
.openInputStream(image), null, options);
|
.openInputStream(image), null, options);
|
||||||
return calcSampleSize(options, size);
|
return calcSampleSize(options, size);
|
||||||
}
|
}
|
||||||
|
@ -445,12 +413,8 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getJingleFileUri(Message message) {
|
public Uri getJingleFileUri(Message message) {
|
||||||
File file = getJingleFile(message);
|
File file = getFile(message);
|
||||||
if (file.exists()) {
|
return Uri.parse("file://" + file.getAbsolutePath());
|
||||||
return Uri.parse("file://" + file.getAbsolutePath());
|
|
||||||
} else {
|
|
||||||
return ImageProvider.getProviderUri(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImageCopyException extends Exception {
|
public class ImageCopyException extends Exception {
|
||||||
|
@ -466,12 +430,18 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap getAvatar(String avatar, int size, Context context) {
|
public Bitmap getAvatar(String avatar, int size) {
|
||||||
Bitmap bm = BitmapFactory.decodeFile(FileBackend.getAvatarPath(context,
|
if (avatar == null) {
|
||||||
avatar));
|
return null;
|
||||||
|
}
|
||||||
|
Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
|
||||||
if (bm == null) {
|
if (bm == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return cropCenterSquare(bm, UIHelper.getRealPx(size, context));
|
return bm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFileAvailable(Message message) {
|
||||||
|
return getFile(message).exists();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
public class AbstractConnectionManager {
|
||||||
|
protected XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
public AbstractConnectionManager(XmppConnectionService service) {
|
||||||
|
this.mXmppConnectionService = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmppConnectionService getXmppConnectionService() {
|
||||||
|
return this.mXmppConnectionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAutoAcceptFileSize() {
|
||||||
|
String config = this.mXmppConnectionService.getPreferences().getString(
|
||||||
|
"auto_accept_file_size", "524288");
|
||||||
|
try {
|
||||||
|
return Long.parseLong(config);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 524288;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,292 @@
|
||||||
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.entities.Bookmark;
|
||||||
|
import eu.siacs.conversations.entities.Contact;
|
||||||
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.ListItem;
|
||||||
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
public class AvatarService {
|
||||||
|
|
||||||
|
private static final int FG_COLOR = 0xFFFAFAFA;
|
||||||
|
private static final int TRANSPARENT = 0x00000000;
|
||||||
|
|
||||||
|
private static final String PREFIX_CONTACT = "contact";
|
||||||
|
private static final String PREFIX_CONVERSATION = "conversation";
|
||||||
|
private static final String PREFIX_ACCOUNT = "account";
|
||||||
|
private static final String PREFIX_GENERIC = "generic";
|
||||||
|
|
||||||
|
private ArrayList<Integer> sizes = new ArrayList<Integer>();
|
||||||
|
|
||||||
|
protected XmppConnectionService mXmppConnectionService = null;
|
||||||
|
|
||||||
|
public AvatarService(XmppConnectionService service) {
|
||||||
|
this.mXmppConnectionService = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap get(Contact contact, int size) {
|
||||||
|
final String KEY = key(contact, size);
|
||||||
|
Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
|
||||||
|
if (avatar != null) {
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
avatar = mXmppConnectionService.getFileBackend().getAvatar(
|
||||||
|
contact.getAvatar(), size);
|
||||||
|
if (avatar == null) {
|
||||||
|
if (contact.getProfilePhoto() != null) {
|
||||||
|
avatar = mXmppConnectionService.getFileBackend()
|
||||||
|
.cropCenterSquare(Uri.parse(contact.getProfilePhoto()),
|
||||||
|
size);
|
||||||
|
if (avatar == null) {
|
||||||
|
avatar = get(contact.getDisplayName(), size);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
avatar = get(contact.getDisplayName(), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(Contact contact) {
|
||||||
|
for (Integer size : sizes) {
|
||||||
|
this.mXmppConnectionService.getBitmapCache().remove(
|
||||||
|
key(contact, size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String key(Contact contact, int size) {
|
||||||
|
synchronized (this.sizes) {
|
||||||
|
if (!this.sizes.contains(size)) {
|
||||||
|
this.sizes.add(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PREFIX_CONTACT + "_" + contact.getAccount().getJid() + "_"
|
||||||
|
+ contact.getJid() + "_" + String.valueOf(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap get(ListItem item, int size) {
|
||||||
|
if (item instanceof Contact) {
|
||||||
|
return get((Contact) item, size);
|
||||||
|
} else if (item instanceof Bookmark) {
|
||||||
|
Bookmark bookmark = (Bookmark) item;
|
||||||
|
if (bookmark.getConversation() != null) {
|
||||||
|
return get(bookmark.getConversation(), size);
|
||||||
|
} else {
|
||||||
|
return get(bookmark.getDisplayName(), size);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return get(item.getDisplayName(), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap get(Conversation conversation, int size) {
|
||||||
|
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||||
|
return get(conversation.getContact(), size);
|
||||||
|
} else {
|
||||||
|
return get(conversation.getMucOptions(), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(Conversation conversation) {
|
||||||
|
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||||
|
clear(conversation.getContact());
|
||||||
|
} else {
|
||||||
|
clear(conversation.getMucOptions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap get(MucOptions mucOptions, int size) {
|
||||||
|
final String KEY = key(mucOptions, size);
|
||||||
|
Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
|
||||||
|
if (bitmap != null) {
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
List<MucOptions.User> users = mucOptions.getUsers();
|
||||||
|
int count = users.size();
|
||||||
|
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
bitmap.eraseColor(TRANSPARENT);
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
String name = mucOptions.getConversation().getName();
|
||||||
|
String letter = name.substring(0, 1);
|
||||||
|
int color = this.getColorForName(name);
|
||||||
|
drawTile(canvas, letter, color, 0, 0, size, size);
|
||||||
|
} else if (count == 1) {
|
||||||
|
drawTile(canvas, users.get(0), 0, 0, size, size);
|
||||||
|
} else if (count == 2) {
|
||||||
|
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
|
||||||
|
drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size);
|
||||||
|
} else if (count == 3) {
|
||||||
|
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
|
||||||
|
drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1);
|
||||||
|
drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size,
|
||||||
|
size);
|
||||||
|
} else if (count == 4) {
|
||||||
|
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1);
|
||||||
|
drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size);
|
||||||
|
drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1);
|
||||||
|
drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size,
|
||||||
|
size);
|
||||||
|
} else {
|
||||||
|
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1);
|
||||||
|
drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size);
|
||||||
|
drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1);
|
||||||
|
drawTile(canvas, "\u2026", 0xFF202020, size / 2 + 1, size / 2 + 1,
|
||||||
|
size, size);
|
||||||
|
}
|
||||||
|
this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(MucOptions options) {
|
||||||
|
for (Integer size : sizes) {
|
||||||
|
this.mXmppConnectionService.getBitmapCache().remove(
|
||||||
|
key(options, size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String key(MucOptions options, int size) {
|
||||||
|
synchronized (this.sizes) {
|
||||||
|
if (!this.sizes.contains(size)) {
|
||||||
|
this.sizes.add(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid()
|
||||||
|
+ "_" + String.valueOf(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap get(Account account, int size) {
|
||||||
|
final String KEY = key(account, size);
|
||||||
|
Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY);
|
||||||
|
if (avatar != null) {
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
avatar = mXmppConnectionService.getFileBackend().getAvatar(
|
||||||
|
account.getAvatar(), size);
|
||||||
|
if (avatar == null) {
|
||||||
|
avatar = get(account.getJid(), size);
|
||||||
|
}
|
||||||
|
mXmppConnectionService.getBitmapCache().put(KEY, avatar);
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(Account account) {
|
||||||
|
for (Integer size : sizes) {
|
||||||
|
this.mXmppConnectionService.getBitmapCache().remove(
|
||||||
|
key(account, size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String key(Account account, int size) {
|
||||||
|
synchronized (this.sizes) {
|
||||||
|
if (!this.sizes.contains(size)) {
|
||||||
|
this.sizes.add(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PREFIX_ACCOUNT + "_" + account.getUuid() + "_"
|
||||||
|
+ String.valueOf(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap get(String name, int size) {
|
||||||
|
final String KEY = key(name, size);
|
||||||
|
Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
|
||||||
|
if (bitmap != null) {
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
String letter = name.substring(0, 1);
|
||||||
|
int color = this.getColorForName(name);
|
||||||
|
drawTile(canvas, letter, color, 0, 0, size, size);
|
||||||
|
mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String key(String name, int size) {
|
||||||
|
synchronized (this.sizes) {
|
||||||
|
if (!this.sizes.contains(size)) {
|
||||||
|
this.sizes.add(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTile(Canvas canvas, String letter, int tileColor,
|
||||||
|
int left, int top, int right, int bottom) {
|
||||||
|
letter = letter.toUpperCase(Locale.getDefault());
|
||||||
|
Paint tilePaint = new Paint(), textPaint = new Paint();
|
||||||
|
tilePaint.setColor(tileColor);
|
||||||
|
textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
textPaint.setColor(FG_COLOR);
|
||||||
|
textPaint.setTypeface(Typeface.create("sans-serif-light",
|
||||||
|
Typeface.NORMAL));
|
||||||
|
textPaint.setTextSize((float) ((right - left) * 0.8));
|
||||||
|
Rect rect = new Rect();
|
||||||
|
|
||||||
|
canvas.drawRect(new Rect(left, top, right, bottom), tilePaint);
|
||||||
|
textPaint.getTextBounds(letter, 0, 1, rect);
|
||||||
|
float width = textPaint.measureText(letter);
|
||||||
|
canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom)
|
||||||
|
/ 2 + rect.height() / 2, textPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTile(Canvas canvas, MucOptions.User user, int left,
|
||||||
|
int top, int right, int bottom) {
|
||||||
|
Contact contact = user.getContact();
|
||||||
|
if (contact != null) {
|
||||||
|
Uri uri = null;
|
||||||
|
if (contact.getAvatar() != null) {
|
||||||
|
uri = mXmppConnectionService.getFileBackend().getAvatarUri(
|
||||||
|
contact.getAvatar());
|
||||||
|
} else if (contact.getProfilePhoto() != null) {
|
||||||
|
uri = Uri.parse(contact.getProfilePhoto());
|
||||||
|
}
|
||||||
|
if (uri != null) {
|
||||||
|
Bitmap bitmap = mXmppConnectionService.getFileBackend()
|
||||||
|
.cropCenter(uri, bottom - top, right - left);
|
||||||
|
if (bitmap != null) {
|
||||||
|
drawTile(canvas, bitmap, left, top, right, bottom);
|
||||||
|
} else {
|
||||||
|
String letter = user.getName().substring(0, 1);
|
||||||
|
int color = this.getColorForName(user.getName());
|
||||||
|
drawTile(canvas, letter, color, left, top, right, bottom);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String letter = user.getName().substring(0, 1);
|
||||||
|
int color = this.getColorForName(user.getName());
|
||||||
|
drawTile(canvas, letter, color, left, top, right, bottom);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String letter = user.getName().substring(0, 1);
|
||||||
|
int color = this.getColorForName(user.getName());
|
||||||
|
drawTile(canvas, letter, color, left, top, right, bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop,
|
||||||
|
int dstright, int dstbottom) {
|
||||||
|
Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom);
|
||||||
|
canvas.drawBitmap(bm, null, dst, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getColorForName(String name) {
|
||||||
|
int holoColors[] = { 0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5,
|
||||||
|
0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722,
|
||||||
|
0xFF795548, 0xFF607d8b };
|
||||||
|
return holoColors[(int) ((name.hashCode() & 0xffffffffl) % holoColors.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,109 +0,0 @@
|
||||||
package eu.siacs.conversations.services;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
|
||||||
import eu.siacs.conversations.entities.Message;
|
|
||||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
|
||||||
import android.content.ContentProvider;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class ImageProvider extends ContentProvider {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ParcelFileDescriptor openFile(Uri uri, String mode)
|
|
||||||
throws FileNotFoundException {
|
|
||||||
ParcelFileDescriptor pfd;
|
|
||||||
FileBackend fileBackend = new FileBackend(getContext());
|
|
||||||
if ("r".equals(mode)) {
|
|
||||||
DatabaseBackend databaseBackend = DatabaseBackend
|
|
||||||
.getInstance(getContext());
|
|
||||||
String uuids = uri.getPath();
|
|
||||||
Log.d(Config.LOGTAG, "uuids = " + uuids + " mode=" + mode);
|
|
||||||
if (uuids == null) {
|
|
||||||
throw new FileNotFoundException();
|
|
||||||
}
|
|
||||||
String[] uuidsSplited = uuids.split("/", 2);
|
|
||||||
if (uuidsSplited.length != 3) {
|
|
||||||
throw new FileNotFoundException();
|
|
||||||
}
|
|
||||||
String conversationUuid = uuidsSplited[1];
|
|
||||||
String messageUuid = uuidsSplited[2].split("\\.")[0];
|
|
||||||
|
|
||||||
Log.d(Config.LOGTAG, "messageUuid=" + messageUuid);
|
|
||||||
|
|
||||||
Conversation conversation = databaseBackend
|
|
||||||
.findConversationByUuid(conversationUuid);
|
|
||||||
if (conversation == null) {
|
|
||||||
throw new FileNotFoundException("conversation "
|
|
||||||
+ conversationUuid + " could not be found");
|
|
||||||
}
|
|
||||||
Message message = databaseBackend.findMessageByUuid(messageUuid);
|
|
||||||
if (message == null) {
|
|
||||||
throw new FileNotFoundException("message " + messageUuid
|
|
||||||
+ " could not be found");
|
|
||||||
}
|
|
||||||
|
|
||||||
Account account = databaseBackend.findAccountByUuid(conversation
|
|
||||||
.getAccountUuid());
|
|
||||||
if (account == null) {
|
|
||||||
throw new FileNotFoundException("account "
|
|
||||||
+ conversation.getAccountUuid() + " cound not be found");
|
|
||||||
}
|
|
||||||
message.setConversation(conversation);
|
|
||||||
conversation.setAccount(account);
|
|
||||||
|
|
||||||
File file = fileBackend.getJingleFileLegacy(message);
|
|
||||||
pfd = ParcelFileDescriptor.open(file,
|
|
||||||
ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
return pfd;
|
|
||||||
} else {
|
|
||||||
throw new FileNotFoundException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int delete(Uri arg0, String arg1, String[] arg2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getType(Uri arg0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri insert(Uri arg0, ContentValues arg1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreate() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
|
|
||||||
String arg4) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getProviderUri(Message message) {
|
|
||||||
return Uri.parse("content://eu.siacs.conversations.images/"
|
|
||||||
+ message.getConversationUuid() + "/" + message.getUuid()
|
|
||||||
+ ".webp");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -11,70 +12,83 @@ import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.NotificationCompat.BigPictureStyle;
|
||||||
|
import android.support.v4.app.NotificationCompat.Builder;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
import eu.siacs.conversations.ui.ConversationActivity;
|
||||||
|
|
||||||
public class NotificationService {
|
public class NotificationService {
|
||||||
|
|
||||||
private XmppConnectionService mXmppConnectionService;
|
private XmppConnectionService mXmppConnectionService;
|
||||||
private NotificationManager mNotificationManager;
|
|
||||||
|
|
||||||
private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>();
|
private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>();
|
||||||
|
|
||||||
public int NOTIFICATION_ID = 0x2342;
|
public static int NOTIFICATION_ID = 0x2342;
|
||||||
private Conversation mOpenConversation;
|
private Conversation mOpenConversation;
|
||||||
private boolean mIsInForeground;
|
private boolean mIsInForeground;
|
||||||
|
private long mLastNotification;
|
||||||
private long mEndGracePeriod = 0L;
|
|
||||||
|
|
||||||
public NotificationService(XmppConnectionService service) {
|
public NotificationService(XmppConnectionService service) {
|
||||||
this.mXmppConnectionService = service;
|
this.mXmppConnectionService = service;
|
||||||
this.mNotificationManager = (NotificationManager) service
|
|
||||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void push(Message message) {
|
public void push(Message message) {
|
||||||
|
|
||||||
PowerManager pm = (PowerManager) mXmppConnectionService
|
PowerManager pm = (PowerManager) mXmppConnectionService
|
||||||
.getSystemService(Context.POWER_SERVICE);
|
.getSystemService(Context.POWER_SERVICE);
|
||||||
boolean isScreenOn = pm.isScreenOn();
|
boolean isScreenOn = pm.isScreenOn();
|
||||||
|
|
||||||
if (this.mIsInForeground && isScreenOn
|
if (this.mIsInForeground && isScreenOn
|
||||||
&& this.mOpenConversation == message.getConversation()) {
|
&& this.mOpenConversation == message.getConversation()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String conversationUuid = message.getConversationUuid();
|
synchronized (notifications) {
|
||||||
if (notifications.containsKey(conversationUuid)) {
|
String conversationUuid = message.getConversationUuid();
|
||||||
notifications.get(conversationUuid).add(message);
|
if (notifications.containsKey(conversationUuid)) {
|
||||||
} else {
|
notifications.get(conversationUuid).add(message);
|
||||||
ArrayList<Message> mList = new ArrayList<Message>();
|
} else {
|
||||||
mList.add(message);
|
ArrayList<Message> mList = new ArrayList<Message>();
|
||||||
notifications.put(conversationUuid, mList);
|
mList.add(message);
|
||||||
|
notifications.put(conversationUuid, mList);
|
||||||
|
}
|
||||||
|
Account account = message.getConversation().getAccount();
|
||||||
|
updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
|
||||||
|
&& !account.inGracePeriod()
|
||||||
|
&& !this.inMiniGracePeriod(account));
|
||||||
}
|
}
|
||||||
updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
|
|
||||||
&& !inGracePeriod());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
notifications.clear();
|
synchronized (notifications) {
|
||||||
updateNotification(false);
|
notifications.clear();
|
||||||
|
updateNotification(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear(Conversation conversation) {
|
public void clear(Conversation conversation) {
|
||||||
notifications.remove(conversation.getUuid());
|
synchronized (notifications) {
|
||||||
updateNotification(false);
|
notifications.remove(conversation.getUuid());
|
||||||
|
updateNotification(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateNotification(boolean notify) {
|
private void updateNotification(boolean notify) {
|
||||||
|
NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
|
||||||
|
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
SharedPreferences preferences = mXmppConnectionService.getPreferences();
|
SharedPreferences preferences = mXmppConnectionService.getPreferences();
|
||||||
|
|
||||||
String ringtone = preferences.getString("notification_ringtone", null);
|
String ringtone = preferences.getString("notification_ringtone", null);
|
||||||
|
@ -82,76 +96,16 @@ public class NotificationService {
|
||||||
true);
|
true);
|
||||||
|
|
||||||
if (notifications.size() == 0) {
|
if (notifications.size() == 0) {
|
||||||
mNotificationManager.cancel(NOTIFICATION_ID);
|
notificationManager.cancel(NOTIFICATION_ID);
|
||||||
} else {
|
} else {
|
||||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
|
if (notify) {
|
||||||
mXmppConnectionService);
|
this.markLastNotification();
|
||||||
mBuilder.setSmallIcon(R.drawable.ic_notification);
|
}
|
||||||
|
Builder mBuilder;
|
||||||
if (notifications.size() == 1) {
|
if (notifications.size() == 1) {
|
||||||
ArrayList<Message> messages = notifications.values().iterator()
|
mBuilder = buildSingleConversations(notify);
|
||||||
.next();
|
|
||||||
if (messages.size() >= 1) {
|
|
||||||
Conversation conversation = messages.get(0)
|
|
||||||
.getConversation();
|
|
||||||
mBuilder.setLargeIcon(conversation.getImage(
|
|
||||||
mXmppConnectionService, 64));
|
|
||||||
mBuilder.setContentTitle(conversation.getName());
|
|
||||||
StringBuilder text = new StringBuilder();
|
|
||||||
for (int i = 0; i < messages.size(); ++i) {
|
|
||||||
text.append(messages.get(i).getReadableBody(
|
|
||||||
mXmppConnectionService));
|
|
||||||
if (i != messages.size() - 1) {
|
|
||||||
text.append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mBuilder.setStyle(new NotificationCompat.BigTextStyle()
|
|
||||||
.bigText(text.toString()));
|
|
||||||
mBuilder.setContentText(messages.get(0).getReadableBody(
|
|
||||||
mXmppConnectionService));
|
|
||||||
if (notify) {
|
|
||||||
mBuilder.setTicker(messages.get(messages.size() - 1)
|
|
||||||
.getReadableBody(mXmppConnectionService));
|
|
||||||
}
|
|
||||||
mBuilder.setContentIntent(createContentIntent(conversation
|
|
||||||
.getUuid()));
|
|
||||||
} else {
|
|
||||||
mNotificationManager.cancel(NOTIFICATION_ID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
|
mBuilder = buildMultipleConversation();
|
||||||
style.setBigContentTitle(notifications.size()
|
|
||||||
+ " "
|
|
||||||
+ mXmppConnectionService
|
|
||||||
.getString(R.string.unread_conversations));
|
|
||||||
StringBuilder names = new StringBuilder();
|
|
||||||
Conversation conversation = null;
|
|
||||||
for (ArrayList<Message> messages : notifications.values()) {
|
|
||||||
if (messages.size() > 0) {
|
|
||||||
conversation = messages.get(0).getConversation();
|
|
||||||
String name = conversation.getName();
|
|
||||||
style.addLine(Html.fromHtml("<b>"
|
|
||||||
+ name
|
|
||||||
+ "</b> "
|
|
||||||
+ messages.get(0).getReadableBody(
|
|
||||||
mXmppConnectionService)));
|
|
||||||
names.append(name);
|
|
||||||
names.append(", ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (names.length() >= 2) {
|
|
||||||
names.delete(names.length() - 2, names.length());
|
|
||||||
}
|
|
||||||
mBuilder.setContentTitle(notifications.size()
|
|
||||||
+ " "
|
|
||||||
+ mXmppConnectionService
|
|
||||||
.getString(R.string.unread_conversations));
|
|
||||||
mBuilder.setContentText(names.toString());
|
|
||||||
mBuilder.setStyle(style);
|
|
||||||
if (conversation != null) {
|
|
||||||
mBuilder.setContentIntent(createContentIntent(conversation
|
|
||||||
.getUuid()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (notify) {
|
if (notify) {
|
||||||
if (vibrate) {
|
if (vibrate) {
|
||||||
|
@ -163,12 +117,147 @@ public class NotificationService {
|
||||||
mBuilder.setSound(Uri.parse(ringtone));
|
mBuilder.setSound(Uri.parse(ringtone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mBuilder.setSmallIcon(R.drawable.ic_notification);
|
||||||
mBuilder.setDeleteIntent(createDeleteIntent());
|
mBuilder.setDeleteIntent(createDeleteIntent());
|
||||||
if (!inGracePeriod()) {
|
mBuilder.setLights(0xffffffff, 2000, 4000);
|
||||||
mBuilder.setLights(0xffffffff, 2000, 4000);
|
|
||||||
}
|
|
||||||
Notification notification = mBuilder.build();
|
Notification notification = mBuilder.build();
|
||||||
mNotificationManager.notify(NOTIFICATION_ID, notification);
|
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder buildMultipleConversation() {
|
||||||
|
Builder mBuilder = new NotificationCompat.Builder(
|
||||||
|
mXmppConnectionService);
|
||||||
|
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
|
||||||
|
style.setBigContentTitle(notifications.size()
|
||||||
|
+ " "
|
||||||
|
+ mXmppConnectionService
|
||||||
|
.getString(R.string.unread_conversations));
|
||||||
|
StringBuilder names = new StringBuilder();
|
||||||
|
Conversation conversation = null;
|
||||||
|
for (ArrayList<Message> messages : notifications.values()) {
|
||||||
|
if (messages.size() > 0) {
|
||||||
|
conversation = messages.get(0).getConversation();
|
||||||
|
String name = conversation.getName();
|
||||||
|
style.addLine(Html.fromHtml("<b>" + name + "</b> "
|
||||||
|
+ getReadableBody(messages.get(0))));
|
||||||
|
names.append(name);
|
||||||
|
names.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (names.length() >= 2) {
|
||||||
|
names.delete(names.length() - 2, names.length());
|
||||||
|
}
|
||||||
|
mBuilder.setContentTitle(notifications.size()
|
||||||
|
+ " "
|
||||||
|
+ mXmppConnectionService
|
||||||
|
.getString(R.string.unread_conversations));
|
||||||
|
mBuilder.setContentText(names.toString());
|
||||||
|
mBuilder.setStyle(style);
|
||||||
|
if (conversation != null) {
|
||||||
|
mBuilder.setContentIntent(createContentIntent(conversation
|
||||||
|
.getUuid()));
|
||||||
|
}
|
||||||
|
return mBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Builder buildSingleConversations(boolean notify) {
|
||||||
|
Builder mBuilder = new NotificationCompat.Builder(
|
||||||
|
mXmppConnectionService);
|
||||||
|
ArrayList<Message> messages = notifications.values().iterator().next();
|
||||||
|
if (messages.size() >= 1) {
|
||||||
|
Conversation conversation = messages.get(0).getConversation();
|
||||||
|
mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
|
||||||
|
.get(conversation, getPixel(64)));
|
||||||
|
mBuilder.setContentTitle(conversation.getName());
|
||||||
|
Message message;
|
||||||
|
if ((message = getImage(messages)) != null) {
|
||||||
|
modifyForImage(mBuilder, message, messages, notify);
|
||||||
|
} else {
|
||||||
|
modifyForTextOnly(mBuilder, messages, notify);
|
||||||
|
}
|
||||||
|
mBuilder.setContentIntent(createContentIntent(conversation
|
||||||
|
.getUuid()));
|
||||||
|
}
|
||||||
|
return mBuilder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void modifyForImage(Builder builder, Message message,
|
||||||
|
ArrayList<Message> messages, boolean notify) {
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = mXmppConnectionService.getFileBackend()
|
||||||
|
.getThumbnail(message, getPixel(288), false);
|
||||||
|
ArrayList<Message> tmp = new ArrayList<Message>();
|
||||||
|
for (Message msg : messages) {
|
||||||
|
if (msg.getType() == Message.TYPE_TEXT
|
||||||
|
&& msg.getDownloadable() == null) {
|
||||||
|
tmp.add(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
|
||||||
|
bigPictureStyle.bigPicture(bitmap);
|
||||||
|
if (tmp.size() > 0) {
|
||||||
|
bigPictureStyle.setSummaryText(getMergedBodies(tmp));
|
||||||
|
builder.setContentText(getReadableBody(tmp.get(0)));
|
||||||
|
} else {
|
||||||
|
builder.setContentText(mXmppConnectionService.getString(R.string.image_file));
|
||||||
|
}
|
||||||
|
builder.setStyle(bigPictureStyle);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
modifyForTextOnly(builder, messages, notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void modifyForTextOnly(Builder builder,
|
||||||
|
ArrayList<Message> messages, boolean notify) {
|
||||||
|
builder.setStyle(new NotificationCompat.BigTextStyle()
|
||||||
|
.bigText(getMergedBodies(messages)));
|
||||||
|
builder.setContentText(getReadableBody(messages.get(0)));
|
||||||
|
if (notify) {
|
||||||
|
builder.setTicker(getReadableBody(messages.get(messages.size() - 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message getImage(ArrayList<Message> messages) {
|
||||||
|
for (Message message : messages) {
|
||||||
|
if (message.getType() == Message.TYPE_IMAGE
|
||||||
|
&& message.getDownloadable() == null
|
||||||
|
&& message.getEncryption() != Message.ENCRYPTION_PGP) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMergedBodies(ArrayList<Message> messages) {
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
for (int i = 0; i < messages.size(); ++i) {
|
||||||
|
text.append(getReadableBody(messages.get(i)));
|
||||||
|
if (i != messages.size() - 1) {
|
||||||
|
text.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getReadableBody(Message message) {
|
||||||
|
if (message.getDownloadable() != null
|
||||||
|
&& (message.getDownloadable().getStatus() == Downloadable.STATUS_OFFER || message
|
||||||
|
.getDownloadable().getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE)) {
|
||||||
|
return mXmppConnectionService.getText(
|
||||||
|
R.string.image_offered_for_download).toString();
|
||||||
|
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
|
return mXmppConnectionService.getText(
|
||||||
|
R.string.encrypted_message_received).toString();
|
||||||
|
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
|
return mXmppConnectionService.getText(R.string.decryption_failed)
|
||||||
|
.toString();
|
||||||
|
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||||
|
return mXmppConnectionService.getText(R.string.image_file)
|
||||||
|
.toString();
|
||||||
|
} else {
|
||||||
|
return message.getBody().trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,16 +315,19 @@ public class NotificationService {
|
||||||
this.mIsInForeground = foreground;
|
this.mIsInForeground = foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activateGracePeriod() {
|
private int getPixel(int dp) {
|
||||||
this.mEndGracePeriod = SystemClock.elapsedRealtime()
|
DisplayMetrics metrics = mXmppConnectionService.getResources()
|
||||||
+ (Config.CARBON_GRACE_PERIOD * 1000);
|
.getDisplayMetrics();
|
||||||
|
return ((int) (dp * metrics.density));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deactivateGracePeriod() {
|
private void markLastNotification() {
|
||||||
this.mEndGracePeriod = 0L;
|
this.mLastNotification = SystemClock.elapsedRealtime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean inGracePeriod() {
|
private boolean inMiniGracePeriod(Account account) {
|
||||||
return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
|
int miniGrace = account.getStatus() == Account.STATUS_ONLINE ? Config.MINI_GRACE_PERIOD
|
||||||
|
: Config.MINI_GRACE_PERIOD * 2;
|
||||||
|
return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Bookmark;
|
import eu.siacs.conversations.entities.Bookmark;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
||||||
|
@ -34,6 +35,7 @@ import eu.siacs.conversations.entities.Presences;
|
||||||
import eu.siacs.conversations.generator.IqGenerator;
|
import eu.siacs.conversations.generator.IqGenerator;
|
||||||
import eu.siacs.conversations.generator.MessageGenerator;
|
import eu.siacs.conversations.generator.MessageGenerator;
|
||||||
import eu.siacs.conversations.generator.PresenceGenerator;
|
import eu.siacs.conversations.generator.PresenceGenerator;
|
||||||
|
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||||
import eu.siacs.conversations.parser.IqParser;
|
import eu.siacs.conversations.parser.IqParser;
|
||||||
import eu.siacs.conversations.parser.MessageParser;
|
import eu.siacs.conversations.parser.MessageParser;
|
||||||
import eu.siacs.conversations.parser.PresenceParser;
|
import eu.siacs.conversations.parser.PresenceParser;
|
||||||
|
@ -74,6 +76,7 @@ import android.net.NetworkInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.FileObserver;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
|
@ -81,11 +84,12 @@ import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.LruCache;
|
||||||
|
|
||||||
public class XmppConnectionService extends Service {
|
public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
public DatabaseBackend databaseBackend;
|
public DatabaseBackend databaseBackend;
|
||||||
private FileBackend fileBackend;
|
private FileBackend fileBackend = new FileBackend(this);
|
||||||
|
|
||||||
public long startDate;
|
public long startDate;
|
||||||
|
|
||||||
|
@ -94,7 +98,8 @@ public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
private MemorizingTrustManager mMemorizingTrustManager;
|
private MemorizingTrustManager mMemorizingTrustManager;
|
||||||
|
|
||||||
private NotificationService mNotificationService;
|
private NotificationService mNotificationService = new NotificationService(
|
||||||
|
this);
|
||||||
|
|
||||||
private MessageParser mMessageParser = new MessageParser(this);
|
private MessageParser mMessageParser = new MessageParser(this);
|
||||||
private PresenceParser mPresenceParser = new PresenceParser(this);
|
private PresenceParser mPresenceParser = new PresenceParser(this);
|
||||||
|
@ -106,20 +111,27 @@ public class XmppConnectionService extends Service {
|
||||||
private CopyOnWriteArrayList<Conversation> conversations = null;
|
private CopyOnWriteArrayList<Conversation> conversations = null;
|
||||||
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
|
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
|
||||||
this);
|
this);
|
||||||
|
private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
|
||||||
|
this);
|
||||||
|
private AvatarService mAvatarService = new AvatarService(this);
|
||||||
|
|
||||||
private OnConversationUpdate mOnConversationUpdate = null;
|
private OnConversationUpdate mOnConversationUpdate = null;
|
||||||
private int convChangedListenerCount = 0;
|
private Integer convChangedListenerCount = 0;
|
||||||
private OnAccountUpdate mOnAccountUpdate = null;
|
private OnAccountUpdate mOnAccountUpdate = null;
|
||||||
private int accountChangedListenerCount = 0;
|
private Integer accountChangedListenerCount = 0;
|
||||||
private OnRosterUpdate mOnRosterUpdate = null;
|
private OnRosterUpdate mOnRosterUpdate = null;
|
||||||
private int rosterChangedListenerCount = 0;
|
private Integer rosterChangedListenerCount = 0;
|
||||||
public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
|
public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContactStatusChanged(Contact contact, boolean online) {
|
public void onContactStatusChanged(Contact contact, boolean online) {
|
||||||
Conversation conversation = find(getConversations(), contact);
|
Conversation conversation = find(getConversations(), contact);
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
conversation.endOtrIfNeeded();
|
if (online && contact.getPresences().size() > 1) {
|
||||||
|
conversation.endOtrIfNeeded();
|
||||||
|
} else {
|
||||||
|
conversation.resetOtrSession();
|
||||||
|
}
|
||||||
if (online && (contact.getPresences().size() == 1)) {
|
if (online && (contact.getPresences().size() == 1)) {
|
||||||
sendUnsendMessages(conversation);
|
sendUnsendMessages(conversation);
|
||||||
}
|
}
|
||||||
|
@ -140,6 +152,17 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private FileObserver fileObserver = new FileObserver(
|
||||||
|
FileBackend.getConversationsDirectory()) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(int event, String path) {
|
||||||
|
if (event == FileObserver.DELETE) {
|
||||||
|
markFileDeleted(path.split("\\.")[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final IBinder mBinder = new XmppConnectionBinder();
|
private final IBinder mBinder = new XmppConnectionBinder();
|
||||||
private OnStatusChanged statusListener = new OnStatusChanged() {
|
private OnStatusChanged statusListener = new OnStatusChanged() {
|
||||||
|
|
||||||
|
@ -252,6 +275,7 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
private LruCache<String, Bitmap> mBitmapCache;
|
||||||
|
|
||||||
public PgpEngine getPgpEngine() {
|
public PgpEngine getPgpEngine() {
|
||||||
if (pgpServiceConnection.isBound()) {
|
if (pgpServiceConnection.isBound()) {
|
||||||
|
@ -271,6 +295,10 @@ public class XmppConnectionService extends Service {
|
||||||
return this.fileBackend;
|
return this.fileBackend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AvatarService getAvatarService() {
|
||||||
|
return this.mAvatarService;
|
||||||
|
}
|
||||||
|
|
||||||
public Message attachImageToConversation(final Conversation conversation,
|
public Message attachImageToConversation(final Conversation conversation,
|
||||||
final Uri uri, final UiCallback<Message> callback) {
|
final Uri uri, final UiCallback<Message> callback) {
|
||||||
final Message message;
|
final Message message;
|
||||||
|
@ -331,15 +359,10 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.wakeLock.acquire();
|
this.wakeLock.acquire();
|
||||||
ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
|
|
||||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
|
||||||
boolean isConnected = activeNetwork != null
|
|
||||||
&& activeNetwork.isConnected();
|
|
||||||
|
|
||||||
for (Account account : accounts) {
|
for (Account account : accounts) {
|
||||||
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
||||||
if (!isConnected) {
|
if (!hasInternetConnection()) {
|
||||||
account.setStatus(Account.STATUS_NO_INTERNET);
|
account.setStatus(Account.STATUS_NO_INTERNET);
|
||||||
if (statusListener != null) {
|
if (statusListener != null) {
|
||||||
statusListener.onStatusChanged(account);
|
statusListener.onStatusChanged(account);
|
||||||
|
@ -398,6 +421,13 @@ public class XmppConnectionService extends Service {
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasInternetConnection() {
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) getApplicationContext()
|
||||||
|
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
||||||
|
return activeNetwork != null && activeNetwork.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("TrulyRandom")
|
@SuppressLint("TrulyRandom")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
@ -406,10 +436,18 @@ public class XmppConnectionService extends Service {
|
||||||
this.mRandom = new SecureRandom();
|
this.mRandom = new SecureRandom();
|
||||||
this.mMemorizingTrustManager = new MemorizingTrustManager(
|
this.mMemorizingTrustManager = new MemorizingTrustManager(
|
||||||
getApplicationContext());
|
getApplicationContext());
|
||||||
this.mNotificationService = new NotificationService(this);
|
|
||||||
|
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||||
|
int cacheSize = maxMemory / 8;
|
||||||
|
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
|
||||||
|
@Override
|
||||||
|
protected int sizeOf(String key, Bitmap bitmap) {
|
||||||
|
return bitmap.getByteCount() / 1024;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.databaseBackend = DatabaseBackend
|
this.databaseBackend = DatabaseBackend
|
||||||
.getInstance(getApplicationContext());
|
.getInstance(getApplicationContext());
|
||||||
this.fileBackend = new FileBackend(getApplicationContext());
|
|
||||||
this.accounts = databaseBackend.getAccounts();
|
this.accounts = databaseBackend.getAccounts();
|
||||||
|
|
||||||
for (Account account : this.accounts) {
|
for (Account account : this.accounts) {
|
||||||
|
@ -420,6 +458,7 @@ public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
getContentResolver().registerContentObserver(
|
getContentResolver().registerContentObserver(
|
||||||
ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
|
ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
|
||||||
|
this.fileObserver.startWatching();
|
||||||
this.pgpServiceConnection = new OpenPgpServiceConnection(
|
this.pgpServiceConnection = new OpenPgpServiceConnection(
|
||||||
getApplicationContext(), "org.sufficientlysecure.keychain");
|
getApplicationContext(), "org.sufficientlysecure.keychain");
|
||||||
this.pgpServiceConnection.bindToService();
|
this.pgpServiceConnection.bindToService();
|
||||||
|
@ -511,8 +550,9 @@ public class XmppConnectionService extends Service {
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void sendMessage(Message message) {
|
public void sendMessage(Message message) {
|
||||||
Account account = message.getConversation().getAccount();
|
Account account = message.getConversation().getAccount();
|
||||||
|
account.deactivateGracePeriod();
|
||||||
Conversation conv = message.getConversation();
|
Conversation conv = message.getConversation();
|
||||||
MessagePacket packet = null;
|
MessagePacket packet = null;
|
||||||
boolean saveInDb = true;
|
boolean saveInDb = true;
|
||||||
|
@ -531,13 +571,14 @@ public class XmppConnectionService extends Service {
|
||||||
&& conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
|
&& conv.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) {
|
||||||
mJingleConnectionManager
|
mJingleConnectionManager
|
||||||
.createNewConnection(message);
|
.createNewConnection(message);
|
||||||
} else if (message.getPresence() == null) {
|
|
||||||
message.setStatus(Message.STATUS_WAITING);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mJingleConnectionManager.createNewConnection(message);
|
mJingleConnectionManager.createNewConnection(message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
|
conv.startOtrIfNeeded();
|
||||||
|
}
|
||||||
message.setStatus(Message.STATUS_WAITING);
|
message.setStatus(Message.STATUS_WAITING);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -554,6 +595,7 @@ public class XmppConnectionService extends Service {
|
||||||
send = true;
|
send = true;
|
||||||
|
|
||||||
} else if (message.getPresence() == null) {
|
} else if (message.getPresence() == null) {
|
||||||
|
conv.startOtrIfNeeded();
|
||||||
message.setStatus(Message.STATUS_WAITING);
|
message.setStatus(Message.STATUS_WAITING);
|
||||||
}
|
}
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
||||||
|
@ -596,7 +638,7 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
conv.getMessages().add(message);
|
conv.add(message);
|
||||||
if (saveInDb) {
|
if (saveInDb) {
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_NONE
|
if (message.getEncryption() == Message.ENCRYPTION_NONE
|
||||||
|| saveEncryptedMessages()) {
|
|| saveEncryptedMessages()) {
|
||||||
|
@ -776,6 +818,7 @@ public class XmppConnectionService extends Service {
|
||||||
.getString("photouri"));
|
.getString("photouri"));
|
||||||
contact.setSystemName(phoneContact
|
contact.setSystemName(phoneContact
|
||||||
.getString("displayname"));
|
.getString("displayname"));
|
||||||
|
getAvatarService().clear(contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -794,12 +837,39 @@ public class XmppConnectionService extends Service {
|
||||||
Account account = accountLookupTable.get(conv.getAccountUuid());
|
Account account = accountLookupTable.get(conv.getAccountUuid());
|
||||||
conv.setAccount(account);
|
conv.setAccount(account);
|
||||||
conv.setMessages(databaseBackend.getMessages(conv, 50));
|
conv.setMessages(databaseBackend.getMessages(conv, 50));
|
||||||
|
checkDeletedFiles(conv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.conversations;
|
return this.conversations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkDeletedFiles(Conversation conversation) {
|
||||||
|
for (Message message : conversation.getMessages()) {
|
||||||
|
if (message.getType() == Message.TYPE_IMAGE
|
||||||
|
&& message.getEncryption() != Message.ENCRYPTION_PGP) {
|
||||||
|
if (!getFileBackend().isFileAvailable(message)) {
|
||||||
|
message.setDownloadable(new DeletedDownloadable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markFileDeleted(String uuid) {
|
||||||
|
for (Conversation conversation : getConversations()) {
|
||||||
|
for (Message message : conversation.getMessages()) {
|
||||||
|
if (message.getType() == Message.TYPE_IMAGE
|
||||||
|
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
||||||
|
&& message.getUuid().equals(uuid)) {
|
||||||
|
if (!getFileBackend().isFileAvailable(message)) {
|
||||||
|
message.setDownloadable(new DeletedDownloadable());
|
||||||
|
updateConversationUi();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void populateWithOrderedConversations(List<Conversation> list) {
|
public void populateWithOrderedConversations(List<Conversation> list) {
|
||||||
populateWithOrderedConversations(list, true);
|
populateWithOrderedConversations(list, true);
|
||||||
}
|
}
|
||||||
|
@ -838,7 +908,7 @@ public class XmppConnectionService extends Service {
|
||||||
for (Message message : messages) {
|
for (Message message : messages) {
|
||||||
message.setConversation(conversation);
|
message.setConversation(conversation);
|
||||||
}
|
}
|
||||||
conversation.getMessages().addAll(0, messages);
|
conversation.addAll(0, messages);
|
||||||
return messages.size();
|
return messages.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,9 +928,9 @@ public class XmppConnectionService extends Service {
|
||||||
public Conversation find(List<Conversation> haystack, Account account,
|
public Conversation find(List<Conversation> haystack, Account account,
|
||||||
String jid) {
|
String jid) {
|
||||||
for (Conversation conversation : haystack) {
|
for (Conversation conversation : haystack) {
|
||||||
if ((account == null || conversation.getAccount().equals(account))
|
if ((account == null || conversation.getAccount() == account)
|
||||||
&& (conversation.getContactJid().split("/", 2)[0]
|
&& (conversation.getContactJid().split("/", 2)[0]
|
||||||
.equals(jid))) {
|
.equalsIgnoreCase(jid))) {
|
||||||
return conversation;
|
return conversation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -927,7 +997,6 @@ public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
public void clearConversationHistory(Conversation conversation) {
|
public void clearConversationHistory(Conversation conversation) {
|
||||||
this.databaseBackend.deleteMessagesInConversation(conversation);
|
this.databaseBackend.deleteMessagesInConversation(conversation);
|
||||||
this.fileBackend.removeFiles(conversation);
|
|
||||||
conversation.getMessages().clear();
|
conversation.getMessages().clear();
|
||||||
updateConversationUi();
|
updateConversationUi();
|
||||||
}
|
}
|
||||||
|
@ -973,60 +1042,85 @@ public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
public void setOnConversationListChangedListener(
|
public void setOnConversationListChangedListener(
|
||||||
OnConversationUpdate listener) {
|
OnConversationUpdate listener) {
|
||||||
this.mNotificationService.deactivateGracePeriod();
|
if (!isScreenOn()) {
|
||||||
if (checkListeners()) {
|
Log.d(Config.LOGTAG,
|
||||||
switchToForeground();
|
"ignoring setOnConversationListChangedListener");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (this.convChangedListenerCount) {
|
||||||
|
if (checkListeners()) {
|
||||||
|
switchToForeground();
|
||||||
|
}
|
||||||
|
this.mOnConversationUpdate = listener;
|
||||||
|
this.mNotificationService.setIsInForeground(true);
|
||||||
|
this.convChangedListenerCount++;
|
||||||
}
|
}
|
||||||
this.mOnConversationUpdate = listener;
|
|
||||||
this.mNotificationService.setIsInForeground(true);
|
|
||||||
this.convChangedListenerCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeOnConversationListChangedListener() {
|
public void removeOnConversationListChangedListener() {
|
||||||
this.convChangedListenerCount--;
|
synchronized (this.convChangedListenerCount) {
|
||||||
if (this.convChangedListenerCount == 0) {
|
this.convChangedListenerCount--;
|
||||||
this.mOnConversationUpdate = null;
|
if (this.convChangedListenerCount <= 0) {
|
||||||
this.mNotificationService.setIsInForeground(false);
|
this.convChangedListenerCount = 0;
|
||||||
if (checkListeners()) {
|
this.mOnConversationUpdate = null;
|
||||||
switchToBackground();
|
this.mNotificationService.setIsInForeground(false);
|
||||||
|
if (checkListeners()) {
|
||||||
|
switchToBackground();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnAccountListChangedListener(OnAccountUpdate listener) {
|
public void setOnAccountListChangedListener(OnAccountUpdate listener) {
|
||||||
this.mNotificationService.deactivateGracePeriod();
|
if (!isScreenOn()) {
|
||||||
if (checkListeners()) {
|
Log.d(Config.LOGTAG, "ignoring setOnAccountListChangedListener");
|
||||||
switchToForeground();
|
return;
|
||||||
|
}
|
||||||
|
synchronized (this.accountChangedListenerCount) {
|
||||||
|
if (checkListeners()) {
|
||||||
|
switchToForeground();
|
||||||
|
}
|
||||||
|
this.mOnAccountUpdate = listener;
|
||||||
|
this.accountChangedListenerCount++;
|
||||||
}
|
}
|
||||||
this.mOnAccountUpdate = listener;
|
|
||||||
this.accountChangedListenerCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeOnAccountListChangedListener() {
|
public void removeOnAccountListChangedListener() {
|
||||||
this.accountChangedListenerCount--;
|
synchronized (this.accountChangedListenerCount) {
|
||||||
if (this.accountChangedListenerCount == 0) {
|
this.accountChangedListenerCount--;
|
||||||
this.mOnAccountUpdate = null;
|
if (this.accountChangedListenerCount <= 0) {
|
||||||
if (checkListeners()) {
|
this.mOnAccountUpdate = null;
|
||||||
switchToBackground();
|
this.accountChangedListenerCount = 0;
|
||||||
|
if (checkListeners()) {
|
||||||
|
switchToBackground();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnRosterUpdateListener(OnRosterUpdate listener) {
|
public void setOnRosterUpdateListener(OnRosterUpdate listener) {
|
||||||
this.mNotificationService.deactivateGracePeriod();
|
if (!isScreenOn()) {
|
||||||
if (checkListeners()) {
|
Log.d(Config.LOGTAG, "ignoring setOnRosterUpdateListener");
|
||||||
switchToForeground();
|
return;
|
||||||
|
}
|
||||||
|
synchronized (this.rosterChangedListenerCount) {
|
||||||
|
if (checkListeners()) {
|
||||||
|
switchToForeground();
|
||||||
|
}
|
||||||
|
this.mOnRosterUpdate = listener;
|
||||||
|
this.rosterChangedListenerCount++;
|
||||||
}
|
}
|
||||||
this.mOnRosterUpdate = listener;
|
|
||||||
this.rosterChangedListenerCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeOnRosterUpdateListener() {
|
public void removeOnRosterUpdateListener() {
|
||||||
this.rosterChangedListenerCount--;
|
synchronized (this.rosterChangedListenerCount) {
|
||||||
if (this.rosterChangedListenerCount == 0) {
|
this.rosterChangedListenerCount--;
|
||||||
this.mOnRosterUpdate = null;
|
if (this.rosterChangedListenerCount <= 0) {
|
||||||
if (checkListeners()) {
|
this.rosterChangedListenerCount = 0;
|
||||||
switchToBackground();
|
this.mOnRosterUpdate = null;
|
||||||
|
if (checkListeners()) {
|
||||||
|
switchToBackground();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1042,11 +1136,10 @@ public class XmppConnectionService extends Service {
|
||||||
XmppConnection connection = account.getXmppConnection();
|
XmppConnection connection = account.getXmppConnection();
|
||||||
if (connection != null && connection.getFeatures().csi()) {
|
if (connection != null && connection.getFeatures().csi()) {
|
||||||
connection.sendActive();
|
connection.sendActive();
|
||||||
Log.d(Config.LOGTAG, account.getJid()
|
|
||||||
+ " sending csi//active");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Log.d(Config.LOGTAG, "app switched into foreground");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchToBackground() {
|
private void switchToBackground() {
|
||||||
|
@ -1055,11 +1148,17 @@ public class XmppConnectionService extends Service {
|
||||||
XmppConnection connection = account.getXmppConnection();
|
XmppConnection connection = account.getXmppConnection();
|
||||||
if (connection != null && connection.getFeatures().csi()) {
|
if (connection != null && connection.getFeatures().csi()) {
|
||||||
connection.sendInactive();
|
connection.sendInactive();
|
||||||
Log.d(Config.LOGTAG, account.getJid()
|
|
||||||
+ " sending csi//inactive");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.mNotificationService.setIsInForeground(false);
|
||||||
|
Log.d(Config.LOGTAG, "app switched into background");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isScreenOn() {
|
||||||
|
PowerManager pm = (PowerManager) this
|
||||||
|
.getSystemService(Context.POWER_SERVICE);
|
||||||
|
return pm.isScreenOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connectMultiModeConversations(Account account) {
|
public void connectMultiModeConversations(Account account) {
|
||||||
|
@ -1197,7 +1296,7 @@ public class XmppConnectionService extends Service {
|
||||||
conversation.getMucOptions().setOffline();
|
conversation.getMucOptions().setOffline();
|
||||||
conversation.deregisterWithBookmark();
|
conversation.deregisterWithBookmark();
|
||||||
Log.d(Config.LOGTAG, conversation.getAccount().getJid()
|
Log.d(Config.LOGTAG, conversation.getAccount().getJid()
|
||||||
+ " leaving muc " + conversation.getContactJid());
|
+ ": leaving muc " + conversation.getContactJid());
|
||||||
} else {
|
} else {
|
||||||
account.pendingConferenceLeaves.add(conversation);
|
account.pendingConferenceLeaves.add(conversation);
|
||||||
}
|
}
|
||||||
|
@ -1214,7 +1313,11 @@ public class XmppConnectionService extends Service {
|
||||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
leaveMuc(conversation);
|
leaveMuc(conversation);
|
||||||
} else {
|
} else {
|
||||||
conversation.endOtrIfNeeded();
|
if (conversation.endOtrIfNeeded()) {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid()
|
||||||
|
+ ": ended otr session with "
|
||||||
|
+ conversation.getContactJid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1230,6 +1333,7 @@ public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
public void updateMessage(Message message) {
|
public void updateMessage(Message message) {
|
||||||
databaseBackend.updateMessage(message);
|
databaseBackend.updateMessage(message);
|
||||||
|
updateConversationUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void syncDirtyContacts(Account account) {
|
protected void syncDirtyContacts(Account account) {
|
||||||
|
@ -1410,10 +1514,16 @@ public class XmppConnectionService extends Service {
|
||||||
if (account.setAvatar(avatar.getFilename())) {
|
if (account.setAvatar(avatar.getFilename())) {
|
||||||
databaseBackend.updateAccount(account);
|
databaseBackend.updateAccount(account);
|
||||||
}
|
}
|
||||||
|
getAvatarService().clear(account);
|
||||||
|
updateConversationUi();
|
||||||
|
updateAccountUi();
|
||||||
} else {
|
} else {
|
||||||
Contact contact = account.getRoster()
|
Contact contact = account.getRoster()
|
||||||
.getContact(avatar.owner);
|
.getContact(avatar.owner);
|
||||||
contact.setAvatar(avatar.getFilename());
|
contact.setAvatar(avatar.getFilename());
|
||||||
|
getAvatarService().clear(contact);
|
||||||
|
updateConversationUi();
|
||||||
|
updateRosterUi();
|
||||||
}
|
}
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.success(avatar);
|
callback.success(avatar);
|
||||||
|
@ -1463,6 +1573,7 @@ public class XmppConnectionService extends Service {
|
||||||
if (account.setAvatar(avatar.getFilename())) {
|
if (account.setAvatar(avatar.getFilename())) {
|
||||||
databaseBackend.updateAccount(account);
|
databaseBackend.updateAccount(account);
|
||||||
}
|
}
|
||||||
|
getAvatarService().clear(account);
|
||||||
callback.success(avatar);
|
callback.success(avatar);
|
||||||
} else {
|
} else {
|
||||||
fetchAvatar(account, avatar, callback);
|
fetchAvatar(account, avatar, callback);
|
||||||
|
@ -1675,6 +1786,10 @@ public class XmppConnectionService extends Service {
|
||||||
return this.pm;
|
return this.pm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LruCache<String, Bitmap> getBitmapCache() {
|
||||||
|
return this.mBitmapCache;
|
||||||
|
}
|
||||||
|
|
||||||
public void replyWithNotAcceptable(Account account, MessagePacket packet) {
|
public void replyWithNotAcceptable(Account account, MessagePacket packet) {
|
||||||
if (account.getStatus() == Account.STATUS_ONLINE) {
|
if (account.getStatus() == Account.STATUS_ONLINE) {
|
||||||
MessagePacket error = this.mMessageGenerator
|
MessagePacket error = this.mMessageGenerator
|
||||||
|
@ -1779,8 +1894,7 @@ public class XmppConnectionService extends Service {
|
||||||
ArrayList<Contact> contacts = new ArrayList<Contact>();
|
ArrayList<Contact> contacts = new ArrayList<Contact>();
|
||||||
for (Account account : getAccounts()) {
|
for (Account account : getAccounts()) {
|
||||||
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
||||||
Contact contact = account.getRoster()
|
Contact contact = account.getRoster().getContactFromRoster(jid);
|
||||||
.getContactAsShownInRoster(jid);
|
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
contacts.add(contact);
|
contacts.add(contact);
|
||||||
}
|
}
|
||||||
|
@ -1792,4 +1906,44 @@ public class XmppConnectionService extends Service {
|
||||||
public NotificationService getNotificationService() {
|
public NotificationService getNotificationService() {
|
||||||
return this.mNotificationService;
|
return this.mNotificationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpConnectionManager getHttpConnectionManager() {
|
||||||
|
return this.mHttpConnectionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DeletedDownloadable implements Downloadable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean start() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStatus() {
|
||||||
|
return Downloadable.STATUS_DELETED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFileSize() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resendFailedMessages(Message message) {
|
||||||
|
List<Message> messages = new ArrayList<Message>();
|
||||||
|
Message current = message;
|
||||||
|
while(current.getStatus() == Message.STATUS_SEND_FAILED) {
|
||||||
|
messages.add(current);
|
||||||
|
if (current.mergable(current.next())) {
|
||||||
|
current = current.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Message msg: messages) {
|
||||||
|
markMessage(msg, Message.STATUS_WAITING);
|
||||||
|
this.resendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ public class ChooseContactActivity extends XmppActivity {
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.choose_contact, menu);
|
getMenuInflater().inflate(R.menu.choose_contact, menu);
|
||||||
MenuItem menuSearchView = (MenuItem) menu.findItem(R.id.action_search);
|
MenuItem menuSearchView = menu.findItem(R.id.action_search);
|
||||||
View mSearchView = menuSearchView.getActionView();
|
View mSearchView = menuSearchView.getActionView();
|
||||||
mSearchEditText = (EditText) mSearchView
|
mSearchEditText = (EditText) mSearchView
|
||||||
.findViewById(R.id.search_field);
|
.findViewById(R.id.search_field);
|
||||||
|
|
|
@ -7,14 +7,12 @@ import org.openintents.openpgp.util.OpenPgpUtils;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.PgpEngine;
|
import eu.siacs.conversations.crypto.PgpEngine;
|
||||||
import eu.siacs.conversations.entities.Account;
|
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
||||||
import eu.siacs.conversations.entities.MucOptions.User;
|
import eu.siacs.conversations.entities.MucOptions.User;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
|
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -41,6 +39,7 @@ public class ConferenceDetailsActivity extends XmppActivity {
|
||||||
private ImageButton mEditNickButton;
|
private ImageButton mEditNickButton;
|
||||||
private TextView mRoleAffiliaton;
|
private TextView mRoleAffiliaton;
|
||||||
private TextView mFullJid;
|
private TextView mFullJid;
|
||||||
|
private TextView mAccountJid;
|
||||||
private LinearLayout membersView;
|
private LinearLayout membersView;
|
||||||
private LinearLayout mMoreDetails;
|
private LinearLayout mMoreDetails;
|
||||||
private Button mInviteButton;
|
private Button mInviteButton;
|
||||||
|
@ -78,6 +77,7 @@ public class ConferenceDetailsActivity extends XmppActivity {
|
||||||
mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button);
|
mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button);
|
||||||
mFullJid = (TextView) findViewById(R.id.muc_jabberid);
|
mFullJid = (TextView) findViewById(R.id.muc_jabberid);
|
||||||
membersView = (LinearLayout) findViewById(R.id.muc_members);
|
membersView = (LinearLayout) findViewById(R.id.muc_members);
|
||||||
|
mAccountJid = (TextView) findViewById(R.id.details_account);
|
||||||
mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details);
|
mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details);
|
||||||
mMoreDetails.setVisibility(View.GONE);
|
mMoreDetails.setVisibility(View.GONE);
|
||||||
mInviteButton = (Button) findViewById(R.id.invite);
|
mInviteButton = (Button) findViewById(R.id.invite);
|
||||||
|
@ -169,37 +169,38 @@ public class ConferenceDetailsActivity extends XmppActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void registerListener() {
|
protected void registerListener() {
|
||||||
if (xmppConnectionServiceBound) {
|
xmppConnectionService
|
||||||
xmppConnectionService
|
.setOnConversationListChangedListener(this.onConvChanged);
|
||||||
.setOnConversationListChangedListener(this.onConvChanged);
|
xmppConnectionService.setOnRenameListener(new OnRenameListener() {
|
||||||
xmppConnectionService.setOnRenameListener(new OnRenameListener() {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRename(final boolean success) {
|
public void onRename(final boolean success) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
populateView();
|
populateView();
|
||||||
if (success) {
|
if (success) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
ConferenceDetailsActivity.this,
|
ConferenceDetailsActivity.this,
|
||||||
getString(R.string.your_nick_has_been_changed),
|
getString(R.string.your_nick_has_been_changed),
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(ConferenceDetailsActivity.this,
|
Toast.makeText(ConferenceDetailsActivity.this,
|
||||||
getString(R.string.nick_in_use),
|
getString(R.string.nick_in_use),
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateView() {
|
private void populateView() {
|
||||||
mYourPhoto.setImageBitmap(conversation.getAccount().getImage(this, 48));
|
mAccountJid.setText(getString(R.string.using_account, conversation
|
||||||
|
.getAccount().getJid()));
|
||||||
|
mYourPhoto.setImageBitmap(avatarService().get(
|
||||||
|
conversation.getAccount(), getPixel(48)));
|
||||||
setTitle(conversation.getName());
|
setTitle(conversation.getName());
|
||||||
mFullJid.setText(conversation.getContactJid().split("/", 2)[0]);
|
mFullJid.setText(conversation.getContactJid().split("/", 2)[0]);
|
||||||
mYourNick.setText(conversation.getMucOptions().getActualNick());
|
mYourNick.setText(conversation.getMucOptions().getActualNick());
|
||||||
|
@ -225,9 +226,8 @@ public class ConferenceDetailsActivity extends XmppActivity {
|
||||||
this.users.addAll(conversation.getMucOptions().getUsers());
|
this.users.addAll(conversation.getMucOptions().getUsers());
|
||||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
membersView.removeAllViews();
|
membersView.removeAllViews();
|
||||||
Account account = conversation.getAccount();
|
|
||||||
for (final User user : conversation.getMucOptions().getUsers()) {
|
for (final User user : conversation.getMucOptions().getUsers()) {
|
||||||
View view = (View) inflater.inflate(R.layout.contact, membersView,
|
View view = inflater.inflate(R.layout.contact, membersView,
|
||||||
false);
|
false);
|
||||||
TextView name = (TextView) view
|
TextView name = (TextView) view
|
||||||
.findViewById(R.id.contact_display_name);
|
.findViewById(R.id.contact_display_name);
|
||||||
|
@ -245,22 +245,14 @@ public class ConferenceDetailsActivity extends XmppActivity {
|
||||||
key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
|
key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
|
||||||
}
|
}
|
||||||
Bitmap bm;
|
Bitmap bm;
|
||||||
if (user.getJid() != null) {
|
Contact contact = user.getContact();
|
||||||
Contact contact = account.getRoster().getContact(user.getJid());
|
if (contact != null) {
|
||||||
if (contact.showInRoster()) {
|
bm = avatarService().get(contact, getPixel(48));
|
||||||
bm = contact.getImage(48, this);
|
name.setText(contact.getDisplayName());
|
||||||
name.setText(contact.getDisplayName());
|
role.setText(user.getName() + " \u2022 "
|
||||||
role.setText(user.getName() + " \u2022 "
|
+ getReadableRole(user.getRole()));
|
||||||
+ getReadableRole(user.getRole()));
|
|
||||||
} else {
|
|
||||||
bm = UIHelper.getContactPicture(user.getName(), 48, this,
|
|
||||||
false);
|
|
||||||
name.setText(user.getName());
|
|
||||||
role.setText(getReadableRole(user.getRole()));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
bm = UIHelper
|
bm = avatarService().get(user.getName(), getPixel(48));
|
||||||
.getContactPicture(user.getName(), 48, this, false);
|
|
||||||
name.setText(user.getName());
|
name.setText(user.getName());
|
||||||
role.setText(getReadableRole(user.getRole()));
|
role.setText(getReadableRole(user.getRole()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,8 @@ public class ContactDetailsActivity extends XmppActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
ContactDetailsActivity.this.xmppConnectionService.deleteContactOnServer(contact);
|
ContactDetailsActivity.this.xmppConnectionService
|
||||||
|
.deleteContactOnServer(contact);
|
||||||
ContactDetailsActivity.this.finish();
|
ContactDetailsActivity.this.finish();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -78,7 +79,8 @@ public class ContactDetailsActivity extends XmppActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(ContactDetailsActivity.this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(
|
||||||
|
ContactDetailsActivity.this);
|
||||||
builder.setTitle(getString(R.string.action_add_phone_book));
|
builder.setTitle(getString(R.string.action_add_phone_book));
|
||||||
builder.setMessage(getString(R.string.add_phone_book_text,
|
builder.setMessage(getString(R.string.add_phone_book_text,
|
||||||
contact.getJid()));
|
contact.getJid()));
|
||||||
|
@ -309,22 +311,21 @@ public class ContactDetailsActivity extends XmppActivity {
|
||||||
} else {
|
} else {
|
||||||
contactJidTv.setText(contact.getJid());
|
contactJidTv.setText(contact.getJid());
|
||||||
}
|
}
|
||||||
accountJidTv.setText(contact.getAccount().getJid());
|
accountJidTv.setText(getString(R.string.using_account, contact
|
||||||
|
.getAccount().getJid()));
|
||||||
UIHelper.prepareContactBadge(this, badge, contact,
|
prepareContactBadge(badge, contact);
|
||||||
getApplicationContext());
|
|
||||||
|
|
||||||
if (contact.getSystemAccount() == null) {
|
if (contact.getSystemAccount() == null) {
|
||||||
badge.setOnClickListener(onBadgeClick);
|
badge.setOnClickListener(onBadgeClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
keys.removeAllViews();
|
keys.removeAllViews();
|
||||||
|
boolean hasKeys = false;
|
||||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
for (Iterator<String> iterator = contact.getOtrFingerprints()
|
for (Iterator<String> iterator = contact.getOtrFingerprints()
|
||||||
.iterator(); iterator.hasNext();) {
|
.iterator(); iterator.hasNext();) {
|
||||||
|
hasKeys = true;
|
||||||
final String otrFingerprint = iterator.next();
|
final String otrFingerprint = iterator.next();
|
||||||
View view = (View) inflater.inflate(R.layout.contact_key, keys,
|
View view = inflater.inflate(R.layout.contact_key, keys, false);
|
||||||
false);
|
|
||||||
TextView key = (TextView) view.findViewById(R.id.key);
|
TextView key = (TextView) view.findViewById(R.id.key);
|
||||||
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
||||||
ImageButton remove = (ImageButton) view
|
ImageButton remove = (ImageButton) view
|
||||||
|
@ -342,8 +343,8 @@ public class ContactDetailsActivity extends XmppActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (contact.getPgpKeyId() != 0) {
|
if (contact.getPgpKeyId() != 0) {
|
||||||
View view = (View) inflater.inflate(R.layout.contact_key, keys,
|
hasKeys = true;
|
||||||
false);
|
View view = inflater.inflate(R.layout.contact_key, keys, false);
|
||||||
TextView key = (TextView) view.findViewById(R.id.key);
|
TextView key = (TextView) view.findViewById(R.id.key);
|
||||||
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
||||||
keyType.setText("PGP Key ID");
|
keyType.setText("PGP Key ID");
|
||||||
|
@ -370,6 +371,20 @@ public class ContactDetailsActivity extends XmppActivity {
|
||||||
});
|
});
|
||||||
keys.addView(view);
|
keys.addView(view);
|
||||||
}
|
}
|
||||||
|
if (hasKeys) {
|
||||||
|
keys.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
keys.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareContactBadge(QuickContactBadge badge, Contact contact) {
|
||||||
|
if (contact.getSystemAccount() != null) {
|
||||||
|
String[] systemAccount = contact.getSystemAccount().split("#");
|
||||||
|
long id = Long.parseLong(systemAccount[0]);
|
||||||
|
badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1]));
|
||||||
|
}
|
||||||
|
badge.setImageBitmap(avatarService().get(contact, getPixel(72)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void confirmToDeleteFingerprint(final String fingerprint) {
|
protected void confirmToDeleteFingerprint(final String fingerprint) {
|
||||||
|
|
|
@ -191,6 +191,7 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
xmppConnectionService.getNotificationService()
|
xmppConnectionService.getNotificationService()
|
||||||
.setOpenConversation(null);
|
.setOpenConversation(null);
|
||||||
}
|
}
|
||||||
|
closeContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -222,7 +223,8 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
ab.setDisplayHomeAsUpEnabled(true);
|
ab.setDisplayHomeAsUpEnabled(true);
|
||||||
ab.setHomeButtonEnabled(true);
|
ab.setHomeButtonEnabled(true);
|
||||||
if (getSelectedConversation().getMode() == Conversation.MODE_SINGLE
|
if (getSelectedConversation().getMode() == Conversation.MODE_SINGLE
|
||||||
|| ConversationActivity.this.useSubjectToIdentifyConference()) {
|
|| ConversationActivity.this
|
||||||
|
.useSubjectToIdentifyConference()) {
|
||||||
ab.setTitle(getSelectedConversation().getName());
|
ab.setTitle(getSelectedConversation().getName());
|
||||||
} else {
|
} else {
|
||||||
ab.setTitle(getSelectedConversation().getContactJid()
|
ab.setTitle(getSelectedConversation().getContactJid()
|
||||||
|
@ -239,19 +241,16 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.conversations, menu);
|
getMenuInflater().inflate(R.menu.conversations, menu);
|
||||||
MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
|
MenuItem menuSecure = menu.findItem(R.id.action_security);
|
||||||
MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
|
MenuItem menuArchive = menu.findItem(R.id.action_archive);
|
||||||
MenuItem menuMucDetails = (MenuItem) menu
|
MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details);
|
||||||
.findItem(R.id.action_muc_details);
|
MenuItem menuContactDetails = menu
|
||||||
MenuItem menuContactDetails = (MenuItem) menu
|
|
||||||
.findItem(R.id.action_contact_details);
|
.findItem(R.id.action_contact_details);
|
||||||
MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
|
MenuItem menuAttach = menu.findItem(R.id.action_attach_file);
|
||||||
MenuItem menuClearHistory = (MenuItem) menu
|
MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history);
|
||||||
.findItem(R.id.action_clear_history);
|
MenuItem menuAdd = menu.findItem(R.id.action_add);
|
||||||
MenuItem menuAdd = (MenuItem) menu.findItem(R.id.action_add);
|
MenuItem menuInviteContact = menu.findItem(R.id.action_invite);
|
||||||
MenuItem menuInviteContact = (MenuItem) menu
|
MenuItem menuMute = menu.findItem(R.id.action_mute);
|
||||||
.findItem(R.id.action_invite);
|
|
||||||
MenuItem menuMute = (MenuItem) menu.findItem(R.id.action_mute);
|
|
||||||
|
|
||||||
if (isConversationsOverviewVisable()
|
if (isConversationsOverviewVisable()
|
||||||
&& isConversationsOverviewHideable()) {
|
&& isConversationsOverviewHideable()) {
|
||||||
|
@ -604,8 +603,11 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
.beginTransaction();
|
.beginTransaction();
|
||||||
transaction.replace(R.id.selected_conversation, selectedFragment,
|
transaction.replace(R.id.selected_conversation, selectedFragment,
|
||||||
"conversation");
|
"conversation");
|
||||||
|
try {
|
||||||
transaction.commitAllowingStateLoss();
|
transaction.commitAllowingStateLoss();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
return selectedFragment;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return selectedFragment;
|
return selectedFragment;
|
||||||
}
|
}
|
||||||
|
@ -624,23 +626,10 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
if (xmppConnectionServiceBound) {
|
if (xmppConnectionServiceBound) {
|
||||||
if ((Intent.ACTION_VIEW.equals(intent.getAction()) && (VIEW_CONVERSATION
|
if (intent != null && VIEW_CONVERSATION.equals(intent.getType())) {
|
||||||
.equals(intent.getType())))) {
|
handleViewConversationIntent(intent);
|
||||||
String convToView = (String) intent.getExtras().get(
|
|
||||||
CONVERSATION);
|
|
||||||
updateConversationList();
|
|
||||||
for (int i = 0; i < conversationList.size(); ++i) {
|
|
||||||
if (conversationList.get(i).getUuid().equals(convToView)) {
|
|
||||||
setSelectedConversation(conversationList.get(i));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paneShouldBeOpen = false;
|
|
||||||
String text = intent.getExtras().getString(TEXT, null);
|
|
||||||
swapConversationFragment().setText(text);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handledViewIntent = false;
|
|
||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -690,6 +679,10 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
} else if (conversationList.size() <= 0) {
|
} else if (conversationList.size() <= 0) {
|
||||||
startActivity(new Intent(this, StartConversationActivity.class));
|
startActivity(new Intent(this, StartConversationActivity.class));
|
||||||
finish();
|
finish();
|
||||||
|
} else if (getIntent() != null
|
||||||
|
&& VIEW_CONVERSATION.equals(getIntent().getType())) {
|
||||||
|
handleViewConversationIntent(getIntent());
|
||||||
|
setIntent(null);
|
||||||
} else if (mOpenConverstaion != null) {
|
} else if (mOpenConverstaion != null) {
|
||||||
selectConversationByUuid(mOpenConverstaion);
|
selectConversationByUuid(mOpenConverstaion);
|
||||||
paneShouldBeOpen = mPanelOpen;
|
paneShouldBeOpen = mPanelOpen;
|
||||||
|
@ -698,14 +691,6 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
}
|
}
|
||||||
swapConversationFragment();
|
swapConversationFragment();
|
||||||
mOpenConverstaion = null;
|
mOpenConverstaion = null;
|
||||||
} else if (getIntent() != null
|
|
||||||
&& VIEW_CONVERSATION.equals(getIntent().getType())) {
|
|
||||||
String uuid = (String) getIntent().getExtras().get(CONVERSATION);
|
|
||||||
String text = getIntent().getExtras().getString(TEXT, null);
|
|
||||||
selectConversationByUuid(uuid);
|
|
||||||
paneShouldBeOpen = false;
|
|
||||||
swapConversationFragment().setText(text);
|
|
||||||
setIntent(null);
|
|
||||||
} else {
|
} else {
|
||||||
showConversationsOverview();
|
showConversationsOverview();
|
||||||
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
|
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
|
||||||
|
@ -727,6 +712,14 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleViewConversationIntent(Intent intent) {
|
||||||
|
String uuid = (String) intent.getExtras().get(CONVERSATION);
|
||||||
|
String text = intent.getExtras().getString(TEXT, null);
|
||||||
|
selectConversationByUuid(uuid);
|
||||||
|
paneShouldBeOpen = false;
|
||||||
|
swapConversationFragment().setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
private void selectConversationByUuid(String uuid) {
|
private void selectConversationByUuid(String uuid) {
|
||||||
for (int i = 0; i < conversationList.size(); ++i) {
|
for (int i = 0; i < conversationList.size(); ++i) {
|
||||||
if (conversationList.get(i).getUuid().equals(uuid)) {
|
if (conversationList.get(i).getUuid().equals(uuid)) {
|
||||||
|
@ -736,11 +729,9 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerListener() {
|
public void registerListener() {
|
||||||
if (xmppConnectionServiceBound) {
|
xmppConnectionService.setOnConversationListChangedListener(this);
|
||||||
xmppConnectionService.setOnConversationListChangedListener(this);
|
xmppConnectionService.setOnAccountListChangedListener(this);
|
||||||
xmppConnectionService.setOnAccountListChangedListener(this);
|
xmppConnectionService.setOnRosterUpdateListener(this);
|
||||||
xmppConnectionService.setOnRosterUpdateListener(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -753,6 +744,7 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
.findFragmentByTag("conversation");
|
.findFragmentByTag("conversation");
|
||||||
if (selectedFragment != null) {
|
if (selectedFragment != null) {
|
||||||
selectedFragment.hideSnackbar();
|
selectedFragment.hideSnackbar();
|
||||||
|
selectedFragment.updateMessages();
|
||||||
}
|
}
|
||||||
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
|
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
|
||||||
pendingImageUri = data.getData();
|
pendingImageUri = data.getData();
|
||||||
|
@ -786,6 +778,10 @@ public class ConversationActivity extends XmppActivity implements
|
||||||
attachAudioToConversation(getSelectedConversation(),
|
attachAudioToConversation(getSelectedConversation(),
|
||||||
data.getData());
|
data.getData());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (requestCode == REQUEST_IMAGE_CAPTURE) {
|
||||||
|
pendingImageUri = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.siacs.conversations.ui;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
import net.java.otr4j.session.SessionStatus;
|
import net.java.otr4j.session.SessionStatus;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
@ -32,9 +33,12 @@ import android.content.IntentSender.SendIntentException;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Selection;
|
import android.text.Selection;
|
||||||
|
import android.view.ContextMenu;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -44,6 +48,8 @@ import android.widget.AbsListView.OnScrollListener;
|
||||||
import android.widget.TextView.OnEditorActionListener;
|
import android.widget.TextView.OnEditorActionListener;
|
||||||
import android.widget.AbsListView;
|
import android.widget.AbsListView;
|
||||||
|
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
|
@ -72,6 +78,9 @@ public class ConversationFragment extends Fragment {
|
||||||
|
|
||||||
private IntentSender askForPassphraseIntent = null;
|
private IntentSender askForPassphraseIntent = null;
|
||||||
|
|
||||||
|
private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<Message>();
|
||||||
|
private boolean mDecryptJobRunning = false;
|
||||||
|
|
||||||
private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
|
private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -189,6 +198,7 @@ public class ConversationFragment extends Fragment {
|
||||||
};
|
};
|
||||||
|
|
||||||
private ConversationActivity activity;
|
private ConversationActivity activity;
|
||||||
|
private Message selectedMessage;
|
||||||
|
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
if (this.conversation == null) {
|
if (this.conversation == null) {
|
||||||
|
@ -322,9 +332,114 @@ public class ConversationFragment extends Fragment {
|
||||||
});
|
});
|
||||||
messagesView.setAdapter(messageListAdapter);
|
messagesView.setAdapter(messageListAdapter);
|
||||||
|
|
||||||
|
registerForContextMenu(messagesView);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||||
|
ContextMenuInfo menuInfo) {
|
||||||
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
|
AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
|
||||||
|
this.selectedMessage = this.messageList.get(acmi.position);
|
||||||
|
populateContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateContextMenu(ContextMenu menu) {
|
||||||
|
if (this.selectedMessage.getType() != Message.TYPE_STATUS) {
|
||||||
|
activity.getMenuInflater().inflate(R.menu.message_context, menu);
|
||||||
|
menu.setHeaderTitle(R.string.message_options);
|
||||||
|
MenuItem copyText = menu.findItem(R.id.copy_text);
|
||||||
|
MenuItem shareImage = menu.findItem(R.id.share_image);
|
||||||
|
MenuItem sendAgain = menu.findItem(R.id.send_again);
|
||||||
|
MenuItem copyUrl = menu.findItem(R.id.copy_url);
|
||||||
|
MenuItem downloadImage = menu.findItem(R.id.download_image);
|
||||||
|
if (this.selectedMessage.getType() != Message.TYPE_TEXT
|
||||||
|
|| this.selectedMessage.getDownloadable() != null) {
|
||||||
|
copyText.setVisible(false);
|
||||||
|
}
|
||||||
|
if (this.selectedMessage.getType() != Message.TYPE_IMAGE
|
||||||
|
|| this.selectedMessage.getDownloadable() != null) {
|
||||||
|
shareImage.setVisible(false);
|
||||||
|
}
|
||||||
|
if (this.selectedMessage.getStatus() != Message.STATUS_SEND_FAILED) {
|
||||||
|
sendAgain.setVisible(false);
|
||||||
|
}
|
||||||
|
if ((this.selectedMessage.getType() != Message.TYPE_IMAGE && this.selectedMessage
|
||||||
|
.getDownloadable() == null)
|
||||||
|
|| this.selectedMessage.getImageParams().url == null) {
|
||||||
|
copyUrl.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedMessage.getType() != Message.TYPE_TEXT
|
||||||
|
|| this.selectedMessage.getDownloadable() != null
|
||||||
|
|| !this.selectedMessage.bodyContainsDownloadable()) {
|
||||||
|
downloadImage.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onContextItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.share_image:
|
||||||
|
shareImage(selectedMessage);
|
||||||
|
return true;
|
||||||
|
case R.id.copy_text:
|
||||||
|
copyText(selectedMessage);
|
||||||
|
return true;
|
||||||
|
case R.id.send_again:
|
||||||
|
resendMessage(selectedMessage);
|
||||||
|
return true;
|
||||||
|
case R.id.copy_url:
|
||||||
|
copyUrl(selectedMessage);
|
||||||
|
return true;
|
||||||
|
case R.id.download_image:
|
||||||
|
downloadImage(selectedMessage);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onContextItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shareImage(Message message) {
|
||||||
|
Intent shareIntent = new Intent();
|
||||||
|
shareIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_STREAM,
|
||||||
|
activity.xmppConnectionService.getFileBackend()
|
||||||
|
.getJingleFileUri(message));
|
||||||
|
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
shareIntent.setType("image/webp");
|
||||||
|
activity.startActivity(Intent.createChooser(shareIntent,
|
||||||
|
getText(R.string.share_with)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyText(Message message) {
|
||||||
|
if (activity.copyTextToClipboard(message.getMergedBody(),
|
||||||
|
R.string.message_text)) {
|
||||||
|
Toast.makeText(activity, R.string.message_copied_to_clipboard,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resendMessage(Message message) {
|
||||||
|
activity.xmppConnectionService.resendFailedMessages(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyUrl(Message message) {
|
||||||
|
if (activity.copyTextToClipboard(
|
||||||
|
message.getImageParams().url.toString(), R.string.image_url)) {
|
||||||
|
Toast.makeText(activity, R.string.url_copied_to_clipboard,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadImage(Message message) {
|
||||||
|
activity.xmppConnectionService.getHttpConnectionManager()
|
||||||
|
.createNewConnection(message);
|
||||||
|
}
|
||||||
|
|
||||||
protected void privateMessageWith(String counterpart) {
|
protected void privateMessageWith(String counterpart) {
|
||||||
this.mEditMessage.setText("");
|
this.mEditMessage.setText("");
|
||||||
this.conversation.setNextPresence(counterpart);
|
this.conversation.setNextPresence(counterpart);
|
||||||
|
@ -356,6 +471,7 @@ public class ConversationFragment extends Fragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
|
mDecryptJobRunning = false;
|
||||||
super.onStop();
|
super.onStop();
|
||||||
if (this.conversation != null) {
|
if (this.conversation != null) {
|
||||||
this.conversation.setNextMessage(mEditMessage.getText().toString());
|
this.conversation.setNextMessage(mEditMessage.getText().toString());
|
||||||
|
@ -395,34 +511,6 @@ public class ConversationFragment extends Fragment {
|
||||||
updateMessages();
|
updateMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decryptMessage(Message message) {
|
|
||||||
PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
|
|
||||||
if (engine != null) {
|
|
||||||
engine.decrypt(message, new UiCallback<Message>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userInputRequried(PendingIntent pi, Message message) {
|
|
||||||
askForPassphraseIntent = pi.getIntentSender();
|
|
||||||
showSnackbar(R.string.openpgp_messages_found,
|
|
||||||
R.string.decrypt, clickToDecryptListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void success(Message message) {
|
|
||||||
activity.xmppConnectionService.databaseBackend
|
|
||||||
.updateMessage(message);
|
|
||||||
updateMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void error(int error, Message message) {
|
|
||||||
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
|
||||||
// updateMessages();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateMessages() {
|
public void updateMessages() {
|
||||||
if (getView() == null) {
|
if (getView() == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -458,13 +546,16 @@ public class ConversationFragment extends Fragment {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (Message message : this.conversation.getMessages()) {
|
for (Message message : this.conversation.getMessages()) {
|
||||||
if ((message.getEncryption() == Message.ENCRYPTION_PGP)
|
if (message.getEncryption() == Message.ENCRYPTION_PGP
|
||||||
&& ((message.getStatus() == Message.STATUS_RECEIVED) || (message
|
&& (message.getStatus() == Message.STATUS_RECEIVED || message
|
||||||
.getStatus() == Message.STATUS_SEND))) {
|
.getStatus() >= Message.STATUS_SEND)
|
||||||
decryptMessage(message);
|
&& message.getDownloadable() == null) {
|
||||||
break;
|
if (!mEncryptedMessages.contains(message)) {
|
||||||
|
mEncryptedMessages.add(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
decryptNext();
|
||||||
this.messageList.clear();
|
this.messageList.clear();
|
||||||
if (this.conversation.getMessages().size() == 0) {
|
if (this.conversation.getMessages().size() == 0) {
|
||||||
messagesLoaded = false;
|
messagesLoaded = false;
|
||||||
|
@ -476,7 +567,7 @@ public class ConversationFragment extends Fragment {
|
||||||
this.messageListAdapter.notifyDataSetChanged();
|
this.messageListAdapter.notifyDataSetChanged();
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||||
if (messageList.size() >= 1) {
|
if (messageList.size() >= 1) {
|
||||||
makeFingerprintWarning(conversation.getLatestEncryption());
|
makeFingerprintWarning();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!conversation.getMucOptions().online()
|
if (!conversation.getMucOptions().online()
|
||||||
|
@ -522,6 +613,40 @@ public class ConversationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void decryptNext() {
|
||||||
|
Message next = this.mEncryptedMessages.peek();
|
||||||
|
PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
|
||||||
|
|
||||||
|
if (next != null && engine != null && !mDecryptJobRunning) {
|
||||||
|
mDecryptJobRunning = true;
|
||||||
|
engine.decrypt(next, new UiCallback<Message>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userInputRequried(PendingIntent pi, Message message) {
|
||||||
|
mDecryptJobRunning = false;
|
||||||
|
askForPassphraseIntent = pi.getIntentSender();
|
||||||
|
showSnackbar(R.string.openpgp_messages_found,
|
||||||
|
R.string.decrypt, clickToDecryptListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void success(Message message) {
|
||||||
|
mDecryptJobRunning = false;
|
||||||
|
mEncryptedMessages.remove();
|
||||||
|
activity.xmppConnectionService.updateMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(int error, Message message) {
|
||||||
|
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
||||||
|
mDecryptJobRunning = false;
|
||||||
|
mEncryptedMessages.remove();
|
||||||
|
activity.xmppConnectionService.updateConversationUi();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void messageSent() {
|
private void messageSent() {
|
||||||
int size = this.messageList.size();
|
int size = this.messageList.size();
|
||||||
messagesView.setSelection(size - 1);
|
messagesView.setSelection(size - 1);
|
||||||
|
@ -594,14 +719,13 @@ public class ConversationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void makeFingerprintWarning(int latestEncryption) {
|
protected void makeFingerprintWarning() {
|
||||||
Set<String> knownFingerprints = conversation.getContact()
|
Set<String> knownFingerprints = conversation.getContact()
|
||||||
.getOtrFingerprints();
|
.getOtrFingerprints();
|
||||||
if ((latestEncryption == Message.ENCRYPTION_OTR)
|
if (conversation.hasValidOtrSession()
|
||||||
&& (conversation.hasValidOtrSession()
|
|
||||||
&& (!conversation.isMuted())
|
&& (!conversation.isMuted())
|
||||||
&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
|
&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
|
||||||
.contains(conversation.getOtrFingerprint())))) {
|
.contains(conversation.getOtrFingerprint()))) {
|
||||||
showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
|
showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
|
||||||
new OnClickListener() {
|
new OnClickListener() {
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
@ -17,6 +15,7 @@ import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
@ -43,7 +42,7 @@ public class EditAccountActivity extends XmppActivity {
|
||||||
private TextView mServerInfoPep;
|
private TextView mServerInfoPep;
|
||||||
private TextView mSessionEst;
|
private TextView mSessionEst;
|
||||||
private TextView mOtrFingerprint;
|
private TextView mOtrFingerprint;
|
||||||
private TextView mOtrFingerprintHeadline;
|
private RelativeLayout mOtrFingerprintBox;
|
||||||
private ImageButton mOtrFingerprintToClipboardButton;
|
private ImageButton mOtrFingerprintToClipboardButton;
|
||||||
|
|
||||||
private String jidToEdit;
|
private String jidToEdit;
|
||||||
|
@ -277,7 +276,7 @@ public class EditAccountActivity extends XmppActivity {
|
||||||
this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm);
|
this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm);
|
||||||
this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
|
this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
|
||||||
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
|
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
|
||||||
this.mOtrFingerprintHeadline = (TextView) findViewById(R.id.otr_fingerprint_headline);
|
this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
|
||||||
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
|
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
|
||||||
this.mSaveButton = (Button) findViewById(R.id.save_button);
|
this.mSaveButton = (Button) findViewById(R.id.save_button);
|
||||||
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
|
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
|
||||||
|
@ -378,8 +377,7 @@ public class EditAccountActivity extends XmppActivity {
|
||||||
final String fingerprint = this.mAccount
|
final String fingerprint = this.mAccount
|
||||||
.getOtrFingerprint(xmppConnectionService);
|
.getOtrFingerprint(xmppConnectionService);
|
||||||
if (fingerprint != null) {
|
if (fingerprint != null) {
|
||||||
this.mOtrFingerprintHeadline.setVisibility(View.VISIBLE);
|
this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
|
||||||
this.mOtrFingerprint.setVisibility(View.VISIBLE);
|
|
||||||
this.mOtrFingerprint.setText(fingerprint);
|
this.mOtrFingerprint.setText(fingerprint);
|
||||||
this.mOtrFingerprintToClipboardButton
|
this.mOtrFingerprintToClipboardButton
|
||||||
.setVisibility(View.VISIBLE);
|
.setVisibility(View.VISIBLE);
|
||||||
|
@ -389,7 +387,7 @@ public class EditAccountActivity extends XmppActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
|
||||||
if (OtrFingerprintToClipBoard(fingerprint)) {
|
if (copyTextToClipboard(fingerprint,R.string.otr_fingerprint)) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
EditAccountActivity.this,
|
EditAccountActivity.this,
|
||||||
R.string.toast_message_otr_fingerprint,
|
R.string.toast_message_otr_fingerprint,
|
||||||
|
@ -398,9 +396,7 @@ public class EditAccountActivity extends XmppActivity {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.mOtrFingerprintToClipboardButton.setVisibility(View.GONE);
|
this.mOtrFingerprintBox.setVisibility(View.GONE);
|
||||||
this.mOtrFingerprint.setVisibility(View.GONE);
|
|
||||||
this.mOtrFingerprintHeadline.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.mAccount.errorStatus()) {
|
if (this.mAccount.errorStatus()) {
|
||||||
|
@ -411,15 +407,4 @@ public class EditAccountActivity extends XmppActivity {
|
||||||
this.mStats.setVisibility(View.GONE);
|
this.mStats.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean OtrFingerprintToClipBoard(String fingerprint) {
|
|
||||||
ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
|
||||||
String label = getResources().getString(R.string.otr_fingerprint);
|
|
||||||
if (mClipBoardManager != null) {
|
|
||||||
ClipData mClipData = ClipData.newPlainText(label, fingerprint);
|
|
||||||
mClipBoardManager.setPrimaryClip(mClipData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,8 @@ public class ManageAccountActivity extends XmppActivity {
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||||
ContextMenuInfo menuInfo) {
|
ContextMenuInfo menuInfo) {
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
ManageAccountActivity.this.getMenuInflater().inflate(R.menu.manageaccounts_context, menu);
|
ManageAccountActivity.this.getMenuInflater().inflate(
|
||||||
|
R.menu.manageaccounts_context, menu);
|
||||||
AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
|
AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
|
||||||
this.selectedAccount = accountList.get(acmi.position);
|
this.selectedAccount = accountList.get(acmi.position);
|
||||||
if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) {
|
if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) {
|
||||||
|
@ -122,6 +123,7 @@ public class ManageAccountActivity extends XmppActivity {
|
||||||
return true;
|
return true;
|
||||||
case R.id.mgmt_account_announce_pgp:
|
case R.id.mgmt_account_announce_pgp:
|
||||||
publishOpenPGPPublicKey(selectedAccount);
|
publishOpenPGPPublicKey(selectedAccount);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onContextItemSelected(item);
|
return super.onContextItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -187,7 +189,8 @@ public class ManageAccountActivity extends XmppActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteAccount(final Account account) {
|
private void deleteAccount(final Account account) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(ManageAccountActivity.this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(
|
||||||
|
ManageAccountActivity.this);
|
||||||
builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
|
builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
|
||||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||||
builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
|
builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
|
||||||
|
|
|
@ -158,8 +158,8 @@ public class PublishProfilePictureActivity extends XmppActivity {
|
||||||
if (this.avatarUri == null) {
|
if (this.avatarUri == null) {
|
||||||
if (this.account.getAvatar() != null
|
if (this.account.getAvatar() != null
|
||||||
|| this.defaultUri == null) {
|
|| this.defaultUri == null) {
|
||||||
this.avatar.setImageBitmap(this.account.getImage(
|
this.avatar.setImageBitmap(avatarService().get(account,
|
||||||
getApplicationContext(), 384));
|
getPixel(194)));
|
||||||
if (this.defaultUri != null) {
|
if (this.defaultUri != null) {
|
||||||
this.avatar
|
this.avatar
|
||||||
.setOnLongClickListener(this.backToDefaultListener);
|
.setOnLongClickListener(this.backToDefaultListener);
|
||||||
|
|
|
@ -463,11 +463,11 @@ public class StartConversationActivity extends XmppActivity {
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
this.mOptionsMenu = menu;
|
this.mOptionsMenu = menu;
|
||||||
getMenuInflater().inflate(R.menu.start_conversation, menu);
|
getMenuInflater().inflate(R.menu.start_conversation, menu);
|
||||||
MenuItem menuCreateContact = (MenuItem) menu
|
MenuItem menuCreateContact = menu
|
||||||
.findItem(R.id.action_create_contact);
|
.findItem(R.id.action_create_contact);
|
||||||
MenuItem menuCreateConference = (MenuItem) menu
|
MenuItem menuCreateConference = menu
|
||||||
.findItem(R.id.action_join_conference);
|
.findItem(R.id.action_join_conference);
|
||||||
mMenuSearchView = (MenuItem) menu.findItem(R.id.action_search);
|
mMenuSearchView = menu.findItem(R.id.action_search);
|
||||||
mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
|
mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
|
||||||
View mSearchView = mMenuSearchView.getActionView();
|
View mSearchView = mMenuSearchView.getActionView();
|
||||||
mSearchEditText = (EditText) mSearchView
|
mSearchEditText = (EditText) mSearchView
|
||||||
|
|
|
@ -12,6 +12,7 @@ import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Presences;
|
import eu.siacs.conversations.entities.Presences;
|
||||||
|
import eu.siacs.conversations.services.AvatarService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
||||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||||
|
@ -20,6 +21,8 @@ import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.AlertDialog.Builder;
|
import android.app.AlertDialog.Builder;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
@ -55,7 +58,6 @@ public abstract class XmppActivity extends Activity {
|
||||||
|
|
||||||
public XmppConnectionService xmppConnectionService;
|
public XmppConnectionService xmppConnectionService;
|
||||||
public boolean xmppConnectionServiceBound = false;
|
public boolean xmppConnectionServiceBound = false;
|
||||||
protected boolean handledViewIntent = false;
|
|
||||||
|
|
||||||
protected int mPrimaryTextColor;
|
protected int mPrimaryTextColor;
|
||||||
protected int mSecondaryTextColor;
|
protected int mSecondaryTextColor;
|
||||||
|
@ -400,8 +402,7 @@ public abstract class XmppActivity extends Activity {
|
||||||
private void quickEdit(final String previousValue,
|
private void quickEdit(final String previousValue,
|
||||||
final OnValueEdited callback, boolean password) {
|
final OnValueEdited callback, boolean password) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
View view = (View) getLayoutInflater()
|
View view = getLayoutInflater().inflate(R.layout.quickedit, null);
|
||||||
.inflate(R.layout.quickedit, null);
|
|
||||||
final EditText editor = (EditText) view.findViewById(R.id.editor);
|
final EditText editor = (EditText) view.findViewById(R.id.editor);
|
||||||
OnClickListener mClickListener = new OnClickListener() {
|
OnClickListener mClickListener = new OnClickListener() {
|
||||||
|
|
||||||
|
@ -448,7 +449,7 @@ public abstract class XmppActivity extends Activity {
|
||||||
listener.onPresenceSelected();
|
listener.onPresenceSelected();
|
||||||
}
|
}
|
||||||
} else if (presences.size() == 1) {
|
} else if (presences.size() == 1) {
|
||||||
String presence = (String) presences.asStringArray()[0];
|
String presence = presences.asStringArray()[0];
|
||||||
conversation.setNextPresence(presence);
|
conversation.setNextPresence(presence);
|
||||||
listener.onPresenceSelected();
|
listener.onPresenceSelected();
|
||||||
} else {
|
} else {
|
||||||
|
@ -526,6 +527,26 @@ public abstract class XmppActivity extends Activity {
|
||||||
return this.mSecondaryBackgroundColor;
|
return this.mSecondaryBackgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPixel(int dp) {
|
||||||
|
DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||||
|
return ((int) (dp * metrics.density));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean copyTextToClipboard(String text,int labelResId) {
|
||||||
|
ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
String label = getResources().getString(labelResId);
|
||||||
|
if (mClipBoardManager != null) {
|
||||||
|
ClipData mClipData = ClipData.newPlainText(label, text);
|
||||||
|
mClipBoardManager.setPrimaryClip(mClipData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvatarService avatarService() {
|
||||||
|
return xmppConnectionService.getAvatarService();
|
||||||
|
}
|
||||||
|
|
||||||
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
|
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
|
||||||
private final WeakReference<ImageView> imageViewReference;
|
private final WeakReference<ImageView> imageViewReference;
|
||||||
private Message message = null;
|
private Message message = null;
|
||||||
|
|
|
@ -28,13 +28,14 @@ public class AccountAdapter extends ArrayAdapter<Account> {
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
LayoutInflater inflater = (LayoutInflater) getContext()
|
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
view = (View) inflater.inflate(R.layout.account_row, parent, false);
|
view = inflater.inflate(R.layout.account_row, parent, false);
|
||||||
}
|
}
|
||||||
TextView jid = (TextView) view.findViewById(R.id.account_jid);
|
TextView jid = (TextView) view.findViewById(R.id.account_jid);
|
||||||
jid.setText(account.getJid());
|
jid.setText(account.getJid());
|
||||||
TextView statusView = (TextView) view.findViewById(R.id.account_status);
|
TextView statusView = (TextView) view.findViewById(R.id.account_status);
|
||||||
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
|
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
|
||||||
imageView.setImageBitmap(account.getImage(activity, 48));
|
imageView.setImageBitmap(activity.avatarService().get(account,
|
||||||
|
activity.getPixel(48)));
|
||||||
switch (account.getStatus()) {
|
switch (account.getStatus()) {
|
||||||
case Account.STATUS_DISABLED:
|
case Account.STATUS_DISABLED:
|
||||||
statusView.setText(getContext().getString(
|
statusView.setText(getContext().getString(
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.List;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
import eu.siacs.conversations.ui.ConversationActivity;
|
||||||
import eu.siacs.conversations.ui.XmppActivity;
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
|
@ -34,14 +35,14 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
LayoutInflater inflater = (LayoutInflater) activity
|
LayoutInflater inflater = (LayoutInflater) activity
|
||||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
view = (View) inflater.inflate(R.layout.conversation_list_row,
|
view = inflater.inflate(R.layout.conversation_list_row,
|
||||||
parent, false);
|
parent, false);
|
||||||
}
|
}
|
||||||
Conversation conv = getItem(position);
|
Conversation conversation = getItem(position);
|
||||||
if (this.activity instanceof ConversationActivity) {
|
if (this.activity instanceof ConversationActivity) {
|
||||||
ConversationActivity activity = (ConversationActivity) this.activity;
|
ConversationActivity activity = (ConversationActivity) this.activity;
|
||||||
if (!activity.isConversationsOverviewHideable()) {
|
if (!activity.isConversationsOverviewHideable()) {
|
||||||
if (conv == activity.getSelectedConversation()) {
|
if (conversation == activity.getSelectedConversation()) {
|
||||||
view.setBackgroundColor(activity
|
view.setBackgroundColor(activity
|
||||||
.getSecondaryBackgroundColor());
|
.getSecondaryBackgroundColor());
|
||||||
} else {
|
} else {
|
||||||
|
@ -53,65 +54,85 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|
||||||
}
|
}
|
||||||
TextView convName = (TextView) view
|
TextView convName = (TextView) view
|
||||||
.findViewById(R.id.conversation_name);
|
.findViewById(R.id.conversation_name);
|
||||||
if (conv.getMode() == Conversation.MODE_SINGLE
|
if (conversation.getMode() == Conversation.MODE_SINGLE
|
||||||
|| activity.useSubjectToIdentifyConference()) {
|
|| activity.useSubjectToIdentifyConference()) {
|
||||||
convName.setText(conv.getName());
|
convName.setText(conversation.getName());
|
||||||
} else {
|
} else {
|
||||||
convName.setText(conv.getContactJid().split("/")[0]);
|
convName.setText(conversation.getContactJid().split("/")[0]);
|
||||||
}
|
}
|
||||||
TextView convLastMsg = (TextView) view
|
TextView mLastMessage = (TextView) view
|
||||||
.findViewById(R.id.conversation_lastmsg);
|
.findViewById(R.id.conversation_lastmsg);
|
||||||
|
TextView mTimestamp = (TextView) view
|
||||||
|
.findViewById(R.id.conversation_lastupdate);
|
||||||
ImageView imagePreview = (ImageView) view
|
ImageView imagePreview = (ImageView) view
|
||||||
.findViewById(R.id.conversation_lastimage);
|
.findViewById(R.id.conversation_lastimage);
|
||||||
|
|
||||||
Message latestMessage = conv.getLatestMessage();
|
Message message = conversation.getLatestMessage();
|
||||||
|
|
||||||
if (latestMessage.getType() == Message.TYPE_TEXT
|
if (!conversation.isRead()) {
|
||||||
|| latestMessage.getType() == Message.TYPE_PRIVATE) {
|
|
||||||
if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP)
|
|
||||||
&& (latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) {
|
|
||||||
String body = Config.PARSE_EMOTICONS ? UIHelper
|
|
||||||
.transformAsciiEmoticons(latestMessage.getBody())
|
|
||||||
: latestMessage.getBody();
|
|
||||||
convLastMsg.setText(body);
|
|
||||||
} else {
|
|
||||||
convLastMsg.setText(R.string.encrypted_message_received);
|
|
||||||
}
|
|
||||||
convLastMsg.setVisibility(View.VISIBLE);
|
|
||||||
imagePreview.setVisibility(View.GONE);
|
|
||||||
} else if (latestMessage.getType() == Message.TYPE_IMAGE) {
|
|
||||||
if (latestMessage.getStatus() >= Message.STATUS_RECEIVED) {
|
|
||||||
convLastMsg.setVisibility(View.GONE);
|
|
||||||
imagePreview.setVisibility(View.VISIBLE);
|
|
||||||
activity.loadBitmap(latestMessage, imagePreview);
|
|
||||||
} else {
|
|
||||||
convLastMsg.setVisibility(View.VISIBLE);
|
|
||||||
imagePreview.setVisibility(View.GONE);
|
|
||||||
if (latestMessage.getStatus() == Message.STATUS_RECEIVED_OFFER) {
|
|
||||||
convLastMsg.setText(R.string.image_offered_for_download);
|
|
||||||
} else if (latestMessage.getStatus() == Message.STATUS_RECEIVING) {
|
|
||||||
convLastMsg.setText(R.string.receiving_image);
|
|
||||||
} else {
|
|
||||||
convLastMsg.setText("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!conv.isRead()) {
|
|
||||||
convName.setTypeface(null, Typeface.BOLD);
|
convName.setTypeface(null, Typeface.BOLD);
|
||||||
convLastMsg.setTypeface(null, Typeface.BOLD);
|
|
||||||
} else {
|
} else {
|
||||||
convName.setTypeface(null, Typeface.NORMAL);
|
convName.setTypeface(null, Typeface.NORMAL);
|
||||||
convLastMsg.setTypeface(null, Typeface.NORMAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
((TextView) view.findViewById(R.id.conversation_lastupdate))
|
if (message.getType() == Message.TYPE_IMAGE
|
||||||
.setText(UIHelper.readableTimeDifference(getContext(), conv
|
|| message.getDownloadable() != null) {
|
||||||
.getLatestMessage().getTimeSent()));
|
Downloadable d = message.getDownloadable();
|
||||||
|
if (conversation.isRead()) {
|
||||||
|
mLastMessage.setTypeface(null, Typeface.ITALIC);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
|
||||||
|
}
|
||||||
|
if (d != null) {
|
||||||
|
mLastMessage.setVisibility(View.VISIBLE);
|
||||||
|
imagePreview.setVisibility(View.GONE);
|
||||||
|
if (d.getStatus() == Downloadable.STATUS_CHECKING) {
|
||||||
|
mLastMessage.setText(R.string.checking_image);
|
||||||
|
} else if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
||||||
|
mLastMessage.setText(R.string.receiving_image);
|
||||||
|
} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
|
||||||
|
mLastMessage.setText(R.string.image_offered_for_download);
|
||||||
|
} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||||
|
mLastMessage.setText(R.string.image_offered_for_download);
|
||||||
|
} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
|
||||||
|
mLastMessage.setText(R.string.image_file_deleted);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setText("");
|
||||||
|
}
|
||||||
|
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
|
imagePreview.setVisibility(View.GONE);
|
||||||
|
mLastMessage.setVisibility(View.VISIBLE);
|
||||||
|
mLastMessage.setText(R.string.encrypted_message_received);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setVisibility(View.GONE);
|
||||||
|
imagePreview.setVisibility(View.VISIBLE);
|
||||||
|
activity.loadBitmap(message, imagePreview);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((message.getEncryption() != Message.ENCRYPTION_PGP)
|
||||||
|
&& (message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) {
|
||||||
|
String body = Config.PARSE_EMOTICONS ? UIHelper
|
||||||
|
.transformAsciiEmoticons(message.getBody()) : message
|
||||||
|
.getBody();
|
||||||
|
mLastMessage.setText(body);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setText(R.string.encrypted_message_received);
|
||||||
|
}
|
||||||
|
if (!conversation.isRead()) {
|
||||||
|
mLastMessage.setTypeface(null, Typeface.BOLD);
|
||||||
|
} else {
|
||||||
|
mLastMessage.setTypeface(null, Typeface.NORMAL);
|
||||||
|
}
|
||||||
|
mLastMessage.setVisibility(View.VISIBLE);
|
||||||
|
imagePreview.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
mTimestamp.setText(UIHelper.readableTimeDifference(getContext(),
|
||||||
|
conversation.getLatestMessage().getTimeSent()));
|
||||||
|
|
||||||
ImageView profilePicture = (ImageView) view
|
ImageView profilePicture = (ImageView) view
|
||||||
.findViewById(R.id.conversation_image);
|
.findViewById(R.id.conversation_image);
|
||||||
profilePicture.setImageBitmap(conv.getImage(activity, 56));
|
profilePicture.setImageBitmap(activity.avatarService().get(
|
||||||
|
conversation, activity.getPixel(56)));
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,11 +47,11 @@ public class KnownHostsAdapter extends ArrayAdapter<String> {
|
||||||
@Override
|
@Override
|
||||||
protected void publishResults(CharSequence constraint,
|
protected void publishResults(CharSequence constraint,
|
||||||
FilterResults results) {
|
FilterResults results) {
|
||||||
ArrayList<String> filteredList = (ArrayList<String>) results.values;
|
ArrayList filteredList = (ArrayList) results.values;
|
||||||
if (results != null && results.count > 0) {
|
if (results != null && results.count > 0) {
|
||||||
clear();
|
clear();
|
||||||
for (String c : filteredList) {
|
for (Object c : filteredList) {
|
||||||
add(c);
|
add((String) c);
|
||||||
}
|
}
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
@ -71,4 +71,4 @@ public class KnownHostsAdapter extends ArrayAdapter<String> {
|
||||||
public Filter getFilter() {
|
public Filter getFilter() {
|
||||||
return domainFilter;
|
return domainFilter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.ListItem;
|
import eu.siacs.conversations.entities.ListItem;
|
||||||
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -14,8 +15,11 @@ import android.widget.TextView;
|
||||||
|
|
||||||
public class ListItemAdapter extends ArrayAdapter<ListItem> {
|
public class ListItemAdapter extends ArrayAdapter<ListItem> {
|
||||||
|
|
||||||
public ListItemAdapter(Context context, List<ListItem> objects) {
|
protected XmppActivity activity;
|
||||||
super(context, 0, objects);
|
|
||||||
|
public ListItemAdapter(XmppActivity activity, List<ListItem> objects) {
|
||||||
|
super(activity, 0, objects);
|
||||||
|
this.activity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,7 +28,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
|
||||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
ListItem item = getItem(position);
|
ListItem item = getItem(position);
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
view = (View) inflater.inflate(R.layout.contact, parent, false);
|
view = inflater.inflate(R.layout.contact, parent, false);
|
||||||
}
|
}
|
||||||
TextView name = (TextView) view.findViewById(R.id.contact_display_name);
|
TextView name = (TextView) view.findViewById(R.id.contact_display_name);
|
||||||
TextView jid = (TextView) view.findViewById(R.id.contact_jid);
|
TextView jid = (TextView) view.findViewById(R.id.contact_jid);
|
||||||
|
@ -32,7 +36,8 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
|
||||||
|
|
||||||
jid.setText(item.getJid());
|
jid.setText(item.getJid());
|
||||||
name.setText(item.getDisplayName());
|
name.setText(item.getDisplayName());
|
||||||
picture.setImageBitmap(item.getImage(48, getContext()));
|
picture.setImageBitmap(activity.avatarService().get(item,
|
||||||
|
activity.getPixel(48)));
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.siacs.conversations.ui.adapter;
|
package eu.siacs.conversations.ui.adapter;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
@ -9,11 +8,10 @@ import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.entities.Message.ImageParams;
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
import eu.siacs.conversations.ui.ConversationActivity;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
|
@ -40,31 +38,26 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
|
|
||||||
private ConversationActivity activity;
|
private ConversationActivity activity;
|
||||||
|
|
||||||
private Bitmap accountBitmap;
|
|
||||||
|
|
||||||
private BitmapCache mBitmapCache = new BitmapCache();
|
|
||||||
private DisplayMetrics metrics;
|
private DisplayMetrics metrics;
|
||||||
|
|
||||||
private OnContactPictureClicked mOnContactPictureClickedListener;
|
private OnContactPictureClicked mOnContactPictureClickedListener;
|
||||||
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
|
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
|
||||||
|
|
||||||
|
private OnLongClickListener openContextMenu = new OnLongClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
v.showContextMenu();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
|
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
|
||||||
super(activity, 0, messages);
|
super(activity, 0, messages);
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
metrics = getContext().getResources().getDisplayMetrics();
|
metrics = getContext().getResources().getDisplayMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap getSelfBitmap() {
|
|
||||||
if (this.accountBitmap == null) {
|
|
||||||
|
|
||||||
if (getCount() > 0) {
|
|
||||||
this.accountBitmap = getItem(0).getConversation().getAccount()
|
|
||||||
.getImage(getContext(), 48);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.accountBitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnContactPictureClicked(OnContactPictureClicked listener) {
|
public void setOnContactPictureClicked(OnContactPictureClicked listener) {
|
||||||
this.mOnContactPictureClickedListener = listener;
|
this.mOnContactPictureClickedListener = listener;
|
||||||
}
|
}
|
||||||
|
@ -101,13 +94,14 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
|
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
|
||||||
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
|
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
|
||||||
if (message.getType() == Message.TYPE_IMAGE) {
|
if (message.getType() == Message.TYPE_IMAGE
|
||||||
String[] fileParams = message.getBody().split(",");
|
|| message.getDownloadable() != null) {
|
||||||
try {
|
ImageParams params = message.getImageParams();
|
||||||
long size = Long.parseLong(fileParams[0]);
|
if (params.size != 0) {
|
||||||
filesize = size / 1024 + " KB";
|
filesize = params.size / 1024 + " KB";
|
||||||
} catch (NumberFormatException e) {
|
}
|
||||||
filesize = "0 KB";
|
if (message.getDownloadable() != null && message.getDownloadable().getStatus() == Downloadable.STATUS_FAILED) {
|
||||||
|
error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (message.getMergedStatus()) {
|
switch (message.getMergedStatus()) {
|
||||||
|
@ -134,14 +128,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
info = getContext().getString(R.string.send_failed);
|
info = getContext().getString(R.string.send_failed);
|
||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
case Message.STATUS_SEND_REJECTED:
|
|
||||||
info = getContext().getString(R.string.send_rejected);
|
|
||||||
error = true;
|
|
||||||
break;
|
|
||||||
case Message.STATUS_RECEPTION_FAILED:
|
|
||||||
info = getContext().getString(R.string.reception_failed);
|
|
||||||
error = true;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if (multiReceived) {
|
if (multiReceived) {
|
||||||
Contact contact = message.getContact();
|
Contact contact = message.getContact();
|
||||||
|
@ -268,6 +254,22 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.messageBody.setTextIsSelectable(true);
|
viewHolder.messageBody.setTextIsSelectable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayDownloadableMessage(ViewHolder viewHolder,
|
||||||
|
final Message message, int resid) {
|
||||||
|
viewHolder.image.setVisibility(View.GONE);
|
||||||
|
viewHolder.messageBody.setVisibility(View.GONE);
|
||||||
|
viewHolder.download_button.setVisibility(View.VISIBLE);
|
||||||
|
viewHolder.download_button.setText(resid);
|
||||||
|
viewHolder.download_button.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
startDonwloadable(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewHolder.download_button.setOnLongClickListener(openContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
private void displayImageMessage(ViewHolder viewHolder,
|
private void displayImageMessage(ViewHolder viewHolder,
|
||||||
final Message message) {
|
final Message message) {
|
||||||
if (viewHolder.download_button != null) {
|
if (viewHolder.download_button != null) {
|
||||||
|
@ -275,23 +277,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
viewHolder.messageBody.setVisibility(View.GONE);
|
viewHolder.messageBody.setVisibility(View.GONE);
|
||||||
viewHolder.image.setVisibility(View.VISIBLE);
|
viewHolder.image.setVisibility(View.VISIBLE);
|
||||||
String[] fileParams = message.getBody().split(",");
|
ImageParams params = message.getImageParams();
|
||||||
if (fileParams.length == 3) {
|
double target = metrics.density * 288;
|
||||||
double target = metrics.density * 288;
|
int scalledW;
|
||||||
int w = Integer.parseInt(fileParams[1]);
|
int scalledH;
|
||||||
int h = Integer.parseInt(fileParams[2]);
|
if (params.width <= params.height) {
|
||||||
int scalledW;
|
scalledW = (int) (params.width / ((double) params.height / target));
|
||||||
int scalledH;
|
scalledH = (int) target;
|
||||||
if (w <= h) {
|
} else {
|
||||||
scalledW = (int) (w / ((double) h / target));
|
scalledW = (int) target;
|
||||||
scalledH = (int) target;
|
scalledH = (int) (params.height / ((double) params.width / target));
|
||||||
} else {
|
|
||||||
scalledW = (int) target;
|
|
||||||
scalledH = (int) (h / ((double) w / target));
|
|
||||||
}
|
|
||||||
viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
|
|
||||||
scalledW, scalledH));
|
|
||||||
}
|
}
|
||||||
|
viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
|
||||||
|
scalledW, scalledH));
|
||||||
activity.loadBitmap(message, viewHolder.image);
|
activity.loadBitmap(message, viewHolder.image);
|
||||||
viewHolder.image.setOnClickListener(new OnClickListener() {
|
viewHolder.image.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
|
@ -303,23 +301,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
getContext().startActivity(intent);
|
getContext().startActivity(intent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
|
viewHolder.image.setOnLongClickListener(openContextMenu);
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onLongClick(View v) {
|
|
||||||
Intent shareIntent = new Intent();
|
|
||||||
shareIntent.setAction(Intent.ACTION_SEND);
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_STREAM,
|
|
||||||
activity.xmppConnectionService.getFileBackend()
|
|
||||||
.getJingleFileUri(message));
|
|
||||||
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
shareIntent.setType("image/webp");
|
|
||||||
getContext().startActivity(
|
|
||||||
Intent.createChooser(shareIntent,
|
|
||||||
getContext().getText(R.string.share_with)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -331,17 +313,22 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder = new ViewHolder();
|
viewHolder = new ViewHolder();
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case NULL:
|
case NULL:
|
||||||
view = (View) activity.getLayoutInflater().inflate(
|
view = activity.getLayoutInflater().inflate(
|
||||||
R.layout.message_null, parent, false);
|
R.layout.message_null, parent, false);
|
||||||
break;
|
break;
|
||||||
case SENT:
|
case SENT:
|
||||||
view = (View) activity.getLayoutInflater().inflate(
|
view = activity.getLayoutInflater().inflate(
|
||||||
R.layout.message_sent, parent, false);
|
R.layout.message_sent, parent, false);
|
||||||
viewHolder.message_box = (LinearLayout) view
|
viewHolder.message_box = (LinearLayout) view
|
||||||
.findViewById(R.id.message_box);
|
.findViewById(R.id.message_box);
|
||||||
viewHolder.contact_picture = (ImageView) view
|
viewHolder.contact_picture = (ImageView) view
|
||||||
.findViewById(R.id.message_photo);
|
.findViewById(R.id.message_photo);
|
||||||
viewHolder.contact_picture.setImageBitmap(getSelfBitmap());
|
viewHolder.contact_picture.setImageBitmap(activity
|
||||||
|
.avatarService().get(
|
||||||
|
item.getConversation().getAccount(),
|
||||||
|
activity.getPixel(48)));
|
||||||
|
viewHolder.download_button = (Button) view
|
||||||
|
.findViewById(R.id.download_button);
|
||||||
viewHolder.indicator = (ImageView) view
|
viewHolder.indicator = (ImageView) view
|
||||||
.findViewById(R.id.security_indicator);
|
.findViewById(R.id.security_indicator);
|
||||||
viewHolder.image = (ImageView) view
|
viewHolder.image = (ImageView) view
|
||||||
|
@ -355,21 +342,18 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
view.setTag(viewHolder);
|
view.setTag(viewHolder);
|
||||||
break;
|
break;
|
||||||
case RECEIVED:
|
case RECEIVED:
|
||||||
view = (View) activity.getLayoutInflater().inflate(
|
view = activity.getLayoutInflater().inflate(
|
||||||
R.layout.message_received, parent, false);
|
R.layout.message_received, parent, false);
|
||||||
viewHolder.message_box = (LinearLayout) view
|
viewHolder.message_box = (LinearLayout) view
|
||||||
.findViewById(R.id.message_box);
|
.findViewById(R.id.message_box);
|
||||||
viewHolder.contact_picture = (ImageView) view
|
viewHolder.contact_picture = (ImageView) view
|
||||||
.findViewById(R.id.message_photo);
|
.findViewById(R.id.message_photo);
|
||||||
|
|
||||||
viewHolder.download_button = (Button) view
|
viewHolder.download_button = (Button) view
|
||||||
.findViewById(R.id.download_button);
|
.findViewById(R.id.download_button);
|
||||||
|
|
||||||
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
||||||
|
viewHolder.contact_picture.setImageBitmap(activity
|
||||||
viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
|
.avatarService().get(item.getContact(),
|
||||||
item.getConversation().getContact(), getContext()));
|
activity.getPixel(48)));
|
||||||
|
|
||||||
}
|
}
|
||||||
viewHolder.indicator = (ImageView) view
|
viewHolder.indicator = (ImageView) view
|
||||||
.findViewById(R.id.security_indicator);
|
.findViewById(R.id.security_indicator);
|
||||||
|
@ -382,15 +366,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
view.setTag(viewHolder);
|
view.setTag(viewHolder);
|
||||||
break;
|
break;
|
||||||
case STATUS:
|
case STATUS:
|
||||||
view = (View) activity.getLayoutInflater().inflate(
|
view = activity.getLayoutInflater().inflate(
|
||||||
R.layout.message_status, parent, false);
|
R.layout.message_status, parent, false);
|
||||||
viewHolder.contact_picture = (ImageView) view
|
viewHolder.contact_picture = (ImageView) view
|
||||||
.findViewById(R.id.message_photo);
|
.findViewById(R.id.message_photo);
|
||||||
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
||||||
|
|
||||||
viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
|
viewHolder.contact_picture.setImageBitmap(activity
|
||||||
item.getConversation().getContact(), getContext()));
|
.avatarService().get(
|
||||||
viewHolder.contact_picture.setAlpha(128);
|
item.getConversation().getContact(),
|
||||||
|
activity.getPixel(32)));
|
||||||
|
viewHolder.contact_picture.setAlpha(0.5f);
|
||||||
viewHolder.contact_picture
|
viewHolder.contact_picture
|
||||||
.setOnClickListener(new OnClickListener() {
|
.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
|
@ -465,38 +451,40 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
|
if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
|
||||||
Contact contact = item.getContact();
|
Contact contact = item.getContact();
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
|
viewHolder.contact_picture.setImageBitmap(activity
|
||||||
contact, getContext()));
|
.avatarService()
|
||||||
|
.get(contact, activity.getPixel(48)));
|
||||||
} else {
|
} else {
|
||||||
String name = item.getPresence();
|
String name = item.getPresence();
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = item.getCounterpart();
|
name = item.getCounterpart();
|
||||||
}
|
}
|
||||||
viewHolder.contact_picture.setImageBitmap(mBitmapCache.get(
|
viewHolder.contact_picture.setImageBitmap(activity
|
||||||
name, getContext()));
|
.avatarService().get(name, activity.getPixel(48)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.getType() == Message.TYPE_IMAGE) {
|
if (item.getType() == Message.TYPE_IMAGE
|
||||||
if (item.getStatus() == Message.STATUS_RECEIVING) {
|
|| item.getDownloadable() != null) {
|
||||||
|
Downloadable d = item.getDownloadable();
|
||||||
|
if (d != null && d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
||||||
displayInfoMessage(viewHolder, R.string.receiving_image);
|
displayInfoMessage(viewHolder, R.string.receiving_image);
|
||||||
} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
|
} else if (d != null
|
||||||
viewHolder.image.setVisibility(View.GONE);
|
&& d.getStatus() == Downloadable.STATUS_CHECKING) {
|
||||||
viewHolder.messageBody.setVisibility(View.GONE);
|
displayInfoMessage(viewHolder, R.string.checking_image);
|
||||||
viewHolder.download_button.setVisibility(View.VISIBLE);
|
} else if (d != null
|
||||||
viewHolder.download_button
|
&& d.getStatus() == Downloadable.STATUS_DELETED) {
|
||||||
.setOnClickListener(new OnClickListener() {
|
displayInfoMessage(viewHolder, R.string.image_file_deleted);
|
||||||
|
} else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER) {
|
||||||
@Override
|
displayDownloadableMessage(viewHolder, item,
|
||||||
public void onClick(View v) {
|
R.string.download_image);
|
||||||
Downloadable downloadable = item
|
} else if (d != null
|
||||||
.getDownloadable();
|
&& d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||||
if (downloadable != null) {
|
displayDownloadableMessage(viewHolder, item,
|
||||||
downloadable.start();
|
R.string.check_image_filesize);
|
||||||
}
|
} else if (d != null && d.getStatus() == Downloadable.STATUS_FAILED) {
|
||||||
}
|
displayInfoMessage(viewHolder, R.string.image_transmission_failed);
|
||||||
});
|
|
||||||
} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
|
} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
|
||||||
|| (item.getEncryption() == Message.ENCRYPTION_NONE)
|
|| (item.getEncryption() == Message.ENCRYPTION_NONE)
|
||||||
|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
|
|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
|
||||||
|
@ -534,6 +522,16 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startDonwloadable(Message message) {
|
||||||
|
Downloadable downloadable = message.getDownloadable();
|
||||||
|
if (downloadable != null) {
|
||||||
|
if (!downloadable.start()) {
|
||||||
|
Toast.makeText(activity, R.string.not_connected_try_again,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ViewHolder {
|
private static class ViewHolder {
|
||||||
|
|
||||||
protected LinearLayout message_box;
|
protected LinearLayout message_box;
|
||||||
|
@ -547,30 +545,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BitmapCache {
|
|
||||||
private HashMap<String, Bitmap> contactBitmaps = new HashMap<String, Bitmap>();
|
|
||||||
private HashMap<String, Bitmap> unknownBitmaps = new HashMap<String, Bitmap>();
|
|
||||||
|
|
||||||
public Bitmap get(Contact contact, Context context) {
|
|
||||||
if (!contactBitmaps.containsKey(contact.getJid())) {
|
|
||||||
contactBitmaps.put(contact.getJid(),
|
|
||||||
contact.getImage(48, context));
|
|
||||||
}
|
|
||||||
return contactBitmaps.get(contact.getJid());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bitmap get(String name, Context context) {
|
|
||||||
if (unknownBitmaps.containsKey(name)) {
|
|
||||||
return unknownBitmaps.get(name);
|
|
||||||
} else {
|
|
||||||
Bitmap bm = UIHelper
|
|
||||||
.getContactPicture(name, 48, context, false);
|
|
||||||
unknownBitmaps.put(name, bm);
|
|
||||||
return bm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnContactPictureClicked {
|
public interface OnContactPictureClicked {
|
||||||
public void onContactPictureClicked(Message message);
|
public void onContactPictureClicked(Message message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import java.nio.charset.Charset;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
@ -28,9 +27,11 @@ public class CryptoHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] hexToBytes(String hexString) {
|
public static byte[] hexToBytes(String hexString) {
|
||||||
byte[] array = new BigInteger(hexString, 16).toByteArray();
|
int len = hexString.length();
|
||||||
if (array[0] == 0) {
|
byte[] array = new byte[len / 2];
|
||||||
array = Arrays.copyOfRange(array, 1, array.length);
|
for (int i = 0; i < len; i += 2) {
|
||||||
|
array[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
|
||||||
|
.digit(hexString.charAt(i + 1), 16));
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class DNSHelper {
|
||||||
for (String dnsserver : dns) {
|
for (String dnsserver : dns) {
|
||||||
InetAddress ip = InetAddress.getByName(dnsserver);
|
InetAddress ip = InetAddress.getByName(dnsserver);
|
||||||
Bundle b = queryDNS(host, ip);
|
Bundle b = queryDNS(host, ip);
|
||||||
if (b.containsKey("name")) {
|
if (b.containsKey("values")) {
|
||||||
return b;
|
return b;
|
||||||
} else if (b.containsKey("error")
|
} else if (b.containsKey("error")
|
||||||
&& "nosrv".equals(b.getString("error", null))) {
|
&& "nosrv".equals(b.getString("error", null))) {
|
||||||
|
@ -45,7 +45,7 @@ public class DNSHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bundle queryDNS(String host, InetAddress dnsServer) {
|
public static Bundle queryDNS(String host, InetAddress dnsServer) {
|
||||||
Bundle namePort = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
try {
|
try {
|
||||||
String qname = "_xmpp-client._tcp." + host;
|
String qname = "_xmpp-client._tcp." + host;
|
||||||
Log.d(Config.LOGTAG,
|
Log.d(Config.LOGTAG,
|
||||||
|
@ -133,42 +133,28 @@ public class DNSHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.size() == 0) {
|
if (result.size() == 0) {
|
||||||
namePort.putString("error", "nosrv");
|
bundle.putString("error", "nosrv");
|
||||||
return namePort;
|
return bundle;
|
||||||
}
|
}
|
||||||
// we now have a list of servers to try :-)
|
ArrayList<Bundle> values = new ArrayList<Bundle>();
|
||||||
|
|
||||||
// classic name/port pair
|
|
||||||
String resultName = result.get(0).getName();
|
|
||||||
namePort.putString("name", resultName);
|
|
||||||
namePort.putInt("port", result.get(0).getPort());
|
|
||||||
|
|
||||||
if (ips4.containsKey(resultName)) {
|
|
||||||
// we have an ip!
|
|
||||||
ArrayList<String> ip = ips4.get(resultName);
|
|
||||||
Collections.shuffle(ip, rnd);
|
|
||||||
namePort.putString("ipv4", ip.get(0));
|
|
||||||
}
|
|
||||||
if (ips6.containsKey(resultName)) {
|
|
||||||
ArrayList<String> ip = ips6.get(resultName);
|
|
||||||
Collections.shuffle(ip, rnd);
|
|
||||||
namePort.putString("ipv6", ip.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// add all other records
|
|
||||||
int i = 0;
|
|
||||||
for (SRV srv : result) {
|
for (SRV srv : result) {
|
||||||
namePort.putString("name" + i, srv.getName());
|
Bundle namePort = new Bundle();
|
||||||
namePort.putInt("port" + i, srv.getPort());
|
namePort.putString("name", srv.getName());
|
||||||
i++;
|
namePort.putInt("port", srv.getPort());
|
||||||
|
if (ips4.containsKey(srv.getName())) {
|
||||||
|
ArrayList<String> ip = ips4.get(srv.getName());
|
||||||
|
Collections.shuffle(ip, rnd);
|
||||||
|
namePort.putString("ipv4", ip.get(0));
|
||||||
|
}
|
||||||
|
values.add(namePort);
|
||||||
}
|
}
|
||||||
|
bundle.putParcelableArrayList("values", values);
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
namePort.putString("error", "timeout");
|
bundle.putString("error", "timeout");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
namePort.putString("error", "unhandled");
|
bundle.putString("error", "unhandled");
|
||||||
}
|
}
|
||||||
return namePort;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class ExifHelper {
|
||||||
|
private static final String TAG = "CameraExif";
|
||||||
|
|
||||||
|
public static int getOrientation(InputStream is) {
|
||||||
|
if (is == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buf = new byte[8];
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
// ISO/IEC 10918-1:1993(E)
|
||||||
|
while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) {
|
||||||
|
int marker = buf[1] & 0xFF;
|
||||||
|
|
||||||
|
// Check if the marker is a padding.
|
||||||
|
if (marker == 0xFF) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the marker is SOI or TEM.
|
||||||
|
if (marker == 0xD8 || marker == 0x01) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Check if the marker is EOI or SOS.
|
||||||
|
if (marker == 0xD9 || marker == 0xDA) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the length and check if it is reasonable.
|
||||||
|
if (!read(is, buf, 2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
length = pack(buf, 0, 2, false);
|
||||||
|
if (length < 2) {
|
||||||
|
Log.e(TAG, "Invalid length");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
length -= 2;
|
||||||
|
|
||||||
|
// Break if the marker is EXIF in APP1.
|
||||||
|
if (marker == 0xE1 && length >= 6) {
|
||||||
|
if (!read(is, buf, 6)) return 0;
|
||||||
|
length -= 6;
|
||||||
|
if (pack(buf, 0, 4, false) == 0x45786966 &&
|
||||||
|
pack(buf, 4, 2, false) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip other markers.
|
||||||
|
try {
|
||||||
|
is.skip(length);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JEITA CP-3451 Exif Version 2.2
|
||||||
|
if (length > 8) {
|
||||||
|
int offset = 0;
|
||||||
|
byte[] jpeg = new byte[length];
|
||||||
|
if (!read(is, jpeg, length)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the byte order.
|
||||||
|
int tag = pack(jpeg, offset, 4, false);
|
||||||
|
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
|
||||||
|
Log.e(TAG, "Invalid byte order");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
boolean littleEndian = (tag == 0x49492A00);
|
||||||
|
|
||||||
|
// Get the offset and check if it is reasonable.
|
||||||
|
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
|
||||||
|
if (count < 10 || count > length) {
|
||||||
|
Log.e(TAG, "Invalid offset");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
offset += count;
|
||||||
|
length -= count;
|
||||||
|
|
||||||
|
// Get the count and go through all the elements.
|
||||||
|
count = pack(jpeg, offset - 2, 2, littleEndian);
|
||||||
|
while (count-- > 0 && length >= 12) {
|
||||||
|
// Get the tag and check if it is orientation.
|
||||||
|
tag = pack(jpeg, offset, 2, littleEndian);
|
||||||
|
if (tag == 0x0112) {
|
||||||
|
// We do not really care about type and count, do we?
|
||||||
|
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
|
||||||
|
switch (orientation) {
|
||||||
|
case 1:
|
||||||
|
return 0;
|
||||||
|
case 3:
|
||||||
|
return 180;
|
||||||
|
case 6:
|
||||||
|
return 90;
|
||||||
|
case 8:
|
||||||
|
return 270;
|
||||||
|
}
|
||||||
|
Log.i(TAG, "Unsupported orientation");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
offset += 12;
|
||||||
|
length -= 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Orientation not found");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int pack(byte[] bytes, int offset, int length,
|
||||||
|
boolean littleEndian) {
|
||||||
|
int step = 1;
|
||||||
|
if (littleEndian) {
|
||||||
|
offset += length - 1;
|
||||||
|
step = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int value = 0;
|
||||||
|
while (length-- > 0) {
|
||||||
|
value = (value << 8) | (bytes[offset] & 0xFF);
|
||||||
|
offset += step;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean read(InputStream is, byte[] buf, int length) {
|
||||||
|
try {
|
||||||
|
return is.read(buf, 0, length) == length;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ public class PhoneHelper {
|
||||||
|
|
||||||
final String[] PROJECTION = new String[] { ContactsContract.Data._ID,
|
final String[] PROJECTION = new String[] { ContactsContract.Data._ID,
|
||||||
ContactsContract.Data.DISPLAY_NAME,
|
ContactsContract.Data.DISPLAY_NAME,
|
||||||
ContactsContract.Data.PHOTO_THUMBNAIL_URI,
|
ContactsContract.Data.PHOTO_URI,
|
||||||
ContactsContract.Data.LOOKUP_KEY,
|
ContactsContract.Data.LOOKUP_KEY,
|
||||||
ContactsContract.CommonDataKinds.Im.DATA };
|
ContactsContract.CommonDataKinds.Im.DATA };
|
||||||
|
|
||||||
|
@ -50,10 +50,8 @@ public class PhoneHelper {
|
||||||
"displayname",
|
"displayname",
|
||||||
cursor.getString(cursor
|
cursor.getString(cursor
|
||||||
.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
|
.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
|
||||||
contact.putString(
|
contact.putString("photouri", cursor.getString(cursor
|
||||||
"photouri",
|
.getColumnIndex(ContactsContract.Data.PHOTO_URI)));
|
||||||
cursor.getString(cursor
|
|
||||||
.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI)));
|
|
||||||
contact.putString("lookup", cursor.getString(cursor
|
contact.putString("lookup", cursor.getString(cursor
|
||||||
.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
|
.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
package eu.siacs.conversations.utils;
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.MucOptions.User;
|
|
||||||
import eu.siacs.conversations.ui.ConversationActivity;
|
import eu.siacs.conversations.ui.ConversationActivity;
|
||||||
import eu.siacs.conversations.ui.ManageAccountActivity;
|
import eu.siacs.conversations.ui.ManageAccountActivity;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
|
@ -25,28 +21,15 @@ import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.provider.ContactsContract.Contacts;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.QuickContactBadge;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
public class UIHelper {
|
public class UIHelper {
|
||||||
private static final int BG_COLOR = 0xFF181818;
|
|
||||||
private static final int FG_COLOR = 0xFFFAFAFA;
|
|
||||||
private static final int TRANSPARENT = 0x00000000;
|
|
||||||
private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE
|
private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE
|
||||||
| DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL;
|
| DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL;
|
||||||
private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME
|
private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME
|
||||||
|
@ -123,167 +106,6 @@ public class UIHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getRealPx(int dp, Context context) {
|
|
||||||
final DisplayMetrics metrics = context.getResources()
|
|
||||||
.getDisplayMetrics();
|
|
||||||
return ((int) (dp * metrics.density));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getNameColor(String name) {
|
|
||||||
/*
|
|
||||||
* int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713,
|
|
||||||
* 0xFFe92727 };
|
|
||||||
*/
|
|
||||||
int holoColors[] = { 0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5,
|
|
||||||
0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722,
|
|
||||||
0xFF795548, 0xFF607d8b };
|
|
||||||
return holoColors[(int) ((name.hashCode() & 0xffffffffl) % holoColors.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void drawTile(Canvas canvas, String letter, int tileColor,
|
|
||||||
int textColor, int left, int top, int right, int bottom) {
|
|
||||||
Paint tilePaint = new Paint(), textPaint = new Paint();
|
|
||||||
tilePaint.setColor(tileColor);
|
|
||||||
textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
textPaint.setColor(textColor);
|
|
||||||
textPaint.setTypeface(Typeface.create("sans-serif-light",
|
|
||||||
Typeface.NORMAL));
|
|
||||||
textPaint.setTextSize((float) ((right - left) * 0.8));
|
|
||||||
Rect rect = new Rect();
|
|
||||||
|
|
||||||
canvas.drawRect(new Rect(left, top, right, bottom), tilePaint);
|
|
||||||
textPaint.getTextBounds(letter, 0, 1, rect);
|
|
||||||
float width = textPaint.measureText(letter);
|
|
||||||
canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom)
|
|
||||||
/ 2 + rect.height() / 2, textPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Bitmap getUnknownContactPicture(String[] names, int size,
|
|
||||||
int bgColor, int fgColor) {
|
|
||||||
int tiles = (names.length > 4) ? 4 : (names.length < 1) ? 1
|
|
||||||
: names.length;
|
|
||||||
Bitmap bitmap = Bitmap
|
|
||||||
.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
|
||||||
Canvas canvas = new Canvas(bitmap);
|
|
||||||
|
|
||||||
String[] letters = new String[tiles];
|
|
||||||
int[] colors = new int[tiles];
|
|
||||||
if (names.length < 1) {
|
|
||||||
letters[0] = "?";
|
|
||||||
colors[0] = 0xFFe92727;
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < tiles; ++i) {
|
|
||||||
letters[i] = (names[i].length() > 0) ? names[i].substring(0, 1)
|
|
||||||
.toUpperCase(Locale.US) : " ";
|
|
||||||
colors[i] = getNameColor(names[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (names.length > 4) {
|
|
||||||
letters[3] = "\u2026"; // Unicode ellipsis
|
|
||||||
colors[3] = 0xFF202020;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitmap.eraseColor(bgColor);
|
|
||||||
|
|
||||||
switch (tiles) {
|
|
||||||
case 1:
|
|
||||||
drawTile(canvas, letters[0], colors[0], fgColor, 0, 0, size, size);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
drawTile(canvas, letters[0], colors[0], fgColor, 0, 0,
|
|
||||||
size / 2 - 1, size);
|
|
||||||
drawTile(canvas, letters[1], colors[1], fgColor, size / 2 + 1, 0,
|
|
||||||
size, size);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
drawTile(canvas, letters[0], colors[0], fgColor, 0, 0,
|
|
||||||
size / 2 - 1, size);
|
|
||||||
drawTile(canvas, letters[1], colors[1], fgColor, size / 2 + 1, 0,
|
|
||||||
size, size / 2 - 1);
|
|
||||||
drawTile(canvas, letters[2], colors[2], fgColor, size / 2 + 1,
|
|
||||||
size / 2 + 1, size, size);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
drawTile(canvas, letters[0], colors[0], fgColor, 0, 0,
|
|
||||||
size / 2 - 1, size / 2 - 1);
|
|
||||||
drawTile(canvas, letters[1], colors[1], fgColor, 0, size / 2 + 1,
|
|
||||||
size / 2 - 1, size);
|
|
||||||
drawTile(canvas, letters[2], colors[2], fgColor, size / 2 + 1, 0,
|
|
||||||
size, size / 2 - 1);
|
|
||||||
drawTile(canvas, letters[3], colors[3], fgColor, size / 2 + 1,
|
|
||||||
size / 2 + 1, size, size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Bitmap getMucContactPicture(Conversation conversation,
|
|
||||||
int size, int bgColor, int fgColor) {
|
|
||||||
List<User> members = conversation.getMucOptions().getUsers();
|
|
||||||
if (members.size() == 0) {
|
|
||||||
return getUnknownContactPicture(
|
|
||||||
new String[] { conversation.getName() }, size, bgColor,
|
|
||||||
fgColor);
|
|
||||||
}
|
|
||||||
ArrayList<String> names = new ArrayList<String>();
|
|
||||||
names.add(conversation.getMucOptions().getActualNick());
|
|
||||||
for (User user : members) {
|
|
||||||
names.add(user.getName());
|
|
||||||
if (names.size() > 4) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String[] mArrayNames = new String[names.size()];
|
|
||||||
names.toArray(mArrayNames);
|
|
||||||
return getUnknownContactPicture(mArrayNames, size, bgColor, fgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap getContactPicture(Conversation conversation,
|
|
||||||
int dpSize, Context context, boolean notification) {
|
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
|
||||||
return getContactPicture(conversation.getContact(), dpSize,
|
|
||||||
context, notification);
|
|
||||||
} else {
|
|
||||||
int fgColor = UIHelper.FG_COLOR, bgColor = (notification) ? UIHelper.BG_COLOR
|
|
||||||
: UIHelper.TRANSPARENT;
|
|
||||||
|
|
||||||
return getMucContactPicture(conversation,
|
|
||||||
getRealPx(dpSize, context), bgColor, fgColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap getContactPicture(Contact contact, int dpSize,
|
|
||||||
Context context, boolean notification) {
|
|
||||||
String uri = contact.getProfilePhoto();
|
|
||||||
if (uri == null) {
|
|
||||||
return getContactPicture(contact.getDisplayName(), dpSize, context,
|
|
||||||
notification);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Bitmap bm = BitmapFactory.decodeStream(context.getContentResolver()
|
|
||||||
.openInputStream(Uri.parse(uri)));
|
|
||||||
return Bitmap.createScaledBitmap(bm, getRealPx(dpSize, context),
|
|
||||||
getRealPx(dpSize, context), false);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return getContactPicture(contact.getDisplayName(), dpSize, context,
|
|
||||||
notification);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap getContactPicture(String name, int dpSize,
|
|
||||||
Context context, boolean notification) {
|
|
||||||
int fgColor = UIHelper.FG_COLOR, bgColor = (notification) ? UIHelper.BG_COLOR
|
|
||||||
: UIHelper.TRANSPARENT;
|
|
||||||
|
|
||||||
return getUnknownContactPicture(new String[] { name },
|
|
||||||
getRealPx(dpSize, context), bgColor, fgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void showErrorNotification(Context context,
|
public static void showErrorNotification(Context context,
|
||||||
List<Account> accounts) {
|
List<Account> accounts) {
|
||||||
NotificationManager mNotificationManager = (NotificationManager) context
|
NotificationManager mNotificationManager = (NotificationManager) context
|
||||||
|
@ -326,16 +148,6 @@ public class UIHelper {
|
||||||
mNotificationManager.notify(1111, notification);
|
mNotificationManager.notify(1111, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void prepareContactBadge(final Activity activity,
|
|
||||||
QuickContactBadge badge, final Contact contact, Context context) {
|
|
||||||
if (contact.getSystemAccount() != null) {
|
|
||||||
String[] systemAccount = contact.getSystemAccount().split("#");
|
|
||||||
long id = Long.parseLong(systemAccount[0]);
|
|
||||||
badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1]));
|
|
||||||
}
|
|
||||||
badge.setImageBitmap(contact.getImage(72, context));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
public static AlertDialog getVerifyFingerprintDialog(
|
public static AlertDialog getVerifyFingerprintDialog(
|
||||||
final ConversationActivity activity,
|
final ConversationActivity activity,
|
||||||
|
@ -370,25 +182,6 @@ public class UIHelper {
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap getSelfContactPicture(Account account, int size,
|
|
||||||
boolean showPhoneSelfContactPicture, Context context) {
|
|
||||||
if (showPhoneSelfContactPicture) {
|
|
||||||
Uri selfiUri = PhoneHelper.getSefliUri(context);
|
|
||||||
if (selfiUri != null) {
|
|
||||||
try {
|
|
||||||
return BitmapFactory.decodeStream(context
|
|
||||||
.getContentResolver().openInputStream(selfiUri));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return getContactPicture(account.getJid(), size, context,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getContactPicture(account.getJid(), size, context, false);
|
|
||||||
} else {
|
|
||||||
return getContactPicture(account.getJid(), size, context, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static class EmoticonPattern {
|
private final static class EmoticonPattern {
|
||||||
Pattern pattern;
|
Pattern pattern;
|
||||||
String replacement;
|
String replacement;
|
||||||
|
|
|
@ -4,14 +4,17 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
@ -22,14 +25,19 @@ import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import de.duenndns.ssl.MemorizingTrustManager;
|
import de.duenndns.ssl.MemorizingTrustManager;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
@ -80,6 +88,7 @@ public class XmppConnection implements Runnable {
|
||||||
private SparseArray<String> messageReceipts = new SparseArray<String>();
|
private SparseArray<String> messageReceipts = new SparseArray<String>();
|
||||||
|
|
||||||
private boolean usingCompression = false;
|
private boolean usingCompression = false;
|
||||||
|
private boolean usingEncryption = false;
|
||||||
|
|
||||||
private int stanzasReceived = 0;
|
private int stanzasReceived = 0;
|
||||||
private int stanzasSent = 0;
|
private int stanzasSent = 0;
|
||||||
|
@ -104,6 +113,7 @@ public class XmppConnection implements Runnable {
|
||||||
private OnBindListener bindListener = null;
|
private OnBindListener bindListener = null;
|
||||||
private OnMessageAcknowledged acknowledgedListener = null;
|
private OnMessageAcknowledged acknowledgedListener = null;
|
||||||
private MemorizingTrustManager mMemorizingTrustManager;
|
private MemorizingTrustManager mMemorizingTrustManager;
|
||||||
|
private final Context applicationContext;
|
||||||
|
|
||||||
public XmppConnection(Account account, XmppConnectionService service) {
|
public XmppConnection(Account account, XmppConnectionService service) {
|
||||||
this.mRandom = service.getRNG();
|
this.mRandom = service.getRNG();
|
||||||
|
@ -112,6 +122,7 @@ public class XmppConnection implements Runnable {
|
||||||
this.wakeLock = service.getPowerManager().newWakeLock(
|
this.wakeLock = service.getPowerManager().newWakeLock(
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, account.getJid());
|
PowerManager.PARTIAL_WAKE_LOCK, account.getJid());
|
||||||
tagWriter = new TagWriter();
|
tagWriter = new TagWriter();
|
||||||
|
applicationContext = service.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void changeStatus(int nextStatus) {
|
protected void changeStatus(int nextStatus) {
|
||||||
|
@ -135,6 +146,7 @@ public class XmppConnection implements Runnable {
|
||||||
protected void connect() {
|
protected void connect() {
|
||||||
Log.d(Config.LOGTAG, account.getJid() + ": connecting");
|
Log.d(Config.LOGTAG, account.getJid() + ": connecting");
|
||||||
usingCompression = false;
|
usingCompression = false;
|
||||||
|
usingEncryption = false;
|
||||||
lastConnect = SystemClock.elapsedRealtime();
|
lastConnect = SystemClock.elapsedRealtime();
|
||||||
lastPingSent = SystemClock.elapsedRealtime();
|
lastPingSent = SystemClock.elapsedRealtime();
|
||||||
this.attempt++;
|
this.attempt++;
|
||||||
|
@ -145,29 +157,47 @@ public class XmppConnection implements Runnable {
|
||||||
tagWriter = new TagWriter();
|
tagWriter = new TagWriter();
|
||||||
packetCallbacks.clear();
|
packetCallbacks.clear();
|
||||||
this.changeStatus(Account.STATUS_CONNECTING);
|
this.changeStatus(Account.STATUS_CONNECTING);
|
||||||
Bundle namePort = DNSHelper.getSRVRecord(account.getServer());
|
Bundle result = DNSHelper.getSRVRecord(account.getServer());
|
||||||
if ("timeout".equals(namePort.getString("error"))) {
|
ArrayList<Parcelable> values = result.getParcelableArrayList("values");
|
||||||
|
if ("timeout".equals(result.getString("error"))) {
|
||||||
Log.d(Config.LOGTAG, account.getJid() + ": dns timeout");
|
Log.d(Config.LOGTAG, account.getJid() + ": dns timeout");
|
||||||
this.changeStatus(Account.STATUS_OFFLINE);
|
this.changeStatus(Account.STATUS_OFFLINE);
|
||||||
return;
|
return;
|
||||||
}
|
} else if (values != null) {
|
||||||
String srvRecordServer = namePort.getString("name");
|
int i = 0;
|
||||||
String srvIpServer = namePort.getString("ipv4");
|
boolean socketError = true;
|
||||||
int srvRecordPort = namePort.getInt("port");
|
while (socketError && values.size() > i) {
|
||||||
if (srvRecordServer != null) {
|
Bundle namePort = (Bundle) values.get(i);
|
||||||
if (srvIpServer != null) {
|
try {
|
||||||
Log.d(Config.LOGTAG, account.getJid()
|
String srvRecordServer = namePort.getString("name");
|
||||||
+ ": using values from dns " + srvRecordServer
|
int srvRecordPort = namePort.getInt("port");
|
||||||
+ "[" + srvIpServer + "]:" + srvRecordPort);
|
String srvIpServer = namePort.getString("ipv4");
|
||||||
socket = new Socket(srvIpServer, srvRecordPort);
|
InetSocketAddress addr;
|
||||||
} else {
|
if (srvIpServer!=null) {
|
||||||
Log.d(Config.LOGTAG, account.getJid()
|
addr = new InetSocketAddress(srvIpServer, srvRecordPort);
|
||||||
+ ": using values from dns " + srvRecordServer
|
Log.d(Config.LOGTAG, account.getJid()
|
||||||
+ ":" + srvRecordPort);
|
+ ": using values from dns " + srvRecordServer
|
||||||
socket = new Socket(srvRecordServer, srvRecordPort);
|
+ "[" + srvIpServer + "]:" + srvRecordPort);
|
||||||
}
|
} else {
|
||||||
} else if (namePort.containsKey("error")
|
addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
|
||||||
&& "nosrv".equals(namePort.getString("error", null))) {
|
Log.d(Config.LOGTAG, account.getJid()
|
||||||
|
+ ": using values from dns "
|
||||||
|
+ srvRecordServer + ":" + srvRecordPort);
|
||||||
|
}
|
||||||
|
socket = new Socket();
|
||||||
|
socket.connect(addr, 20000);
|
||||||
|
socketError = false;
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
i++;
|
||||||
|
} catch (IOException e) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (socketError) {
|
||||||
|
throw new IOException();
|
||||||
|
}
|
||||||
|
} else if (result.containsKey("error")
|
||||||
|
&& "nosrv".equals(result.getString("error", null))) {
|
||||||
socket = new Socket(account.getServer(), 5222);
|
socket = new Socket(account.getServer(), 5222);
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, account.getJid()
|
Log.d(Config.LOGTAG, account.getJid()
|
||||||
|
@ -504,6 +534,15 @@ public class XmppConnection implements Runnable {
|
||||||
tagWriter.writeTag(startTLS);
|
tagWriter.writeTag(startTLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SharedPreferences getPreferences() {
|
||||||
|
return PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(applicationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean enableLegacySSL() {
|
||||||
|
return getPreferences().getBoolean("enable_legacy_ssl", false);
|
||||||
|
}
|
||||||
|
|
||||||
private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
|
private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
|
||||||
IOException {
|
IOException {
|
||||||
tagReader.readTag();
|
tagReader.readTag();
|
||||||
|
@ -515,11 +554,27 @@ public class XmppConnection implements Runnable {
|
||||||
SSLSocketFactory factory = sc.getSocketFactory();
|
SSLSocketFactory factory = sc.getSocketFactory();
|
||||||
|
|
||||||
HostnameVerifier verifier = this.mMemorizingTrustManager
|
HostnameVerifier verifier = this.mMemorizingTrustManager
|
||||||
.wrapHostnameVerifier(new org.apache.http.conn.ssl.StrictHostnameVerifier());
|
.wrapHostnameVerifier(new StrictHostnameVerifier());
|
||||||
SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
|
SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,
|
||||||
socket.getInetAddress().getHostAddress(), socket.getPort(),
|
socket.getInetAddress().getHostAddress(), socket.getPort(),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
// Support all protocols except legacy SSL.
|
||||||
|
// The min SDK version prevents us having to worry about SSLv2. In
|
||||||
|
// future, this may be
|
||||||
|
// true of SSLv3 as well.
|
||||||
|
final String[] supportProtocols;
|
||||||
|
if (enableLegacySSL()) {
|
||||||
|
supportProtocols = sslSocket.getSupportedProtocols();
|
||||||
|
} else {
|
||||||
|
final List<String> supportedProtocols = new LinkedList<String>(
|
||||||
|
Arrays.asList(sslSocket.getSupportedProtocols()));
|
||||||
|
supportedProtocols.remove("SSLv3");
|
||||||
|
supportProtocols = new String[supportedProtocols.size()];
|
||||||
|
supportedProtocols.toArray(supportProtocols);
|
||||||
|
}
|
||||||
|
sslSocket.setEnabledProtocols(supportProtocols);
|
||||||
|
|
||||||
if (verifier != null
|
if (verifier != null
|
||||||
&& !verifier.verify(account.getServer(),
|
&& !verifier.verify(account.getServer(),
|
||||||
sslSocket.getSession())) {
|
sslSocket.getSession())) {
|
||||||
|
@ -533,6 +588,7 @@ public class XmppConnection implements Runnable {
|
||||||
sendStartStream();
|
sendStartStream();
|
||||||
Log.d(Config.LOGTAG, account.getJid()
|
Log.d(Config.LOGTAG, account.getJid()
|
||||||
+ ": TLS connection established");
|
+ ": TLS connection established");
|
||||||
|
usingEncryption = true;
|
||||||
processStream(tagReader.readTag());
|
processStream(tagReader.readTag());
|
||||||
sslSocket.close();
|
sslSocket.close();
|
||||||
} catch (NoSuchAlgorithmException e1) {
|
} catch (NoSuchAlgorithmException e1) {
|
||||||
|
@ -562,20 +618,20 @@ public class XmppConnection implements Runnable {
|
||||||
private void processStreamFeatures(Tag currentTag)
|
private void processStreamFeatures(Tag currentTag)
|
||||||
throws XmlPullParserException, IOException {
|
throws XmlPullParserException, IOException {
|
||||||
this.streamFeatures = tagReader.readElement(currentTag);
|
this.streamFeatures = tagReader.readElement(currentTag);
|
||||||
if (this.streamFeatures.hasChild("starttls")
|
if (this.streamFeatures.hasChild("starttls") && !usingEncryption) {
|
||||||
&& account.isOptionSet(Account.OPTION_USETLS)) {
|
|
||||||
sendStartTLS();
|
sendStartTLS();
|
||||||
} else if (compressionAvailable()) {
|
} else if (compressionAvailable()) {
|
||||||
sendCompressionZlib();
|
sendCompressionZlib();
|
||||||
} else if (this.streamFeatures.hasChild("register")
|
} else if (this.streamFeatures.hasChild("register")
|
||||||
&& (account.isOptionSet(Account.OPTION_REGISTER))) {
|
&& account.isOptionSet(Account.OPTION_REGISTER)
|
||||||
|
&& usingEncryption) {
|
||||||
sendRegistryRequest();
|
sendRegistryRequest();
|
||||||
} else if (!this.streamFeatures.hasChild("register")
|
} else if (!this.streamFeatures.hasChild("register")
|
||||||
&& (account.isOptionSet(Account.OPTION_REGISTER))) {
|
&& account.isOptionSet(Account.OPTION_REGISTER)) {
|
||||||
changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED);
|
changeStatus(Account.STATUS_REGISTRATION_NOT_SUPPORTED);
|
||||||
disconnect(true);
|
disconnect(true);
|
||||||
} else if (this.streamFeatures.hasChild("mechanisms")
|
} else if (this.streamFeatures.hasChild("mechanisms")
|
||||||
&& shouldAuthenticate) {
|
&& shouldAuthenticate && usingEncryption) {
|
||||||
List<String> mechanisms = extractMechanisms(streamFeatures
|
List<String> mechanisms = extractMechanisms(streamFeatures
|
||||||
.findChild("mechanisms"));
|
.findChild("mechanisms"));
|
||||||
if (mechanisms.contains("PLAIN")) {
|
if (mechanisms.contains("PLAIN")) {
|
||||||
|
@ -591,6 +647,10 @@ public class XmppConnection implements Runnable {
|
||||||
this.tagWriter.writeStanzaAsync(resume);
|
this.tagWriter.writeStanzaAsync(resume);
|
||||||
} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
|
} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
|
||||||
sendBindRequest();
|
sendBindRequest();
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid()
|
||||||
|
+ ": incompatible server. disconnecting");
|
||||||
|
disconnect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -910,7 +970,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disconnect(boolean force) {
|
public void disconnect(boolean force) {
|
||||||
Log.d(Config.LOGTAG, account.getJid()+": disconnecting");
|
Log.d(Config.LOGTAG, account.getJid() + ": disconnecting");
|
||||||
try {
|
try {
|
||||||
if (force) {
|
if (force) {
|
||||||
socket.close();
|
socket.close();
|
||||||
|
|
|
@ -16,6 +16,7 @@ import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Downloadable;
|
import eu.siacs.conversations.entities.Downloadable;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
@ -33,17 +34,18 @@ public class JingleConnection implements Downloadable {
|
||||||
private JingleConnectionManager mJingleConnectionManager;
|
private JingleConnectionManager mJingleConnectionManager;
|
||||||
private XmppConnectionService mXmppConnectionService;
|
private XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
public static final int STATUS_INITIATED = 0;
|
protected static final int JINGLE_STATUS_INITIATED = 0;
|
||||||
public static final int STATUS_ACCEPTED = 1;
|
protected static final int JINGLE_STATUS_ACCEPTED = 1;
|
||||||
public static final int STATUS_TERMINATED = 2;
|
protected static final int JINGLE_STATUS_TERMINATED = 2;
|
||||||
public static final int STATUS_CANCELED = 3;
|
protected static final int JINGLE_STATUS_CANCELED = 3;
|
||||||
public static final int STATUS_FINISHED = 4;
|
protected static final int JINGLE_STATUS_FINISHED = 4;
|
||||||
public static final int STATUS_TRANSMITTING = 5;
|
protected static final int JINGLE_STATUS_TRANSMITTING = 5;
|
||||||
public static final int STATUS_FAILED = 99;
|
protected static final int JINGLE_STATUS_FAILED = 99;
|
||||||
|
|
||||||
private int ibbBlockSize = 4096;
|
private int ibbBlockSize = 4096;
|
||||||
|
|
||||||
private int status = -1;
|
private int mJingleStatus = -1;
|
||||||
|
private int mStatus = -1;
|
||||||
private Message message;
|
private Message message;
|
||||||
private String sessionId;
|
private String sessionId;
|
||||||
private Account account;
|
private Account account;
|
||||||
|
@ -54,7 +56,7 @@ public class JingleConnection implements Downloadable {
|
||||||
|
|
||||||
private String transportId;
|
private String transportId;
|
||||||
private Element fileOffer;
|
private Element fileOffer;
|
||||||
private JingleFile file = null;
|
private DownloadableFile file = null;
|
||||||
|
|
||||||
private String contentName;
|
private String contentName;
|
||||||
private String contentCreator;
|
private String contentCreator;
|
||||||
|
@ -71,11 +73,7 @@ public class JingleConnection implements Downloadable {
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||||
if (packet.getType() == IqPacket.TYPE_ERROR) {
|
if (packet.getType() == IqPacket.TYPE_ERROR) {
|
||||||
if (initiator.equals(account.getFullJid())) {
|
cancel();
|
||||||
mXmppConnectionService.markMessage(message,
|
|
||||||
Message.STATUS_SEND_FAILED);
|
|
||||||
}
|
|
||||||
status = STATUS_FAILED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -83,7 +81,7 @@ public class JingleConnection implements Downloadable {
|
||||||
final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
|
final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFileTransmitted(JingleFile file) {
|
public void onFileTransmitted(DownloadableFile file) {
|
||||||
if (responder.equals(account.getFullJid())) {
|
if (responder.equals(account.getFullJid())) {
|
||||||
sendSuccess();
|
sendSuccess();
|
||||||
if (acceptedAutomatically) {
|
if (acceptedAutomatically) {
|
||||||
|
@ -96,8 +94,8 @@ public class JingleConnection implements Downloadable {
|
||||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||||
int imageHeight = options.outHeight;
|
int imageHeight = options.outHeight;
|
||||||
int imageWidth = options.outWidth;
|
int imageWidth = options.outWidth;
|
||||||
message.setBody(Long.toString(file.getSize()) + ','
|
message.setBody(Long.toString(file.getSize()) + '|'
|
||||||
+ imageWidth + ',' + imageHeight);
|
+ imageWidth + '|' + imageHeight);
|
||||||
mXmppConnectionService.databaseBackend.createMessage(message);
|
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||||
mXmppConnectionService.markMessage(message,
|
mXmppConnectionService.markMessage(message,
|
||||||
Message.STATUS_RECEIVED);
|
Message.STATUS_RECEIVED);
|
||||||
|
@ -148,8 +146,8 @@ public class JingleConnection implements Downloadable {
|
||||||
return this.sessionId;
|
return this.sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccountJid() {
|
public Account getAccount() {
|
||||||
return this.account.getFullJid();
|
return this.account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCounterPart() {
|
public String getCounterPart() {
|
||||||
|
@ -253,13 +251,14 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(Account account, JinglePacket packet) {
|
public void init(Account account, JinglePacket packet) {
|
||||||
this.status = STATUS_INITIATED;
|
this.mJingleStatus = JINGLE_STATUS_INITIATED;
|
||||||
Conversation conversation = this.mXmppConnectionService
|
Conversation conversation = this.mXmppConnectionService
|
||||||
.findOrCreateConversation(account,
|
.findOrCreateConversation(account,
|
||||||
packet.getFrom().split("/", 2)[0], false);
|
packet.getFrom().split("/", 2)[0], false);
|
||||||
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
|
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
|
||||||
|
this.message.setStatus(Message.STATUS_RECEIVED);
|
||||||
this.message.setType(Message.TYPE_IMAGE);
|
this.message.setType(Message.TYPE_IMAGE);
|
||||||
this.message.setStatus(Message.STATUS_RECEIVED_OFFER);
|
this.mStatus = Downloadable.STATUS_OFFER;
|
||||||
this.message.setDownloadable(this);
|
this.message.setDownloadable(this);
|
||||||
String[] fromParts = packet.getFrom().split("/", 2);
|
String[] fromParts = packet.getFrom().split("/", 2);
|
||||||
this.message.setPresence(fromParts[1]);
|
this.message.setPresence(fromParts[1]);
|
||||||
|
@ -304,7 +303,8 @@ public class JingleConnection implements Downloadable {
|
||||||
if (supportedFile) {
|
if (supportedFile) {
|
||||||
long size = Long.parseLong(fileSize.getContent());
|
long size = Long.parseLong(fileSize.getContent());
|
||||||
message.setBody(Long.toString(size));
|
message.setBody(Long.toString(size));
|
||||||
conversation.getMessages().add(message);
|
conversation.add(message);
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
if (size <= this.mJingleConnectionManager
|
if (size <= this.mJingleConnectionManager
|
||||||
.getAutoAcceptFileSize()) {
|
.getAutoAcceptFileSize()) {
|
||||||
Log.d(Config.LOGTAG, "auto accepting file from "
|
Log.d(Config.LOGTAG, "auto accepting file from "
|
||||||
|
@ -323,7 +323,7 @@ public class JingleConnection implements Downloadable {
|
||||||
.push(message);
|
.push(message);
|
||||||
}
|
}
|
||||||
this.file = this.mXmppConnectionService.getFileBackend()
|
this.file = this.mXmppConnectionService.getFileBackend()
|
||||||
.getJingleFile(message, false);
|
.getFile(message, false);
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
byte[] key = conversation.getSymmetricKey();
|
byte[] key = conversation.getSymmetricKey();
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
|
@ -350,12 +350,13 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendInitRequest() {
|
private void sendInitRequest() {
|
||||||
|
this.mXmppConnectionService.markMessage(this.message, Message.STATUS_OFFERED);
|
||||||
JinglePacket packet = this.bootstrapPacket("session-initiate");
|
JinglePacket packet = this.bootstrapPacket("session-initiate");
|
||||||
Content content = new Content(this.contentCreator, this.contentName);
|
Content content = new Content(this.contentCreator, this.contentName);
|
||||||
if (message.getType() == Message.TYPE_IMAGE) {
|
if (message.getType() == Message.TYPE_IMAGE) {
|
||||||
content.setTransportId(this.transportId);
|
content.setTransportId(this.transportId);
|
||||||
this.file = this.mXmppConnectionService.getFileBackend()
|
this.file = this.mXmppConnectionService.getFileBackend().getFile(
|
||||||
.getJingleFile(message, false);
|
message, false);
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
Conversation conversation = this.message.getConversation();
|
Conversation conversation = this.message.getConversation();
|
||||||
this.mXmppConnectionService.renewSymmetricKey(conversation);
|
this.mXmppConnectionService.renewSymmetricKey(conversation);
|
||||||
|
@ -369,7 +370,7 @@ public class JingleConnection implements Downloadable {
|
||||||
content.socks5transport().setChildren(getCandidatesAsElements());
|
content.socks5transport().setChildren(getCandidatesAsElements());
|
||||||
packet.setContent(content);
|
packet.setContent(content);
|
||||||
this.sendJinglePacket(packet);
|
this.sendJinglePacket(packet);
|
||||||
this.status = STATUS_INITIATED;
|
this.mJingleStatus = JINGLE_STATUS_INITIATED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,8 +383,9 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAccept() {
|
private void sendAccept() {
|
||||||
status = STATUS_ACCEPTED;
|
mJingleStatus = JINGLE_STATUS_ACCEPTED;
|
||||||
mXmppConnectionService.markMessage(message, Message.STATUS_RECEIVING);
|
this.mStatus = Downloadable.STATUS_DOWNLOADING;
|
||||||
|
mXmppConnectionService.updateConversationUi();
|
||||||
this.mJingleConnectionManager.getPrimaryCandidate(this.account,
|
this.mJingleConnectionManager.getPrimaryCandidate(this.account,
|
||||||
new OnPrimaryCandidateFound() {
|
new OnPrimaryCandidateFound() {
|
||||||
|
|
||||||
|
@ -457,7 +459,7 @@ public class JingleConnection implements Downloadable {
|
||||||
Content content = packet.getJingleContent();
|
Content content = packet.getJingleContent();
|
||||||
mergeCandidates(JingleCandidate.parse(content.socks5transport()
|
mergeCandidates(JingleCandidate.parse(content.socks5transport()
|
||||||
.getChildren()));
|
.getChildren()));
|
||||||
this.status = STATUS_ACCEPTED;
|
this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
|
||||||
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
|
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
|
||||||
this.connectNextCandidate();
|
this.connectNextCandidate();
|
||||||
return true;
|
return true;
|
||||||
|
@ -492,7 +494,8 @@ public class JingleConnection implements Downloadable {
|
||||||
} else if (content.socks5transport().hasChild("candidate-error")) {
|
} else if (content.socks5transport().hasChild("candidate-error")) {
|
||||||
Log.d(Config.LOGTAG, "received candidate error");
|
Log.d(Config.LOGTAG, "received candidate error");
|
||||||
this.receivedCandidate = true;
|
this.receivedCandidate = true;
|
||||||
if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) {
|
if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
|
||||||
|
&& (this.sentCandidate)) {
|
||||||
this.connect();
|
this.connect();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -504,7 +507,8 @@ public class JingleConnection implements Downloadable {
|
||||||
JingleCandidate candidate = getCandidate(cid);
|
JingleCandidate candidate = getCandidate(cid);
|
||||||
candidate.flagAsUsedByCounterpart();
|
candidate.flagAsUsedByCounterpart();
|
||||||
this.receivedCandidate = true;
|
this.receivedCandidate = true;
|
||||||
if ((status == STATUS_ACCEPTED) && (this.sentCandidate)) {
|
if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
|
||||||
|
&& (this.sentCandidate)) {
|
||||||
this.connect();
|
this.connect();
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG,
|
Log.d(Config.LOGTAG,
|
||||||
|
@ -532,7 +536,7 @@ public class JingleConnection implements Downloadable {
|
||||||
this.sendFallbackToIbb();
|
this.sendFallbackToIbb();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.status = STATUS_TRANSMITTING;
|
this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
|
||||||
if (connection.needsActivation()) {
|
if (connection.needsActivation()) {
|
||||||
if (connection.getCandidate().isOurs()) {
|
if (connection.getCandidate().isOurs()) {
|
||||||
Log.d(Config.LOGTAG, "candidate "
|
Log.d(Config.LOGTAG, "candidate "
|
||||||
|
@ -619,13 +623,15 @@ public class JingleConnection implements Downloadable {
|
||||||
packet.setReason(reason);
|
packet.setReason(reason);
|
||||||
this.sendJinglePacket(packet);
|
this.sendJinglePacket(packet);
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this.status = STATUS_FINISHED;
|
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
||||||
this.mXmppConnectionService.markMessage(this.message,
|
this.message.setStatus(Message.STATUS_RECEIVED);
|
||||||
Message.STATUS_RECEIVED);
|
this.message.setDownloadable(null);
|
||||||
|
this.mXmppConnectionService.updateMessage(message);
|
||||||
this.mJingleConnectionManager.finishConnection(this);
|
this.mJingleConnectionManager.finishConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendFallbackToIbb() {
|
private void sendFallbackToIbb() {
|
||||||
|
Log.d(Config.LOGTAG, "sending fallback to ibb");
|
||||||
JinglePacket packet = this.bootstrapPacket("transport-replace");
|
JinglePacket packet = this.bootstrapPacket("transport-replace");
|
||||||
Content content = new Content(this.contentCreator, this.contentName);
|
Content content = new Content(this.contentCreator, this.contentName);
|
||||||
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
||||||
|
@ -637,6 +643,7 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean receiveFallbackToIbb(JinglePacket packet) {
|
private boolean receiveFallbackToIbb(JinglePacket packet) {
|
||||||
|
Log.d(Config.LOGTAG, "receiving fallack to ibb");
|
||||||
String receivedBlockSize = packet.getJingleContent().ibbTransport()
|
String receivedBlockSize = packet.getJingleContent().ibbTransport()
|
||||||
.getAttribute("block-size");
|
.getAttribute("block-size");
|
||||||
if (receivedBlockSize != null) {
|
if (receivedBlockSize != null) {
|
||||||
|
@ -691,7 +698,7 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSuccess() {
|
private void receiveSuccess() {
|
||||||
this.status = STATUS_FINISHED;
|
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
||||||
this.mXmppConnectionService.markMessage(this.message,
|
this.mXmppConnectionService.markMessage(this.message,
|
||||||
Message.STATUS_SEND);
|
Message.STATUS_SEND);
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
|
@ -699,20 +706,15 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
this.status = STATUS_CANCELED;
|
this.mJingleStatus = JINGLE_STATUS_CANCELED;
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
if (this.message != null) {
|
if (this.message != null) {
|
||||||
if (this.responder.equals(account.getFullJid())) {
|
if (this.responder.equals(account.getFullJid())) {
|
||||||
this.mXmppConnectionService.markMessage(this.message,
|
this.mStatus = Downloadable.STATUS_FAILED;
|
||||||
Message.STATUS_RECEPTION_FAILED);
|
this.mXmppConnectionService.updateConversationUi();
|
||||||
} else {
|
} else {
|
||||||
if (this.status == STATUS_INITIATED) {
|
this.mXmppConnectionService.markMessage(this.message,
|
||||||
this.mXmppConnectionService.markMessage(this.message,
|
Message.STATUS_SEND_FAILED);
|
||||||
Message.STATUS_SEND_REJECTED);
|
|
||||||
} else {
|
|
||||||
this.mXmppConnectionService.markMessage(this.message,
|
|
||||||
Message.STATUS_SEND_FAILED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.mJingleConnectionManager.finishConnection(this);
|
this.mJingleConnectionManager.finishConnection(this);
|
||||||
|
@ -789,7 +791,7 @@ public class JingleConnection implements Downloadable {
|
||||||
.setAttribute("cid", cid);
|
.setAttribute("cid", cid);
|
||||||
packet.setContent(content);
|
packet.setContent(content);
|
||||||
this.sentCandidate = true;
|
this.sentCandidate = true;
|
||||||
if ((receivedCandidate) && (status == STATUS_ACCEPTED)) {
|
if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
this.sendJinglePacket(packet);
|
this.sendJinglePacket(packet);
|
||||||
|
@ -802,7 +804,7 @@ public class JingleConnection implements Downloadable {
|
||||||
content.socks5transport().addChild("candidate-error");
|
content.socks5transport().addChild("candidate-error");
|
||||||
packet.setContent(content);
|
packet.setContent(content);
|
||||||
this.sentCandidate = true;
|
this.sentCandidate = true;
|
||||||
if ((receivedCandidate) && (status == STATUS_ACCEPTED)) {
|
if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
this.sendJinglePacket(packet);
|
this.sendJinglePacket(packet);
|
||||||
|
@ -816,8 +818,8 @@ public class JingleConnection implements Downloadable {
|
||||||
return this.responder;
|
return this.responder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStatus() {
|
public int getJingleStatus() {
|
||||||
return this.status;
|
return this.mJingleStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean equalCandidateExists(JingleCandidate candidate) {
|
private boolean equalCandidateExists(JingleCandidate candidate) {
|
||||||
|
@ -867,17 +869,34 @@ public class JingleConnection implements Downloadable {
|
||||||
return this.transport;
|
return this.transport;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public boolean start() {
|
||||||
if (status == STATUS_INITIATED) {
|
if (account.getStatus() == Account.STATUS_ONLINE) {
|
||||||
new Thread(new Runnable() {
|
if (mJingleStatus == JINGLE_STATUS_INITIATED) {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
sendAccept();
|
sendAccept();
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, "status (" + status + ") was not ok");
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStatus() {
|
||||||
|
return this.mStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFileSize() {
|
||||||
|
if (this.file != null) {
|
||||||
|
return this.file.getExpectedSize();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,14 @@ import android.util.Log;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
|
|
||||||
public class JingleConnectionManager {
|
public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
private XmppConnectionService xmppConnectionService;
|
|
||||||
|
|
||||||
private List<JingleConnection> connections = new CopyOnWriteArrayList<JingleConnection>();
|
private List<JingleConnection> connections = new CopyOnWriteArrayList<JingleConnection>();
|
||||||
|
|
||||||
private HashMap<String, JingleCandidate> primaryCandidates = new HashMap<String, JingleCandidate>();
|
private HashMap<String, JingleCandidate> primaryCandidates = new HashMap<String, JingleCandidate>();
|
||||||
|
@ -28,7 +26,7 @@ public class JingleConnectionManager {
|
||||||
private SecureRandom random = new SecureRandom();
|
private SecureRandom random = new SecureRandom();
|
||||||
|
|
||||||
public JingleConnectionManager(XmppConnectionService service) {
|
public JingleConnectionManager(XmppConnectionService service) {
|
||||||
this.xmppConnectionService = service;
|
super(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deliverPacket(Account account, JinglePacket packet) {
|
public void deliverPacket(Account account, JinglePacket packet) {
|
||||||
|
@ -38,7 +36,7 @@ public class JingleConnectionManager {
|
||||||
connections.add(connection);
|
connections.add(connection);
|
||||||
} else {
|
} else {
|
||||||
for (JingleConnection connection : connections) {
|
for (JingleConnection connection : connections) {
|
||||||
if (connection.getAccountJid().equals(account.getFullJid())
|
if (connection.getAccount() == account
|
||||||
&& connection.getSessionId().equals(
|
&& connection.getSessionId().equals(
|
||||||
packet.getSessionId())
|
packet.getSessionId())
|
||||||
&& connection.getCounterPart().equals(packet.getFrom())) {
|
&& connection.getCounterPart().equals(packet.getFrom())) {
|
||||||
|
@ -46,8 +44,13 @@ public class JingleConnectionManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
account.getXmppConnection().sendIqPacket(
|
IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR);
|
||||||
packet.generateRespone(IqPacket.TYPE_ERROR), null);
|
Element error = response.addChild("error");
|
||||||
|
error.setAttribute("type", "cancel");
|
||||||
|
error.addChild("item-not-found",
|
||||||
|
"urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
|
error.addChild("unknown-session", "urn:xmpp:jingle:errors:1");
|
||||||
|
account.getXmppConnection().sendIqPacket(response, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,10 +71,6 @@ public class JingleConnectionManager {
|
||||||
this.connections.remove(connection);
|
this.connections.remove(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmppConnectionService getXmppConnectionService() {
|
|
||||||
return this.xmppConnectionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getPrimaryCandidate(Account account,
|
public void getPrimaryCandidate(Account account,
|
||||||
final OnPrimaryCandidateFound listener) {
|
final OnPrimaryCandidateFound listener) {
|
||||||
if (!this.primaryCandidates.containsKey(account.getJid())) {
|
if (!this.primaryCandidates.containsKey(account.getJid())) {
|
||||||
|
@ -128,16 +127,6 @@ public class JingleConnectionManager {
|
||||||
return new BigInteger(50, random).toString(32);
|
return new BigInteger(50, random).toString(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getAutoAcceptFileSize() {
|
|
||||||
String config = this.xmppConnectionService.getPreferences().getString(
|
|
||||||
"auto_accept_file_size", "524288");
|
|
||||||
try {
|
|
||||||
return Long.parseLong(config);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return 524288;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deliverIbbPacket(Account account, IqPacket packet) {
|
public void deliverIbbPacket(Account account, IqPacket packet) {
|
||||||
String sid = null;
|
String sid = null;
|
||||||
Element payload = null;
|
Element payload = null;
|
||||||
|
@ -152,7 +141,8 @@ public class JingleConnectionManager {
|
||||||
}
|
}
|
||||||
if (sid != null) {
|
if (sid != null) {
|
||||||
for (JingleConnection connection : connections) {
|
for (JingleConnection connection : connections) {
|
||||||
if (connection.hasTransportId(sid)) {
|
if (connection.getAccount() == account
|
||||||
|
&& connection.hasTransportId(sid)) {
|
||||||
JingleTransport transport = connection.getTransport();
|
JingleTransport transport = connection.getTransport();
|
||||||
if (transport instanceof JingleInbandTransport) {
|
if (transport instanceof JingleInbandTransport) {
|
||||||
JingleInbandTransport inbandTransport = (JingleInbandTransport) transport;
|
JingleInbandTransport inbandTransport = (JingleInbandTransport) transport;
|
||||||
|
@ -170,7 +160,7 @@ public class JingleConnectionManager {
|
||||||
|
|
||||||
public void cancelInTransmission() {
|
public void cancelInTransmission() {
|
||||||
for (JingleConnection connection : this.connections) {
|
for (JingleConnection connection : this.connections) {
|
||||||
if (connection.getStatus() == JingleConnection.STATUS_TRANSMITTING) {
|
if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) {
|
||||||
connection.cancel();
|
connection.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.security.Key;
|
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class JingleFile extends File {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 2247012619505115863L;
|
|
||||||
|
|
||||||
private long expectedSize = 0;
|
|
||||||
private String sha1sum;
|
|
||||||
private Key aeskey;
|
|
||||||
|
|
||||||
public JingleFile(String path) {
|
|
||||||
super(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getSize() {
|
|
||||||
return super.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExpectedSize() {
|
|
||||||
if (this.aeskey != null) {
|
|
||||||
return (this.expectedSize / 16 + 1) * 16;
|
|
||||||
} else {
|
|
||||||
return this.expectedSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExpectedSize(long size) {
|
|
||||||
this.expectedSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSha1Sum() {
|
|
||||||
return this.sha1sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSha1Sum(String sum) {
|
|
||||||
this.sha1sum = sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setKey(byte[] key) {
|
|
||||||
if (key.length >= 32) {
|
|
||||||
byte[] secretKey = new byte[32];
|
|
||||||
System.arraycopy(key, 0, secretKey, 0, 32);
|
|
||||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
|
||||||
} else if (key.length >= 16) {
|
|
||||||
byte[] secretKey = new byte[16];
|
|
||||||
System.arraycopy(key, 0, secretKey, 0, 16);
|
|
||||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
|
||||||
} else {
|
|
||||||
Log.d(Config.LOGTAG, "weird key");
|
|
||||||
}
|
|
||||||
Log.d(Config.LOGTAG,
|
|
||||||
"using aes key "
|
|
||||||
+ CryptoHelper.bytesToHex(this.aeskey.getEncoded()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Key getKey() {
|
|
||||||
return this.aeskey;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
@ -10,6 +9,7 @@ import java.util.Arrays;
|
||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
|
@ -26,7 +26,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
|
|
||||||
private boolean established = false;
|
private boolean established = false;
|
||||||
|
|
||||||
private JingleFile file;
|
private DownloadableFile file;
|
||||||
|
|
||||||
private InputStream fileInputStream = null;
|
private InputStream fileInputStream = null;
|
||||||
private OutputStream fileOutputStream;
|
private OutputStream fileOutputStream;
|
||||||
|
@ -77,7 +77,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive(JingleFile file,
|
public void receive(DownloadableFile file,
|
||||||
OnFileTransmissionStatusChanged callback) {
|
OnFileTransmissionStatusChanged callback) {
|
||||||
this.onFileTransmissionStatusChanged = callback;
|
this.onFileTransmissionStatusChanged = callback;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
|
@ -86,7 +86,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
digest.reset();
|
digest.reset();
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
this.fileOutputStream = getOutputStream(file);
|
this.fileOutputStream = file.createOutputStream();
|
||||||
if (this.fileOutputStream == null) {
|
if (this.fileOutputStream == null) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
|
@ -100,20 +100,19 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(JingleFile file, OnFileTransmissionStatusChanged callback) {
|
public void send(DownloadableFile file,
|
||||||
|
OnFileTransmissionStatusChanged callback) {
|
||||||
this.onFileTransmissionStatusChanged = callback;
|
this.onFileTransmissionStatusChanged = callback;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
try {
|
try {
|
||||||
this.digest = MessageDigest.getInstance("SHA-1");
|
this.digest = MessageDigest.getInstance("SHA-1");
|
||||||
this.digest.reset();
|
this.digest.reset();
|
||||||
fileInputStream = this.getInputStream(file);
|
fileInputStream = this.file.createInputStream();
|
||||||
if (fileInputStream == null) {
|
if (fileInputStream == null) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.sendNextBlock();
|
this.sendNextBlock();
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
callback.onFileTransferAborted();
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
|
||||||
public class JingleSocks5Transport extends JingleTransport {
|
public class JingleSocks5Transport extends JingleTransport {
|
||||||
|
@ -29,11 +30,11 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
StringBuilder destBuilder = new StringBuilder();
|
StringBuilder destBuilder = new StringBuilder();
|
||||||
destBuilder.append(jingleConnection.getSessionId());
|
destBuilder.append(jingleConnection.getSessionId());
|
||||||
if (candidate.isOurs()) {
|
if (candidate.isOurs()) {
|
||||||
destBuilder.append(jingleConnection.getAccountJid());
|
destBuilder.append(jingleConnection.getAccount().getFullJid());
|
||||||
destBuilder.append(jingleConnection.getCounterPart());
|
destBuilder.append(jingleConnection.getCounterPart());
|
||||||
} else {
|
} else {
|
||||||
destBuilder.append(jingleConnection.getCounterPart());
|
destBuilder.append(jingleConnection.getCounterPart());
|
||||||
destBuilder.append(jingleConnection.getAccountJid());
|
destBuilder.append(jingleConnection.getAccount().getFullJid());
|
||||||
}
|
}
|
||||||
mDigest.reset();
|
mDigest.reset();
|
||||||
this.destination = CryptoHelper.bytesToHex(mDigest
|
this.destination = CryptoHelper.bytesToHex(mDigest
|
||||||
|
@ -86,7 +87,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(final JingleFile file,
|
public void send(final DownloadableFile file,
|
||||||
final OnFileTransmissionStatusChanged callback) {
|
final OnFileTransmissionStatusChanged callback) {
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
digest.reset();
|
digest.reset();
|
||||||
fileInputStream = getInputStream(file);
|
fileInputStream = file.createInputStream();
|
||||||
if (fileInputStream == null) {
|
if (fileInputStream == null) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
|
@ -132,7 +133,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receive(final JingleFile file,
|
public void receive(final DownloadableFile file,
|
||||||
final OnFileTransmissionStatusChanged callback) {
|
final OnFileTransmissionStatusChanged callback) {
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@ -145,7 +146,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
socket.setSoTimeout(30000);
|
socket.setSoTimeout(30000);
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
OutputStream fileOutputStream = getOutputStream(file);
|
OutputStream fileOutputStream = file.createOutputStream();
|
||||||
if (fileOutputStream == null) {
|
if (fileOutputStream == null) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,88 +1,13 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.CipherOutputStream;
|
|
||||||
import javax.crypto.CipherInputStream;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public abstract class JingleTransport {
|
public abstract class JingleTransport {
|
||||||
public abstract void connect(final OnTransportConnected callback);
|
public abstract void connect(final OnTransportConnected callback);
|
||||||
|
|
||||||
public abstract void receive(final JingleFile file,
|
public abstract void receive(final DownloadableFile file,
|
||||||
final OnFileTransmissionStatusChanged callback);
|
final OnFileTransmissionStatusChanged callback);
|
||||||
|
|
||||||
public abstract void send(final JingleFile file,
|
public abstract void send(final DownloadableFile file,
|
||||||
final OnFileTransmissionStatusChanged callback);
|
final OnFileTransmissionStatusChanged callback);
|
||||||
|
|
||||||
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
||||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
|
||||||
|
|
||||||
protected InputStream getInputStream(JingleFile file)
|
|
||||||
throws FileNotFoundException {
|
|
||||||
if (file.getKey() == null) {
|
|
||||||
return new FileInputStream(file);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, file.getKey(), ips);
|
|
||||||
Log.d(Config.LOGTAG, "opening encrypted input stream");
|
|
||||||
return new CipherInputStream(new FileInputStream(file), cipher);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected OutputStream getOutputStream(JingleFile file)
|
|
||||||
throws FileNotFoundException {
|
|
||||||
if (file.getKey() == null) {
|
|
||||||
return new FileOutputStream(file);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, file.getKey(), ips);
|
|
||||||
Log.d(Config.LOGTAG, "opening encrypted output stream");
|
|
||||||
return new CipherOutputStream(new FileOutputStream(file),
|
|
||||||
cipher);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
|
||||||
public interface OnFileTransmissionStatusChanged {
|
public interface OnFileTransmissionStatusChanged {
|
||||||
public void onFileTransmitted(JingleFile file);
|
public void onFileTransmitted(DownloadableFile file);
|
||||||
|
|
||||||
public void onFileTransferAborted();
|
public void onFileTransferAborted();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleFile;
|
|
||||||
|
|
||||||
public class Content extends Element {
|
public class Content extends Element {
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ public class Content extends Element {
|
||||||
this.transportId = sid;
|
this.transportId = sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFileOffer(JingleFile actualFile, boolean otr) {
|
public void setFileOffer(DownloadableFile actualFile, boolean otr) {
|
||||||
Element description = this.addChild("description",
|
Element description = this.addChild("description",
|
||||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||||
Element offer = description.addChild("offer");
|
Element offer = description.addChild("offer");
|
||||||
|
|