Merge branch 'feature/release-4.1'

This commit is contained in:
Sylvain Berfini 2019-04-16 13:42:52 +02:00
commit 7db1fe5380
1078 changed files with 49173 additions and 293095 deletions

44
.gitignore vendored
View file

@ -6,55 +6,21 @@
.settings .settings
adb.pid adb.pid
bc-android.keystore bc-android.keystore
bin
build build
certdata.txt *.iml
check_tools.mk
default.properties
doc
gen
liblinphone-junit-report.xml
liblinphone-sdk/android-*
liblinphonetester_*.zip
libs
libs-debug
linphone-android.iml
linphone-junit-report*.xml
linphonetester_*.zip
lint.xml lint.xml
local.properties local.properties
Makefile
obj
proguard-project.txt
project.properties
res/.DS_Store res/.DS_Store
res/raw/lpconfig.xsd res/raw/lpconfig.xsd
submodules/externals/build/cunit/CUnit/
submodules/externals/build/ffmpeg/arm/
submodules/externals/build/ffmpeg/x86
submodules/externals/build/libvpx/arm
submodules/externals/build/libvpx/x86
submodules/externals/build/openh264/arm
submodules/externals/build/openh264/x86
submodules/externals/ffmpeg/arm/
tests/*$py.class
tests/build.xml
tests/project.properties
ant_password.properties
liblinphone_tester/liblinphonetester_*
liblinphone_tester/tests.output
tests/linphonetester_*
tests/tests.output
WORK
.d .d
google-services.json
.*clang* .*clang*
**/*.iml **/*.iml
src/linphone-wrapper
liblinphone_tester/res/raw/
**/.classpath **/.classpath
**/.project **/.project
**/*.kdev4 **/*.kdev4
liblinphone-sdk/res/
**/.vscode **/.vscode
res/value-hi_IN res/value-hi_IN
linphone-sdk-android/*.aar
app/release
keystore.properties
app/src/main/res/xml/contacts.xml

View file

@ -0,0 +1,33 @@
job-android:
stage: build
tags: [ "docker-android" ]
image: gitlab.linphone.org:4567/bc/public/linphone-android/bc-dev-android:28
before_script:
- if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then eval $(ssh-agent -s); fi
- if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then echo "$SCP_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null; fi
script:
- sdkmanager
- scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_KEYSTORE_PATH app/
- scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_GOOGLE_SERVICES_PATH app/
- echo storePassword=$ANDROID_KEYSTORE_PASSWORD > keystore.properties
- echo keyPassword=$ANDROID_KEYSTORE_KEY_PASSWORD >> keystore.properties
- echo keyAlias=$ANDROID_KEYSTORE_KEY_ALIAS >> keystore.properties
- echo storeFile=$ANDROID_KEYSTORE_FILE >> keystore.properties
- ./gradlew assembleDebug
- ./gradlew assembleRelease
artifacts:
paths:
- ./app/build/outputs/apk/debug/linphone-android-debug-*.apk
- ./app/build/outputs/apk/release/linphone-android-release-*.apk
when: always
expire_in: 1 week
.scheduled-job-android:
extends: job-android
only:
- schedules

View file

@ -0,0 +1,12 @@
job-android-upload:
stage: deploy
tags: [ "deploy" ]
only:
- schedules
dependencies:
- job-android
script:
- cd app/build/outputs/apk/ && rsync ./debug/*.apk $DEPLOY_SERVER:$ANDROID_DEPLOY_DIRECTORY

19
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,19 @@
#################################################
# Base configuration
#################################################
#################################################
# Platforms to test
#################################################
include:
- '.gitlab-ci-files/job-android.yml'
- '.gitlab-ci-files/job-upload.yml'
stages:
- build
- deploy

122
.gitmodules vendored
View file

@ -1,122 +0,0 @@
[submodule "submodules/linphone"]
path = submodules/linphone
url = https://gitlab.linphone.org/BC/public/linphone.git
[submodule "submodules/externals/gsm"]
path = submodules/externals/gsm
url = https://gitlab.linphone.org/BC/public/external/gsm.git
[submodule "submodules/externals/speex"]
path = submodules/externals/speex
url = https://gitlab.linphone.org/BC/public/external/speex.git
[submodule "submodules/externals/ffmpeg"]
path = submodules/externals/ffmpeg
url = https://gitlab.linphone.org/BC/public/external/ffmpeg.git
ignore = dirty
[submodule "submodules/externals/x264"]
path = submodules/externals/x264
url = https://gitlab.linphone.org/BC/public/external/x264.git
ignore = dirty
[submodule "submodules/msx264"]
path = submodules/msx264
url = https://gitlab.linphone.org/BC/public/msx264.git
[submodule "submodules/externals/opencore-amr"]
path = submodules/externals/opencore-amr
url = https://gitlab.linphone.org/BC/public/external/opencore-amr.git
ignore = dirty
[submodule "submodules/msamr"]
path = submodules/msamr
url = https://gitlab.linphone.org/BC/public/msamr.git
[submodule "submodules/externals/libvpx"]
path = submodules/externals/libvpx
url = https://gitlab.linphone.org/BC/public/external/libvpx.git
ignore = dirty
[submodule "submodules/bzrtp"]
path = submodules/bzrtp
url = https://gitlab.linphone.org/BC/public/bzrtp.git
[submodule "submodules/externals/srtp"]
path = submodules/externals/srtp
url = https://gitlab.linphone.org/BC/public/external/srtp.git
[submodule "submodules/mssilk"]
path = submodules/mssilk
url = https://gitlab.linphone.org/BC/public/mssilk.git
[submodule "submodules/bcg729"]
path = submodules/bcg729
url = https://gitlab.linphone.org/BC/public/bcg729.git
[submodule "submodules/belle-sip"]
path = submodules/belle-sip
url = https://gitlab.linphone.org/BC/public/belle-sip.git
[submodule "submodules/externals/libxml2"]
path = submodules/externals/libxml2
url = https://gitlab.linphone.org/BC/public/external/libxml2.git
ignore = dirty
[submodule "submodules/externals/libupnp"]
path = submodules/externals/libupnp
url = https://gitlab.linphone.org/BC/public/external/libupnp.git
[submodule "submodules/externals/opus"]
path = submodules/externals/opus
url = https://gitlab.linphone.org/BC/public/external/opus.git
ignore = dirty
[submodule "submodules/mswebrtc"]
path = submodules/mswebrtc
url = https://gitlab.linphone.org/BC/public/mswebrtc.git
[submodule "submodules/msopenh264"]
path = submodules/msopenh264
url = https://gitlab.linphone.org/BC/public/msopenh264.git
[submodule "submodules/externals/openh264"]
path = submodules/externals/openh264
url = https://gitlab.linphone.org/BC/public/external/openh264.git
ignore = dirty
[submodule "submodules/mscodec2"]
path = submodules/mscodec2
url = https://gitlab.linphone.org/BC/public/mscodec2.git
[submodule "submodules/bctoolbox"]
path = submodules/bctoolbox
url = https://gitlab.linphone.org/BC/public/bctoolbox.git
[submodule "submodules/externals/mbedtls"]
path = submodules/externals/mbedtls
url = https://gitlab.linphone.org/BC/public/external/mbedtls.git
ignore = dirty
[submodule "submodules/cmake-builder"]
path = submodules/cmake-builder
url = https://gitlab.linphone.org/BC/public/linphone-cmake-builder.git
[submodule "submodules/externals/bv16-floatingpoint"]
path = submodules/externals/bv16-floatingpoint
url = https://gitlab.linphone.org/BC/public/external/bv16-floatingpoint.git
[submodule "submodules/belr"]
path = submodules/belr
url = https://gitlab.linphone.org/BC/public/belr.git
[submodule "submodules/belcard"]
path = submodules/belcard
url = https://gitlab.linphone.org/BC/public/belcard.git
[submodule "submodules/bcunit"]
path = submodules/bcunit
url = https://gitlab.linphone.org/BC/public/bcunit.git
[submodule "submodules/externals/vo-amrwbenc"]
path = submodules/externals/vo-amrwbenc
url = https://gitlab.linphone.org/BC/public/external/vo-amrwbenc.git
[submodule "submodules/externals/codec2"]
path = submodules/externals/codec2
url = https://gitlab.linphone.org/BC/public/external/codec2.git
[submodule "submodules/externals/libjpeg-turbo"]
path = submodules/externals/libjpeg-turbo
url = https://gitlab.linphone.org/BC/public/external/libjpeg-turbo.git
[submodule "submodules/mediastreamer2"]
path = submodules/mediastreamer2
url = https://gitlab.linphone.org/BC/public/mediastreamer2.git
[submodule "submodules/oRTP"]
path = submodules/oRTP
url = https://gitlab.linphone.org/BC/public/ortp.git
[submodule "submodules/bcmatroska2"]
path = submodules/bcmatroska2
url = https://gitlab.linphone.org/BC/public/bcmatroska2.git
[submodule "submodules/externals/xerces-c"]
path = submodules/externals/xerces-c
url = https://gitlab.linphone.org/BC/public/external/xerces-c.git
[submodule "submodules/externals/libxsd"]
path = submodules/externals/libxsd
url = https://gitlab.linphone.org/BC/public/external/libxsd.git
[submodule "submodules/externals/soci"]
path = submodules/externals/soci
url = https://gitlab.linphone.org/BC/public/external/soci.git
[submodule "submodules/externals/zxing-cpp"]
path = submodules/externals/zxing-cpp
url = https://gitlab.linphone.org/BC/public/external/zxing-cpp.git

View file

@ -5,6 +5,6 @@ minimum_perc = 1
type = ANDROID type = ANDROID
[linphone-android.stringsxml] [linphone-android.stringsxml]
file_filter = res/values-<lang>/strings.xml file_filter = app/src/main/res/values-<lang>/strings.xml
source_file = res/values/strings.xml source_file = app/src/main/res/values/strings.xml
source_lang = en source_lang = en

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.linphone"
xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto"
android:versionCode="4001"
android:versionName="4.0.0">
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="28"/>
</manifest>

View file

@ -10,9 +10,39 @@ Group changes to describe their impact on the project, as follows:
Fixed for any bug fixes. Fixed for any bug fixes.
Security to invite users to upgrade in case of vulnerabilities. Security to invite users to upgrade in case of vulnerabilities.
## [Incomming] ## [4.1] - 2019-xx-xx
- feature: support of H265 codec.
- feature: use TextureView instead of GL2JNIView, easier to use and will fix issues ### Improvements
- Improved UI.
- Added adaptive icon for Android 8+.
- Use of binary SDK hosted in Maven repository instead of having to build it manually.
- Asynchronous fetch of native contacts.
- Removed unused graphical resources and improved some existing ones.
- Updated translations.
- Updated sliders used to answer or hangup incoming call so they can be used with accessibility tools.
- Reworked settings, new ones are available, some unused have been removed.
- Video overlay also displays local preview now.
- In About fragment, the license text is a link to the full license on gnu.org.
### Features
- Added new end to end LIME encryption for instant messaging, both for single chat rooms and group ones.
- Send multiple files with text in the same chat message.
- Allow multiple images to be shared from an external app through linphone at once.
- Support of H265 codec.
- Use TextureView instead of GL2JNIView, easier to use and will fix issues.
- Send SMS to invite your friends in using Linphone.
- Reply to / mark as read chat message in notification.
- Answer or hangup calls in notification.
- Setting to automatically download incoming files, either always, never or depending on their sizes. This adds the feature of having images received by file transfer in notifications on Android 8+.
- Call recording.
- Get remote provisioning URL from QR code scan.
- Allow rich input while composing chat message to easily send images from keyboard.
- Animated GIFs are no longer displayed as static images.
- A dark mode is available.
- List all calls with the same SIP address in history detail view.
### Fixes
- Fixed basic chat room with same correspondant with multiple local accounts always displaying the same one when clicking on it in chat rooms list view.
## [4.0.1] - 2018-06-26 ## [4.0.1] - 2018-06-26
@ -34,7 +64,6 @@ Group changes to describe their impact on the project, as follows:
### Fixed ### Fixed
- issue with changing push notification token not passed to library, possibly resulting in a loss of incoming calls. - issue with changing push notification token not passed to library, possibly resulting in a loss of incoming calls.
## [3.3.0] - 2017-10-18 ## [3.3.0] - 2017-10-18
### Added ### Added

156
README.md
View file

@ -1,100 +1,104 @@
[![pipeline status](https://gitlab.linphone.org/BC/public/linphone-android/badges/master/pipeline.svg)](https://gitlab.linphone.org/BC/public/linphone-android/commits/master) [![pipeline status](https://gitlab.linphone.org/BC/public/linphone-sdk/badges/master/pipeline.svg)](https://gitlab.linphone.org/BC/public/linphone-android/commits/feature/release-4.1)
Linphone is a free VoIP and video softphone based on the SIP protocol. Linphone is a free VoIP and video softphone based on the SIP protocol.
# COMPILATION INSTRUCTIONS # What's new
## To build liblinphone for Android, you must: Now the default way of building linphone-android is to download the AAR SDK in our maven repository.
Compared to previous versions, this project no longer uses submodules developper has to build in order to get a working app.
However, if you wish to use a locally compiled SDK see below how to proceed.
1. Download the latest Android sdk with platform-tools and tools updated to latest revision, then add both 'tools' and 'platform-tools' folders in your path and the android-sdk folder to ANDROID_HOME environment variable. We offer different flavors for the SDK in our maven repository: org.linphone.no-video (a build without video) and org.linphone.legacy (old java wrapper if you didn't migrate your app code to the new one yet).
2. Download the latest Android NDK from google and add it to your path (no symlink !!!) and ANDROID_NDK environment variable. The repository structure has also been cleaned and updated, and changing the package name can now be done in a single step.
This allows developpers to keep a stable version as well as a developpment one on the same device easily.
3. Install _yasm_, _nasm_ (For OpenH224 support only), _python_, _pkg_config_, _doxygen_, _graphviz_ and _cmake(>=3.12)_. # Building the app
* On 64 bits linux systems you'll need the _ia32-libs_ package.
* With the latest Debian (multiarch), you need this:
* `dpkg --add-architecture i386`
* `aptitude update`
* `aptitude install libstdc++6:i386 libgcc1:i386 zlib1g:i386 libncurses5:i386`
4. Run `./prepare.py` in the top level directory. This will configure the build and generate a Makefile in the top level directory. Some options can be passed to choose what you want to include in the build and the platforms for which you want to build. Use `./prepare.py --help` to see what these options are. If you have Android Studio, simply open the project, wait for the gradle synchronization and then build/install the app.
It will download the linphone library from our Maven repository as an AAR file so you don't have to build anything yourself.
5. Run the Makefile script in the top level directory, `make`.
6. _(optional)_ To install the generated apk into a plugged device, run `make install`.
7. _(optional)_ To generate a liblinphone SDK zip containing a full jar and native libraries, run `make liblinphone-android-sdk`
8. _(optional)_ To generate a libmediastreamer2 SDK zip containing a full jar and native libraries, run `make mediastreamer2-sdk`
9. _(optional)_ To generate a signed apk to publish on the Google Play, run `make release`. Make sure you filled the gradle.properties values for version.name, store file, store password, key alias and key password to correctly sign the generated apk:
* RELEASE_STORE_FILE=""
* RELEASE_STORE_PASSWORD=
* RELEASE_KEY_ALIAS=
* RELEASE_KEY_PASSWORD=
If you don't, the passwords will be asked at the signing phase.
10. _(optional)_ Once you compiled the libraries succesfully with 'make', you can reduce the compilation time using 'make quick': it will only generate a new APK from java files.
## To create an apk with a different package name
You need to edit the build.gradle file:
1. look for the function named "getPackageName()" and change it value accordingly
2. also update the values in the AndroidManifest file where the comment <!-- Change package ! --> appears
3. change the package name also in the files: res/xml/syncadapter.xml, res/xml/contacts.xml and res/values/non_localizable_custom where <!-- Change package ! --> appears
4. run again the Makefile script by calling "make"
## To run the liblinphone test suite on android
Simply run `make liblinphone_tester`. This will be build everything, generate an apk, and install it on the connected device if any.
You can speed up the compilation by using ccache (compiler cache, see [ccache.samba.org](https://ccache.samba.org/)). Give the *"-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"* options to the *prepare.py* script.
# PUSH NOTIFICATION
## Firebase
To enable firebase in Linphone, just add your 'google-service.json' in project root, add your key at 'push_sender_id' and add 'firebase' at 'push_type' in 'res/values/non_localizable_custom.xml'
Be sure to have all services for Firebase in your 'AndroidManifest.xml'
# TROUBLESHOOTING
If you encounter the following issue:
If you don't have Android Studio, you can build and install the app using gradle:
``` ```
E/dalvikvm( 2465): dlopen("/data/app-lib/org.linphone-1/liblinphone-armeabi-v7a.so") failed: ./gradlew assembleDebug
Cannot load library: soinfo_relocate(linker.cpp:975): cannot locate symbol "rand" referenced ```
by "liblinphone-armeabi-v7a.so" will compile the APK file (assembleRelease to instead if you want to build a release package), and then
```
./gradlew installDebug
```
to install the generated APK in the previous step (use installRelease instead if you built a release package).
APK files are stored within ```./app/build/outputs/apk/debug/``` and ```./app/build/outputs/apk/release/``` directories.
## Building a local SDK
1. Clone the linphone-sdk repository from out gitlab:
```
git clone https://gitlab.linphone.org/BC/public/linphone-sdk.git --recursive
``` ```
It's because you have installed the android-21 platform (which is chosen automatically because it's the most recent) and you deployed the apk on a android < 5 device. 2. Follow the instructions in the linphone-sdk/README file to build the SDK.
To fix this, in the Makefile, force *ANDROID_MOST_RECENT_TARGET=android-19*.
If you encounter troubles with the make clean target and you are using the 8e android ndk, the solution can be found [here](https://groups.google.com/forum/?fromgroups=#!topic/android-ndk/3wIbb-h3nDU).
If you built the app using eclipse, ensure you ran at least once the make command (see above steps 0 to 3) ! Else you'll have this exceptions:
3. Edit in the linphone-sdk-android folder of this project the symbolic link (debug and/or release) to the generated AAR.
We recommend to at least create the link for the release AAR that can be used for debug APK flavor because it is smaller and will reduce the time required to install the APK.
``` ```
FATAL EXCEPTION: main ln -s <path to linphone-sdk>/linphone-sdk/build/linphone-sdk/bin/outputs/aar/linphone-sdk-android-release.aar linphone-sdk-android/linphone-sdk-android-release.aar
java.lang.ExceptionInInitializerError ln -s <path to linphone-sdk>/linphone-sdk/build/linphone-sdk/bin/outputs/aar/linphone-sdk-android-debug.aar linphone-sdk-android/linphone-sdk-android-debug.aar
...
Caused by: java.lang.UnsatisfiedLinkError: Couldn't load linphone-armeabi-v7a: findLibrary
returned null
``` ```
# BUILD OPTIONS 4. Rebuild the app in Android Studio.
The build options are to be passed to the *prepare.py* script. For example to enable the x264 encoder give the *"-DENABLE_X264=YES"* to *prepare.py*. ## Native debugging
The available options can be listed with the `./prepare.py --list-features` 1. Install LLDB from SDK Tools in Android-studio.
2. In Android-studio go to Run->Edit Configurations->Debugger.
3. Select 'Dual' or 'Native' and add the path to linphone-sdk libraries.
4. Open native file and put your breakpoint on it.
5. Make sure you are using the debug AAR in the app/build.gradle script and not the release one (to have faster builds by default the release AAR is used even for debug APK flavor).
6. Debug app.
## Create an APK with a different package name
Before the 4.1 release, there were a lot of files to edit to change the package name.
Now, simply edit the app/build.gradle file and change the value returned by method ```getPackageName()```
The next build will automatically use this value everywhere thanks to ```manifestPlaceholders``` feature of gradle and Android.
You may have already noticed that the app installed by Android Studio has ```org.linphone.debug``` package name.
If you build the app as release, the package name will be ```org.linphone```.
## Firebase push notifications
Now that Google Cloud Messaging has been deprecated and will be completely removed on April 11th 2019, the only official way of using push notifications is through Firebase.
However to make Firebase push notifications work, the project needs to have a file named app/google-services.json that contains some confidential informations, so you won't find it (it has been added to the .gitignore file).
This means that if you compile this project, you won't have push notification feature working in the app!
To enable them, just add your own ```google-services.json``` in the app folder.
## Translations
We use transifex so the community can translate the strings of the app in their own language.
Note for developpers: here's how to push/pull string resources to/from transifex:
```
tx pull -af
```
to update local translations with latest transifex changes
```
tx push -s -f --no-interactive
```
to push new strings to transifex so they can be translated.
# CONTRIBUTIONS # CONTRIBUTIONS
In order to submit a patch for inclusion in linphone's source code: In order to submit a patch for inclusion in linphone's source code:
1. First make sure your patch applies to latest git sources before submitting: patches made to old versions can't be merged. 1. First make sure your patch applies to latest git sources before submitting: patches made to old versions can't and won't be merged.
2. Fill out and send us an email with the link of pullrequest and the [Contributor Agreement](http://www.belledonne-communications.com/downloads/Belledonne_communications_CA.pdf) for your patch to be included in the git tree. The goal of this agreement to grant us peaceful exercise of our rights on the linphone source code, while not losing your rights on your contribution. 2. Fill out and send us an email with the link of pullrequest and the [Contributor Agreement](http://www.belledonne-communications.com/downloads/Belledonne_communications_CA.pdf) for your patch to be included in the git tree.
The goal of this agreement to grant us peaceful exercise of our rights on the linphone source code, while not losing your rights on your contribution.

1
app/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

198
app/build.gradle Normal file
View file

@ -0,0 +1,198 @@
apply plugin: 'com.android.application'
static def getPackageName() {
return "org.linphone"
}
static def firebaseEnabled() {
File googleFile = new File('app/google-services.json')
return googleFile.exists()
}
static def isLocalDebugAarAvailable() {
File debugAar = new File('linphone-sdk-android/linphone-sdk-android-debug.aar')
return debugAar.exists()
}
static def isLocalReleaseAarAvailable() {
File releaseAar = new File('linphone-sdk-android/linphone-sdk-android-release.aar')
return releaseAar.exists()
}
static def isLocalAarAvailable() {
return isLocalDebugAarAvailable() || isLocalReleaseAarAvailable()
}
///// Exclude Files /////
def excludeFiles = []
if (!firebaseEnabled()) {
excludeFiles.add('**/Firebase*')
println '[Push Notification] Firebase disabled'
}
def excludePackage = []
excludePackage.add('**/gdb.*')
excludePackage.add('**/libopenh264**')
excludePackage.add('**/**tester**')
excludePackage.add('**/LICENSE.txt')
def gitVersion = new ByteArrayOutputStream()
task getGitVersion {
exec {
commandLine 'git', 'describe', '--always'
standardOutput = gitVersion
}
doLast {
gitVersion = gitVersion.toString().trim()
println("Git version: " + gitVersion)
}
}
project.tasks['preBuild'].dependsOn 'getGitVersion'
/////////////////////////
repositories {
maven {
// Replace snapshots by releases for releases !
url "https://linphone.org/releases/maven_repository"
}
}
android {
lintOptions {
abortOnError false
}
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 4119
versionName "4.1"
applicationId getPackageName()
multiDexEnabled true
manifestPlaceholders = [linphone_address_mime_type:"vnd.android.cursor.item/vnd." + getPackageName() + ".provider.sip_address"]
}
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "linphone-android-${variant.buildType.name}-${gitVersion.toString().trim()}.apk"
}
}
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
signingConfigs {
release {
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
}
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
resValue "string", "sync_account_type", getPackageName() + ".sync"
resValue "string", "file_provider", getPackageName() + ".provider"
resValue "string", "linphone_address_mime_type", "vnd.android.cursor.item/vnd." + getPackageName() + ".provider.sip_address"
if (!firebaseEnabled()) {
resValue "string", "gcm_defaultSenderId", "none"
}
}
debug {
applicationIdSuffix ".debug"
debuggable true
jniDebuggable true
versionNameSuffix '-debug'
resValue "string", "sync_account_type", getPackageName() + ".sync"
resValue "string", "file_provider", getPackageName() + ".provider"
resValue "string", "linphone_address_mime_type", "vnd.android.cursor.item/vnd." + getPackageName() + ".provider.sip_address"
if (!firebaseEnabled()) {
resValue "string", "gcm_defaultSenderId", "none"
}
}
}
sourceSets {
main {
java.excludes = excludeFiles
packagingOptions {
excludes = excludePackage
}
}
}
packagingOptions {
pickFirst 'META-INF/NOTICE'
pickFirst 'META-INF/LICENSE'
exclude 'META-INF/MANIFEST.MF'
}
}
dependencies {
if (firebaseEnabled()) {
implementation 'com.google.firebase:firebase-messaging:17.5.0'
}
implementation 'com.android.billingclient:billing:1.2'
implementation 'org.apache.commons:commons-compress:1.18'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.google.android.material:material:1.1.0-alpha04'
implementation 'com.google.android:flexbox:1.1.0'
implementation 'com.github.bumptech.glide:glide:4.9.0'
if (isLocalAarAvailable()) {
if (isLocalReleaseAarAvailable()) {
//debug AAR is a lot bigger than release one, and APK install time will be much longer so use release one for day-to-day development
implementation project(path: ":linphone-sdk-android", configuration: 'release')
} else {
releaseImplementation project(path: ":linphone-sdk-android", configuration: 'release')
debugImplementation project(path: ":linphone-sdk-android", configuration: 'debug')
}
} else {
debugImplementation "org.linphone:linphone-sdk-android-debug:4.1+"
releaseImplementation "org.linphone:linphone-sdk-android:4.1+"
}
}
if (firebaseEnabled()) {
apply plugin: 'com.google.gms.google-services'
}
task generateContactsXml(type: Copy) {
from 'contacts.xml'
into "src/main/res/xml/"
filter {
line -> line
.replaceAll('%%AUTO_GENERATED%%', 'This file has been automatically generated, do not edit or commit !')
.replaceAll('%%PACKAGE_NAME%%', getPackageName())
}
}
project.tasks['preBuild'].dependsOn 'generateContactsXml'
apply plugin: "com.diffplug.gradle.spotless"
spotless {
java {
target '**/*.java'
googleJavaFormat('1.6').aosp()
removeUnusedImports()
}
}
project.tasks['preBuild'].dependsOn 'spotlessApply'

11
app/contacts.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ContactsSource xmlns:android="http://schemas.android.com/apk/res/android">
<!-- %%AUTO_GENERATED%% -->
<ContactsDataKind
android:detailColumn="data3"
android:detailSocialSummary="true"
android:icon="@drawable/linphone_logo"
android:mimeType="vnd.android.cursor.item/vnd.%%PACKAGE_NAME%%.provider.sip_address"
android:summaryColumn="data2" />
<!-- You can use @string/linphone_address_mime_type above ! You have to hardcode it... -->
</ContactsSource>

57
app/google-services.json Normal file
View file

@ -0,0 +1,57 @@
{
"project_info": {
"project_number": "929724111839",
"firebase_url": "https://linphone-android-8a563.firebaseio.com",
"project_id": "linphone-android-8a563",
"storage_bucket": "linphone-android-8a563.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:929724111839:android:4662ea9a056188c4",
"android_client_info": {
"package_name": "org.linphone"
}
},
"oauth_client": [
{
"client_id": "929724111839-co5kffto4j7dets7oolvfv0056cvpfbl.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "org.linphone",
"certificate_hash": "85463a95603f7b6331899b74b85d53d043dcd500"
}
},
{
"client_id": "929724111839-v5so1tcd65iil7dd7sde8jgii44h8luf.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCKrwWhkbA7Iy3wpEI8_ZvKOMp5jf6vV6A"
}
]
},
{
"client_info": {
"mobilesdk_app_id": "1:929724111839:android:3cf90ee1d2f8fcb6",
"android_client_info": {
"package_name": "org.linphone.debug"
}
},
"oauth_client": [
{
"client_id": "929724111839-v5so1tcd65iil7dd7sde8jgii44h8luf.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCKrwWhkbA7Iy3wpEI8_ZvKOMp5jf6vV6A"
}
]
}
],
"configuration_version": "1"
}

1
app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1 @@
-dontwarn org.apache.**

View file

@ -1,153 +1,163 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="org.linphone" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" package="org.linphone"
android:installLocation="auto" android:installLocation="auto">
android:versionCode="4002"
android:versionName="4.0.1">
<uses-sdk <uses-permission android:name="android.permission.INTERNET" />
android:minSdkVersion="16" <uses-permission android:name="android.permission.RECORD_AUDIO" />
android:targetSdkVersion="28"/> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<!-- Needed to allow Linphone to install on tablets, since android.permission.CALL_PHONE implies android.hardware.telephony is required --> <!-- Needed to allow Linphone to install on tablets, since android.permission.CALL_PHONE implies android.hardware.telephony is required -->
<uses-feature <uses-feature
android:name="android.hardware.telephony" android:name="android.hardware.telephony"
android:required="false"/> android:required="false" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA" />
<!-- Needed to allow Linphone to install on tablets, since android.permission.CAMERA implies android.hardware.camera and android.hardware.camera.autofocus are required --> <!-- Needed to allow Linphone to install on tablets, since android.permission.CAMERA implies android.hardware.camera and android.hardware.camera.autofocus are required -->
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false"/> android:required="false" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Needed to store received images if the user wants to --> <!-- Needed to store received images if the user wants to -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Needed to use our own Contact editor --> <!-- Needed to use our own Contact editor -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<!-- Needed to route the audio to the bluetooth headset if available --> <!-- Needed to route the audio to the bluetooth headset if available -->
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY"/> <uses-permission android:name="android.permission.BROADCAST_STICKY" />
<!-- Needed to pre fill the wizard email field (only if enabled in custom settings) --> <!-- Needed to pre fill the wizard email field (only if enabled in custom settings) -->
<uses-permission android:name="android.permission.GET_ACCOUNTS"/> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<!-- Needed for in-app purchase --> <!-- Needed for in-app purchase -->
<!-- <uses-permission android:name="com.android.vending.BILLING"/> --> <!-- <uses-permission android:name="com.android.vending.BILLING"/> -->
<!-- Needed for overlay widget and floating notifications --> <!-- Needed for overlay widget and floating notifications -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- Needed for kill application yourself --> <!-- Needed for kill application yourself -->
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/> <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<supports-screens <supports-screens
android:anyDensity="true" android:anyDensity="true"
android:largeScreens="true" android:largeScreens="true"
android:normalScreens="true" android:normalScreens="true"
android:smallScreens="true" android:smallScreens="true"
android:xlargeScreens="true"/> android:xlargeScreens="true" />
<application <application
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" android:label="@string/app_name"
android:largeHeap="true"> android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round">
<activity <activity
android:name=".activities.LinphoneLauncherActivity" android:name=".LinphoneLauncherActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTask"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/NoTitle" android:launchMode="singleTask"
android:noHistory="true"
android:theme="@style/LinphoneStyleLight"
android:windowSoftInputMode="adjustPan|stateHidden"> android:windowSoftInputMode="adjustPan|stateHidden">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".LinphoneActivity"
android:launchMode="singleTask"
android:theme="@style/LinphoneStyleLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.CALL"/> <action android:name="android.intent.action.CALL" />
<action android:name="android.intent.action.CALL_PRIVILEGED"/> <action android:name="android.intent.action.CALL_PRIVILEGED" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel"/> <data android:scheme="tel" />
<data android:scheme="sip"/> <data android:scheme="sip" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SENDTO"/> <action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sip"/> <data android:scheme="sip" />
<data android:scheme="imto"/> <data android:scheme="imto" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<data android:mimeType="@string/sync_mimetype"/> <!-- Change package in res/values/non_localizable_custom.xml ! --> <action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/> <data android:mimeType="${linphone_address_mime_type}" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE"/> <category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="text/*"/>
<data android:mimeType="image/*"/> <data android:mimeType="text/*" />
<data android:mimeType="audio/*"/> <data android:mimeType="image/*" />
<data android:mimeType="video/*"/> <data android:mimeType="audio/*" />
<data android:mimeType="application/*"/> <data android:mimeType="video/*" />
<data android:mimeType="application/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="image/*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="org.linphone.intent.action.CallLaunched" /> <action android:name="org.linphone.intent.action.CallLaunched" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".activities.LinphoneActivity"
android:launchMode="singleTask"
android:theme="@style/NoTitle">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<activity <activity
android:name=".call.CallIncomingActivity" android:name=".call.CallIncomingActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/NoTitle"> android:noHistory="true"
android:showWhenLocked="true"
android:turnScreenOn="true"
android:theme="@style/LinphoneStyleLight">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".call.CallOutgoingActivity" android:name=".call.CallOutgoingActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/NoTitle"> android:noHistory="true"
android:theme="@style/LinphoneStyleLight">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".call.CallActivity" android:name=".call.CallActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/FullScreen"> android:noHistory="true"
android:showWhenLocked="true"
android:theme="@style/LinphoneStyleLight">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
<!-- <!--
<intent-filter> <intent-filter>
@ -161,134 +171,131 @@
<activity <activity
android:name=".assistant.AssistantActivity" android:name=".assistant.AssistantActivity"
android:screenOrientation="behind" android:screenOrientation="behind"
android:theme="@style/NoTitle"> android:theme="@style/LinphoneStyleLight">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".purchase.InAppPurchaseActivity" android:name=".purchase.InAppPurchaseActivity"
android:screenOrientation="nosensor" android:screenOrientation="nosensor"
android:theme="@style/NoTitle"> android:theme="@style/LinphoneStyleLight">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".assistant.RemoteProvisioningLoginActivity" android:name=".assistant.RemoteProvisioningLoginActivity"
android:screenOrientation="nosensor" android:screenOrientation="nosensor"
android:theme="@style/NoTitle"> android:theme="@style/LinphoneStyleLight">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".assistant.RemoteProvisioningActivity" android:name=".assistant.RemoteProvisioningActivity"
android:screenOrientation="nosensor" android:screenOrientation="nosensor"
android:theme="@style/NoTitle"> android:theme="@style/LinphoneStyleLight">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<data android:scheme="linphone-config"/> <!-- Change if needed --> <data android:scheme="linphone-config" /> <!-- Change if needed -->
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name=".LinphoneService" android:name=".LinphoneService"
android:label="@string/service_name"/> android:label="@string/service_name" />
<service <service
android:name=".sync.SyncService" android:name=".sync.SyncService"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.content.SyncAdapter"/> <action android:name="android.content.SyncAdapter" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.content.SyncAdapter" android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter"/> android:resource="@xml/syncadapter" />
<meta-data <meta-data
android:name="android.provider.CONTACTS_STRUCTURE" android:name="android.provider.CONTACTS_STRUCTURE"
android:resource="@xml/contacts"/> android:resource="@xml/contacts" />
</service> </service>
<service android:name=".sync.AuthenticationService"> <service android:name=".sync.AuthenticationService">
<intent-filter> <intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/> <action android:name="android.accounts.AccountAuthenticator" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.accounts.AccountAuthenticator" android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/> android:resource="@xml/authenticator" />
</service> </service>
<receiver <receiver
android:name=".receivers.BluetoothManager" android:name=".receivers.BluetoothManager"
android:enabled="false"> android:enabled="false"/>
</receiver>
<receiver android:name=".receivers.BootReceiver"> <receiver android:name=".receivers.BootReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.ACTION_SHUTDOWN"/> <action android:name="android.intent.action.ACTION_SHUTDOWN" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".receivers.PhoneStateChangedReceiver"> <receiver android:name=".receivers.PhoneStateChangedReceiver">
<intent-filter android:priority="999"> <intent-filter android:priority="999">
<action android:name="android.intent.action.PHONE_STATE"/> <action android:name="android.intent.action.PHONE_STATE" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- This one needs to be registered from application --> <receiver android:name=".receivers.HookReceiver">
<receiver android:name=".receivers.KeepAliveReceiver"/>
<receiver android:name=".receivers.HookReceiver" >
<intent-filter> <intent-filter>
<action android:name="com.base.module.phone.HOOKEVENT" /> <action android:name="com.base.module.phone.HOOKEVENT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".receivers.OutgoingCallReceiver" <!--<receiver
android:permission="android.permission.PROCESS_OUTGOING_CALLS"> android:name=".receivers.OutgoingCallReceiver"
android:permission="android.permission.PROCESS_OUTGOING_CALLS">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" /> <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter> </intent-filter>
</receiver> </receiver>-->
<receiver <receiver
android:name=".receivers.AccountEnableReceiver" android:name=".receivers.AccountEnableReceiver"
android:permission="android.permission.USE_SIP"> android:permission="android.permission.USE_SIP">
<intent-filter> <intent-filter>
<action android:name="org.linphone.intent.ACCOUNTACTIVATE"/> <action android:name="org.linphone.intent.ACCOUNTACTIVATE" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<service android:name=".firebase.FirebaseIdService" android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<service android:name=".firebase.FirebaseMessaging"> <service android:name=".firebase.FirebaseMessaging">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/> <action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter> </intent-filter>
</service> </service>
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="org.linphone.provider" android:authorities="${applicationId}.provider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/> android:resource="@xml/provider_paths" />
</provider> </provider>
<activity android:name=".activities.LinphoneGenericActivity"/> <activity android:name=".utils.LinphoneGenericActivity" />
<receiver
android:name=".notifications.NotificationBroadcastReceiver"
android:enabled="true"
android:exported="false" />
</application> </application>
</manifest> </manifest>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,107 @@
package org.linphone;
/*
LinphoneLauncherActivity.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static android.content.Intent.ACTION_MAIN;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Handler;
import org.linphone.assistant.RemoteProvisioningActivity;
import org.linphone.settings.LinphonePreferences;
/** Launch Linphone main activity when Service is ready. */
public class LinphoneLauncherActivity extends Activity {
private Handler mHandler;
private ServiceWaitThread mServiceThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Hack to avoid to draw twice LinphoneActivity on tablets
if (getResources().getBoolean(R.bool.orientation_portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
if (getResources().getBoolean(R.bool.use_full_screen_image_splashscreen)) {
setContentView(R.layout.launch_screen_full_image);
} else {
setContentView(R.layout.launch_screen);
}
mHandler = new Handler();
if (LinphoneService.isReady()) {
onServiceReady();
} else {
// start linphone as background
startService(new Intent(ACTION_MAIN).setClass(this, LinphoneService.class));
mServiceThread = new ServiceWaitThread();
mServiceThread.start();
}
}
private void onServiceReady() {
final Class<? extends Activity> classToStart;
/*if (getResources().getBoolean(R.bool.show_tutorials_instead_of_app)) {
classToStart = TutorialLauncherActivity.class;
} else */
if (getResources().getBoolean(R.bool.display_sms_remote_provisioning_activity)
&& LinphonePreferences.instance().isFirstRemoteProvisioning()) {
classToStart = RemoteProvisioningActivity.class;
} else {
classToStart = LinphoneActivity.class;
}
mHandler.postDelayed(
new Runnable() {
@Override
public void run() {
startActivity(
getIntent().setClass(LinphoneLauncherActivity.this, classToStart));
}
},
500);
LinphoneManager.getInstance().changeStatusToOnline();
}
private class ServiceWaitThread extends Thread {
public void run() {
while (!LinphoneService.isReady()) {
try {
sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException("waiting thread sleep() has been interrupted");
}
}
mHandler.post(
new Runnable() {
@Override
public void run() {
onServiceReady();
}
});
mServiceThread = null;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,542 @@
package org.linphone;
/*
LinphoneService.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Activity;
import android.app.Application;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.provider.ContactsContract;
import android.view.WindowManager;
import java.util.ArrayList;
import org.linphone.call.CallIncomingActivity;
import org.linphone.contacts.ContactsManager;
import org.linphone.core.Call;
import org.linphone.core.Call.State;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory;
import org.linphone.core.GlobalState;
import org.linphone.core.LogLevel;
import org.linphone.core.LoggingService;
import org.linphone.core.LoggingServiceListener;
import org.linphone.core.ProxyConfig;
import org.linphone.core.RegistrationState;
import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version;
import org.linphone.notifications.NotificationsManager;
import org.linphone.receivers.BluetoothManager;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.LinphoneGL2JNIViewOverlay;
import org.linphone.views.LinphoneOverlay;
import org.linphone.views.LinphoneTextureViewOverlay;
/**
* Linphone service, reacting to Incoming calls, ...<br>
*
* <p>Roles include:
*
* <ul>
* <li>Initializing LinphoneManager
* <li>Starting C libLinphone through LinphoneManager
* <li>Reacting to LinphoneManager state changes
* <li>Delegating GUI state change actions to GUI listener
*/
public final class LinphoneService extends Service {
/* Listener needs to be implemented in the Service as it calls
* setLatestEventInfo and startActivity() which needs a context.
*/
private static final String START_LINPHONE_LOGS = " ==== Phone information dump ====";
private static LinphoneService sInstance;
public final Handler handler = new Handler();
private boolean mTestDelayElapsed = true;
private CoreListenerStub mListener;
private WindowManager mWindowManager;
private LinphoneOverlay mOverlay;
private Application.ActivityLifecycleCallbacks mActivityCallbacks;
private NotificationsManager mNotificationManager;
private String mIncomingReceivedActivityName;
private Class<? extends Activity> mIncomingReceivedActivity = CallIncomingActivity.class;
private LoggingServiceListener mJavaLoggingService =
new LoggingServiceListener() {
@Override
public void onLogMessageWritten(
LoggingService logService, String domain, LogLevel lev, String message) {
switch (lev) {
case Debug:
android.util.Log.d(domain, message);
break;
case Message:
android.util.Log.i(domain, message);
break;
case Warning:
android.util.Log.w(domain, message);
break;
case Error:
android.util.Log.e(domain, message);
break;
case Fatal:
default:
android.util.Log.wtf(domain, message);
break;
}
}
};
public LoggingServiceListener getJavaLoggingService() {
return mJavaLoggingService;
}
public static boolean isReady() {
return sInstance != null && sInstance.mTestDelayElapsed;
}
public static LinphoneService instance() {
if (isReady()) return sInstance;
throw new RuntimeException("LinphoneService not instantiated yet");
}
public NotificationsManager getNotificationManager() {
return mNotificationManager;
}
public void removeForegroundServiceNotificationIfPossible() {
mNotificationManager.removeForegroundServiceNotificationIfPossible();
}
public Class<? extends Activity> getIncomingReceivedActivity() {
return mIncomingReceivedActivity;
}
public void setCurrentlyDisplayedChatRoom(String address) {
if (address != null) {
mNotificationManager.resetMessageNotifCount(address);
}
}
private void onBackgroundMode() {
Log.i("[Service] App has entered background mode");
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) {
LinphoneManager.getLcIfManagerNotDestroyedOrNull().enterBackground();
}
}
private void onForegroundMode() {
Log.i("[Service] App has left background mode");
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) {
LinphoneManager.getLcIfManagerNotDestroyedOrNull().enterForeground();
}
}
private void setupActivityMonitor() {
if (mActivityCallbacks != null) return;
getApplication()
.registerActivityLifecycleCallbacks(mActivityCallbacks = new ActivityMonitor());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
boolean isPush = false;
if (intent != null && intent.getBooleanExtra("PushNotification", false)) {
Log.i("[Service] [Push Notification] LinphoneService started because of a push");
isPush = true;
}
if (sInstance != null) {
Log.w("[Service] Attempt to start the LinphoneService but it is already running !");
return START_STICKY;
}
LinphoneManager.createAndStart(this, isPush);
sInstance = this; // sInstance is ready once linphone manager has been created
mNotificationManager = new NotificationsManager(this);
LinphoneManager.getLc()
.addListener(
mListener =
new CoreListenerStub() {
@Override
public void onCallStateChanged(
Core lc, Call call, Call.State state, String message) {
if (sInstance == null) {
Log.i(
"[Service] Service not ready, discarding call state change to ",
state.toString());
return;
}
if (getResources()
.getBoolean(R.bool.enable_call_notification)) {
mNotificationManager.displayCallNotification(call);
}
if (state == Call.State.IncomingReceived
|| state == State.IncomingEarlyMedia) {
if (!LinphoneManager.getInstance().getCallGsmON())
onIncomingReceived();
}
if (state == State.End
|| state == State.Released
|| state == State.Error) {
destroyOverlay();
}
if (state == State.Released
&& call.getCallLog().getStatus()
== Call.Status.Missed) {
mNotificationManager.displayMissedCallNotification(
call);
}
}
@Override
public void onGlobalStateChanged(
Core lc, GlobalState state, String message) {
// TODO global state if ON
}
@Override
public void onRegistrationStateChanged(
Core lc,
ProxyConfig cfg,
RegistrationState state,
String smessage) {
// TODO registration status
}
});
if (Version.sdkAboveOrEqual(Version.API26_O_80)
&& intent.getBooleanExtra("ForceStartForeground", false)) {
mNotificationManager.startForeground();
}
if (!Version.sdkAboveOrEqual(Version.API26_O_80)
|| (ContactsManager.getInstance() != null
&& ContactsManager.getInstance().hasReadContactsAccess())) {
getContentResolver()
.registerContentObserver(
ContactsContract.Contacts.CONTENT_URI,
true,
ContactsManager.getInstance());
}
if (!mTestDelayElapsed) {
// Only used when testing. Simulates a 5 seconds delay for launching service
handler.postDelayed(
new Runnable() {
@Override
public void run() {
mTestDelayElapsed = true;
}
},
5000);
}
BluetoothManager.getInstance().initBluetooth();
return START_STICKY;
}
@SuppressWarnings("unchecked")
@Override
public void onCreate() {
super.onCreate();
setupActivityMonitor();
// Needed in order for the two next calls to succeed, libraries must have been loaded first
LinphonePreferences.instance().setContext(getBaseContext());
Factory.instance().setLogCollectionPath(getFilesDir().getAbsolutePath());
boolean isDebugEnabled = LinphonePreferences.instance().isDebugEnabled();
LinphoneUtils.configureLoggingService(isDebugEnabled, getString(R.string.app_name));
// LinphoneService isn't ready yet so we have to manually set up the Java logging service
if (LinphonePreferences.instance().useJavaLogger()) {
Factory.instance().getLoggingService().addListener(mJavaLoggingService);
}
// Dump some debugging information to the logs
Log.i(START_LINPHONE_LOGS);
dumpDeviceInformation();
dumpInstalledLinphoneInformation();
mIncomingReceivedActivityName =
LinphonePreferences.instance().getActivityToLaunchOnIncomingReceived();
try {
mIncomingReceivedActivity =
(Class<? extends Activity>) Class.forName(mIncomingReceivedActivityName);
} catch (ClassNotFoundException e) {
Log.e(e);
}
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
}
public void createOverlay() {
if (mOverlay != null) destroyOverlay();
Core core = LinphoneManager.getLc();
Call call = core.getCurrentCall();
if (call == null || !call.getCurrentParams().videoEnabled()) return;
if ("MSAndroidOpenGLDisplay".equals(core.getVideoDisplayFilter())) {
mOverlay = new LinphoneGL2JNIViewOverlay(this);
} else {
mOverlay = new LinphoneTextureViewOverlay(this);
}
WindowManager.LayoutParams params = mOverlay.getWindowManagerLayoutParams();
params.x = 0;
params.y = 0;
mOverlay.addToWindowManager(mWindowManager, params);
}
public void destroyOverlay() {
if (mOverlay != null) {
mOverlay.removeFromWindowManager(mWindowManager);
mOverlay.destroy();
}
mOverlay = null;
}
private void dumpDeviceInformation() {
StringBuilder sb = new StringBuilder();
sb.append("DEVICE=").append(Build.DEVICE).append("\n");
sb.append("MODEL=").append(Build.MODEL).append("\n");
sb.append("MANUFACTURER=").append(Build.MANUFACTURER).append("\n");
sb.append("SDK=").append(Build.VERSION.SDK_INT).append("\n");
sb.append("Supported ABIs=");
for (String abi : Version.getCpuAbis()) {
sb.append(abi).append(", ");
}
sb.append("\n");
Log.i(sb.toString());
}
private void dumpInstalledLinphoneInformation() {
PackageInfo info = null;
try {
info = getPackageManager().getPackageInfo(getPackageName(), 0);
} catch (NameNotFoundException nnfe) {
Log.e(nnfe);
}
if (info != null) {
Log.i(
"[Service] Linphone version is ",
info.versionName + " (" + info.versionCode + ")");
} else {
Log.i("[Service] Linphone version is unknown");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onTaskRemoved(Intent rootIntent) {
boolean serviceNotif = LinphonePreferences.instance().getServiceNotificationVisibility();
if (serviceNotif) {
Log.i("[Service] Service is running in foreground, don't stop it");
} else if (getResources().getBoolean(R.bool.kill_service_with_task_manager)) {
Log.i("[Service] Task removed, stop service");
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.terminateAllCalls();
}
// If push is enabled, don't unregister account, otherwise do unregister
if (LinphonePreferences.instance().isPushNotificationEnabled()) {
if (lc != null) lc.setNetworkReachable(false);
}
stopSelf();
}
super.onTaskRemoved(rootIntent);
}
@Override
public synchronized void onDestroy() {
if (mActivityCallbacks != null) {
getApplication().unregisterActivityLifecycleCallbacks(mActivityCallbacks);
mActivityCallbacks = null;
}
destroyOverlay();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
lc = null; // To allow the gc calls below to free the Core
}
sInstance = null;
LinphoneManager.destroy();
// Make sure our notification is gone.
if (mNotificationManager != null) {
mNotificationManager.destroy();
}
// This will prevent the app from crashing if the service gets killed in background mode
if (LinphoneActivity.isInstanciated()) {
Log.w("[Service] Service is getting destroyed, finish LinphoneActivity");
LinphoneActivity.instance().finish();
}
if (LinphonePreferences.instance().useJavaLogger()) {
Factory.instance().getLoggingService().removeListener(mJavaLoggingService);
}
super.onDestroy();
}
@SuppressWarnings("unchecked")
public void setActivityToLaunchOnIncomingReceived(String activityName) {
try {
mIncomingReceivedActivity = (Class<? extends Activity>) Class.forName(activityName);
mIncomingReceivedActivityName = activityName;
LinphonePreferences.instance()
.setActivityToLaunchOnIncomingReceived(mIncomingReceivedActivityName);
} catch (ClassNotFoundException e) {
Log.e(e);
}
}
private void onIncomingReceived() {
Intent intent = new Intent().setClass(this, mIncomingReceivedActivity);
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().startActivity(intent);
} else {
// This flag is required to start an Activity from a Service context
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
/*Believe me or not, but knowing the application visibility state on Android is a nightmare.
After two days of hard work I ended with the following class, that does the job more or less reliabily.
*/
class ActivityMonitor implements Application.ActivityLifecycleCallbacks {
private final ArrayList<Activity> activities = new ArrayList<>();
private boolean mActive = false;
private int mRunningActivities = 0;
private InactivityChecker mLastChecker;
@Override
public synchronized void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Log.i("[Service] Activity created:" + activity);
if (!activities.contains(activity)) activities.add(activity);
}
@Override
public void onActivityStarted(Activity activity) {
Log.i("Activity started:" + activity);
}
@Override
public synchronized void onActivityResumed(Activity activity) {
Log.i("[Service] Activity resumed:" + activity);
if (activities.contains(activity)) {
mRunningActivities++;
Log.i("[Service] runningActivities=" + mRunningActivities);
checkActivity();
}
}
@Override
public synchronized void onActivityPaused(Activity activity) {
Log.i("[Service] Activity paused:" + activity);
if (activities.contains(activity)) {
mRunningActivities--;
Log.i("[Service] runningActivities=" + mRunningActivities);
checkActivity();
}
}
@Override
public void onActivityStopped(Activity activity) {
Log.i("[Service] Activity stopped:" + activity);
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override
public synchronized void onActivityDestroyed(Activity activity) {
Log.i("[Service] Activity destroyed:" + activity);
activities.remove(activity);
}
void startInactivityChecker() {
if (mLastChecker != null) mLastChecker.cancel();
LinphoneService.this.handler.postDelayed(
(mLastChecker = new InactivityChecker()), 2000);
}
void checkActivity() {
if (mRunningActivities == 0) {
if (mActive) startInactivityChecker();
} else if (mRunningActivities > 0) {
if (!mActive) {
mActive = true;
LinphoneService.this.onForegroundMode();
}
if (mLastChecker != null) {
mLastChecker.cancel();
mLastChecker = null;
}
}
}
class InactivityChecker implements Runnable {
private boolean isCanceled;
void cancel() {
isCanceled = true;
}
@Override
public void run() {
synchronized (LinphoneService.this) {
if (!isCanceled) {
if (ActivityMonitor.this.mRunningActivities == 0 && mActive) {
mActive = false;
LinphoneService.this.onBackgroundMode();
}
}
}
}
}
}
}

View file

@ -0,0 +1,958 @@
package org.linphone.assistant;
/*
AssistantActivity.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.Manifest;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneLauncherActivity;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListener;
import org.linphone.core.Address;
import org.linphone.core.AuthInfo;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.DialPlan;
import org.linphone.core.Factory;
import org.linphone.core.ProxyConfig;
import org.linphone.core.RegistrationState;
import org.linphone.core.TransportType;
import org.linphone.core.tools.Log;
import org.linphone.core.tools.OpenH264DownloadHelper;
import org.linphone.fragments.StatusFragment;
import org.linphone.mediastream.Version;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.ThemableActivity;
public class AssistantActivity extends ThemableActivity
implements OnClickListener,
ActivityCompat.OnRequestPermissionsResultCallback,
AccountCreatorListener {
private static final int PERMISSIONS_REQUEST_RECORD_AUDIO = 201;
private static final int PERMISSIONS_REQUEST_CAMERA = 202;
private static AssistantActivity sInstance;
public DialPlan country;
private ImageView mBack /*, mCancel*/;
private AssistantFragmentsEnum mCurrentFragment;
private AssistantFragmentsEnum mLastFragment;
private AssistantFragmentsEnum mFirstFragment;
private Fragment mFragment;
private LinphonePreferences mPrefs;
private boolean mAccountCreated = false,
mNewAccount = false,
mIsLink = false,
mFromPref = false;
private CoreListenerStub mListener;
private Address mAddress;
private StatusFragment mStatus;
private ProgressDialog mProgress;
private Dialog mDialog;
private boolean mRemoteProvisioningInProgress;
private boolean mEchoCancellerAlreadyDone;
private AccountCreator mAccountCreator;
private CountryListAdapter mCountryListAdapter;
private LinearLayout mTopBar;
public static AssistantActivity instance() {
return sInstance;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getBoolean(R.bool.orientation_portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
setContentView(R.layout.assistant);
initUI();
if (getIntent().getBooleanExtra("LinkPhoneNumber", false)) {
mIsLink = true;
if (getIntent().getBooleanExtra("FromPref", false)) mFromPref = true;
displayCreateAccount();
} else {
mFirstFragment =
getResources().getBoolean(R.bool.assistant_use_linphone_login_as_first_fragment)
? AssistantFragmentsEnum.LINPHONE_LOGIN
: AssistantFragmentsEnum.WELCOME;
if (mFirstFragment == AssistantFragmentsEnum.WELCOME) {
mFirstFragment =
getResources()
.getBoolean(
R.bool.assistant_use_create_linphone_account_as_first_fragment)
? AssistantFragmentsEnum.CREATE_ACCOUNT
: AssistantFragmentsEnum.WELCOME;
}
if (findViewById(R.id.fragment_container) != null) {
if (savedInstanceState == null) {
display(mFirstFragment);
} else {
mCurrentFragment =
(AssistantFragmentsEnum)
savedInstanceState.getSerializable("CurrentFragment");
}
}
}
if (savedInstanceState != null && savedInstanceState.containsKey("echoCanceller")) {
mEchoCancellerAlreadyDone = savedInstanceState.getBoolean("echoCanceller");
} else {
mEchoCancellerAlreadyDone = false;
}
mPrefs = LinphonePreferences.instance();
mStatus.enableSideMenu(false);
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) {
mAccountCreator =
LinphoneManager.getLc()
.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(this);
}
mCountryListAdapter = new CountryListAdapter(getApplicationContext());
mListener =
new CoreListenerStub() {
@Override
public void onConfiguringStatus(
Core lc, final ConfiguringState state, String message) {
if (mProgress != null) mProgress.dismiss();
if (state == ConfiguringState.Successful) {
goToLinphoneActivity();
} else if (state == ConfiguringState.Failed) {
Toast.makeText(
AssistantActivity.instance(),
getString(R.string.remote_provisioning_failure),
Toast.LENGTH_LONG)
.show();
}
}
@Override
public void onRegistrationStateChanged(
Core lc, ProxyConfig cfg, RegistrationState state, String smessage) {
if (mRemoteProvisioningInProgress) {
if (mProgress != null) mProgress.dismiss();
if (state == RegistrationState.Ok) {
mRemoteProvisioningInProgress = false;
success();
}
} else if (mAccountCreated && !mNewAccount) {
if (mAddress != null
&& mAddress.asString()
.equals(cfg.getIdentityAddress().asString())) {
if (state == RegistrationState.Ok) {
if (mProgress != null) mProgress.dismiss();
if (getResources()
.getBoolean(R.bool.use_phone_number_validation)
&& cfg.getDomain()
.equals(getString(R.string.default_domain))
&& LinphoneManager.getLc().getDefaultProxyConfig()
!= null) {
loadAccountCreator(cfg).isAccountExist();
} else {
success();
}
} else if (state == RegistrationState.Failed) {
if (mProgress != null) mProgress.dismiss();
if (mDialog == null || !mDialog.isShowing()) {
mDialog = createErrorDialog(cfg, smessage);
mDialog.setCancelable(false);
mDialog.show();
}
} else if (!(state == RegistrationState.Progress)) {
if (mProgress != null) mProgress.dismiss();
}
}
}
}
};
sInstance = this;
}
@Override
protected void onResume() {
super.onResume();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
}
}
@Override
protected void onPause() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
super.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putSerializable("CurrentFragment", mCurrentFragment);
outState.putBoolean("echoCanceller", mEchoCancellerAlreadyDone);
super.onSaveInstanceState(outState);
}
public void updateStatusFragment(StatusFragment fragment) {
mStatus = fragment;
}
private AccountCreator loadAccountCreator(ProxyConfig cfg) {
ProxyConfig cfgTab[] = LinphoneManager.getLc().getProxyConfigList();
int n = -1;
for (int i = 0; i < cfgTab.length; i++) {
if (cfgTab[i].equals(cfg)) {
n = i;
break;
}
}
if (n >= 0) {
mAccountCreator.setDomain(mPrefs.getAccountDomain(n));
mAccountCreator.setUsername(mPrefs.getAccountUsername(n));
}
return mAccountCreator;
}
private void initUI() {
mBack = findViewById(R.id.back);
mBack.setOnClickListener(this);
// mCancel = findViewById(R.id.assistant_cancel);
// mCancel.setOnClickListener(this);
mTopBar = findViewById(R.id.topbar);
if (getResources().getBoolean(R.bool.assistant_hide_top_bar)) {
mTopBar.setVisibility(View.GONE);
}
}
private void changeFragment(Fragment newFragment) {
hideKeyboard();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.commitAllowingStateLoss();
}
@Override
public void onClick(View v) {
int id = v.getId();
boolean firstLaunch = LinphonePreferences.instance().isFirstLaunch();
/*if (id == R.id.assistant_cancel) {
hideKeyboard();
LinphonePreferences.instance().firstLaunchSuccessful();
if (getResources().getBoolean(R.bool.assistant_cancel_move_to_back)) {
moveTaskToBack(true);
} else {
if (firstLaunch) startActivity(new Intent().setClass(this, LinphoneActivity.class));
finish();
}
} else*/
if (id == R.id.back) {
hideKeyboard();
if (mCurrentFragment == AssistantFragmentsEnum.WELCOME) {
LinphonePreferences.instance().firstLaunchSuccessful();
if (getResources().getBoolean(R.bool.assistant_cancel_move_to_back)) {
moveTaskToBack(true);
} else {
if (firstLaunch)
startActivity(new Intent().setClass(this, LinphoneActivity.class));
finish();
}
} else {
onBackPressed();
}
}
}
@Override
public void onBackPressed() {
if (mIsLink) {
return;
}
boolean firstLaunch = LinphonePreferences.instance().isFirstLaunch();
if (mCurrentFragment == mFirstFragment) {
LinphonePreferences.instance().firstLaunchSuccessful();
if (getResources().getBoolean(R.bool.assistant_cancel_move_to_back)) {
moveTaskToBack(true);
} else {
LinphonePreferences.instance().firstLaunchSuccessful();
if (firstLaunch) startActivity(new Intent().setClass(this, LinphoneActivity.class));
finish();
}
} else if (mCurrentFragment == AssistantFragmentsEnum.LOGIN
|| mCurrentFragment == AssistantFragmentsEnum.LINPHONE_LOGIN
|| mCurrentFragment == AssistantFragmentsEnum.CREATE_ACCOUNT
|| mCurrentFragment == AssistantFragmentsEnum.REMOTE_PROVISIONING) {
displayMenu();
} else if (mCurrentFragment == AssistantFragmentsEnum.WELCOME) {
if (firstLaunch) startActivity(new Intent().setClass(this, LinphoneActivity.class));
finish();
} else if (mCurrentFragment == AssistantFragmentsEnum.COUNTRY_CHOOSER) {
if (mLastFragment.equals(AssistantFragmentsEnum.LINPHONE_LOGIN)) {
displayLoginLinphone(null, null);
} else {
displayCreateAccount();
}
} else if (mCurrentFragment == AssistantFragmentsEnum.QRCODE_READER) {
displayRemoteProvisioning("");
}
}
public void hideKeyboard() {
InputMethodManager imm =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
View view = this.getCurrentFocus();
if (imm != null && view != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
private void checkAndRequestAudioPermission() {
checkAndRequestPermission(
Manifest.permission.RECORD_AUDIO, PERMISSIONS_REQUEST_RECORD_AUDIO);
}
private void checkAndRequestVideoPermission() {
checkAndRequestPermission(Manifest.permission.CAMERA, PERMISSIONS_REQUEST_CAMERA);
}
private void checkAndRequestPermission(String permission, int result) {
int permissionGranted = getPackageManager().checkPermission(permission, getPackageName());
Log.i(
"[Permission] "
+ permission
+ " is "
+ (permissionGranted == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
if (permissionGranted != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for " + permission);
ActivityCompat.requestPermissions(this, new String[] {permission}, result);
}
}
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, final int[] grantResults) {
for (int i = 0; i < permissions.length; i++) {
Log.i(
"[Permission] "
+ permissions[i]
+ " is "
+ (grantResults[i] == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
}
switch (requestCode) {
case PERMISSIONS_REQUEST_CAMERA:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
displayQRCodeReader();
}
break;
case PERMISSIONS_REQUEST_RECORD_AUDIO:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
launchEchoCancellerCalibration();
} else {
isEchoCalibrationFinished();
}
break;
}
}
private void launchEchoCancellerCalibration() {
int recordAudio =
getPackageManager()
.checkPermission(Manifest.permission.RECORD_AUDIO, getPackageName());
Log.i(
"[Permission] Record audio permission is "
+ (recordAudio == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
if (recordAudio == PackageManager.PERMISSION_GRANTED) {
EchoCancellerCalibrationFragment fragment = new EchoCancellerCalibrationFragment();
fragment.enableEcCalibrationResultSending(true);
changeFragment(fragment);
mCurrentFragment = AssistantFragmentsEnum.ECHO_CANCELLER_CALIBRATION;
mBack.setVisibility(View.VISIBLE);
mBack.setEnabled(false);
} else {
checkAndRequestAudioPermission();
}
}
private void configureProxyConfig(AccountCreator accountCreator) {
Core lc = LinphoneManager.getLc();
ProxyConfig proxyConfig = lc.createProxyConfig();
AuthInfo authInfo;
String identity = proxyConfig.getIdentityAddress().asStringUriOnly();
if (identity == null || accountCreator.getUsername() == null) {
LinphoneUtils.displayErrorAlert(getString(R.string.error), this);
return;
}
identity = identity.replace("?", accountCreator.getUsername());
Address addr = Factory.instance().createAddress(identity);
addr.setDisplayName(accountCreator.getUsername());
mAddress = addr;
proxyConfig.edit();
proxyConfig.setIdentityAddress(addr);
if (accountCreator.getPhoneNumber() != null && accountCreator.getPhoneNumber().length() > 0)
proxyConfig.setDialPrefix(
org.linphone.core.Utils.getPrefixFromE164(accountCreator.getPhoneNumber()));
proxyConfig.done();
authInfo =
Factory.instance()
.createAuthInfo(
accountCreator.getUsername(),
null,
accountCreator.getPassword(),
accountCreator.getHa1(),
proxyConfig.getRealm(),
proxyConfig.getDomain());
lc.addProxyConfig(proxyConfig);
lc.addAuthInfo(authInfo);
lc.setDefaultProxyConfig(proxyConfig);
if (LinphonePreferences.instance() != null)
LinphonePreferences.instance().setPushNotificationEnabled(true);
if (!mNewAccount) {
displayRegistrationInProgressDialog();
}
mAccountCreated = true;
}
public void linphoneLogIn(AccountCreator accountCreator) {
LinphoneManager.getLc()
.loadConfigFromXml(LinphoneManager.getInstance().getLinphoneDynamicConfigFile());
configureProxyConfig(accountCreator);
// Restore default values for proxy config
LinphoneManager.getLc()
.loadConfigFromXml(LinphoneManager.getInstance().getDefaultDynamicConfigFile());
}
public void genericLogIn(
String username,
String userid,
String password,
String displayname,
String prefix,
String domain,
TransportType transport) {
Core core = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (core == null) return;
AuthInfo authInfo =
Factory.instance().createAuthInfo(username, userid, password, null, null, domain);
core.addAuthInfo(authInfo);
ProxyConfig proxyConfig = core.createProxyConfig();
String identity = "sip:" + username + "@" + domain;
Address identityAddr = Factory.instance().createAddress(identity);
if (identityAddr != null) {
identityAddr.setDisplayName(displayname);
proxyConfig.setIdentityAddress(identityAddr);
}
String proxy = "<sip:" + domain + ";transport=" + transport.name().toLowerCase() + ">";
proxyConfig.setServerAddr(proxy);
proxyConfig.setDialPrefix(prefix);
core.addProxyConfig(proxyConfig);
core.setDefaultProxyConfig(proxyConfig);
mAccountCreated = true;
success();
}
private void display(AssistantFragmentsEnum fragment) {
switch (fragment) {
case WELCOME:
displayMenu();
break;
case LINPHONE_LOGIN:
displayLoginLinphone(null, null);
break;
case CREATE_ACCOUNT:
displayCreateAccount();
break;
default:
throw new IllegalStateException("Can't handle " + fragment);
}
}
private void displayMenu() {
mFragment = new WelcomeFragment();
changeFragment(mFragment);
country = null;
mCurrentFragment = AssistantFragmentsEnum.WELCOME;
}
public void displayLoginGeneric() {
mFragment = new LoginFragment();
changeFragment(mFragment);
mCurrentFragment = AssistantFragmentsEnum.LOGIN;
}
public void displayLoginLinphone(String username, String password) {
mFragment = new LinphoneLoginFragment();
Bundle extras = new Bundle();
extras.putString("Phone", null);
extras.putString("Dialcode", null);
extras.putString("Username", username);
extras.putString("Password", password);
mFragment.setArguments(extras);
changeFragment(mFragment);
mCurrentFragment = AssistantFragmentsEnum.LINPHONE_LOGIN;
}
public void displayCreateAccount() {
mFragment = new CreateAccountFragment();
Bundle extra = new Bundle();
extra.putBoolean("LinkPhoneNumber", mIsLink);
extra.putBoolean("LinkFromPref", mFromPref);
mFragment.setArguments(extra);
changeFragment(mFragment);
mCurrentFragment = AssistantFragmentsEnum.CREATE_ACCOUNT;
}
public void displayRemoteProvisioning(String url) {
mFragment = new RemoteProvisioningFragment();
Bundle extra = new Bundle();
extra.putString("RemoteUrl", url);
mFragment.setArguments(extra);
changeFragment(mFragment);
mCurrentFragment = AssistantFragmentsEnum.REMOTE_PROVISIONING;
}
public void displayQRCodeReader() {
if (getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName())
!= PackageManager.PERMISSION_GRANTED) {
checkAndRequestVideoPermission();
} else {
mFragment = new QrCodeFragment();
changeFragment(mFragment);
mCurrentFragment = AssistantFragmentsEnum.QRCODE_READER;
}
}
public void displayCountryChooser() {
mFragment = new CountryListFragment();
changeFragment(mFragment);
mLastFragment = mCurrentFragment;
mCurrentFragment = AssistantFragmentsEnum.COUNTRY_CHOOSER;
}
private void launchDownloadCodec() {
if (OpenH264DownloadHelper.isOpenH264DownloadEnabled()) {
OpenH264DownloadHelper downloadHelper =
Factory.instance().createOpenH264DownloadHelper(this);
if (Version.getCpuAbis().contains("armeabi-v7a")
&& !Version.getCpuAbis().contains("x86")
&& !downloadHelper.isCodecFound()) {
CodecDownloaderFragment codecFragment = new CodecDownloaderFragment();
changeFragment(codecFragment);
mCurrentFragment = AssistantFragmentsEnum.DOWNLOAD_CODEC;
mBack.setEnabled(false);
} else goToLinphoneActivity();
} else {
goToLinphoneActivity();
}
}
public void endDownloadCodec() {
goToLinphoneActivity();
}
private void displayRegistrationInProgressDialog() {
if (LinphoneManager.getLc().isNetworkReachable()) {
mProgress = ProgressDialog.show(this, null, null);
Drawable d = new ColorDrawable(ContextCompat.getColor(this, R.color.light_grey_color));
d.setAlpha(200);
mProgress
.getWindow()
.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
mProgress.getWindow().setBackgroundDrawable(d);
mProgress.setContentView(R.layout.wait_layout);
mProgress.show();
}
}
public void displayRemoteProvisioningInProgressDialog() {
mRemoteProvisioningInProgress = true;
mProgress = ProgressDialog.show(this, null, null);
Drawable d = new ColorDrawable(ContextCompat.getColor(this, R.color.light_grey_color));
d.setAlpha(200);
mProgress
.getWindow()
.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
mProgress.getWindow().setBackgroundDrawable(d);
mProgress.setContentView(R.layout.wait_layout);
mProgress.show();
}
public void displayAssistantConfirm(String username, String password, String email) {
CreateAccountActivationFragment fragment = new CreateAccountActivationFragment();
mNewAccount = true;
Bundle extras = new Bundle();
extras.putString("Username", username);
extras.putString("Password", password);
extras.putString("Email", email);
fragment.setArguments(extras);
changeFragment(fragment);
mCurrentFragment = AssistantFragmentsEnum.CREATE_ACCOUNT_ACTIVATION;
}
public void displayAssistantCodeConfirm(
String username, String phone, String dialcode, boolean recoverAccount) {
CreateAccountCodeActivationFragment fragment = new CreateAccountCodeActivationFragment();
mNewAccount = true;
Bundle extras = new Bundle();
extras.putString("Username", username);
extras.putString("Phone", phone);
extras.putString("Dialcode", dialcode);
extras.putBoolean("RecoverAccount", recoverAccount);
extras.putBoolean("LinkAccount", mIsLink);
fragment.setArguments(extras);
changeFragment(fragment);
mCurrentFragment = AssistantFragmentsEnum.CREATE_ACCOUNT_CODE_ACTIVATION;
}
public void displayAssistantLinphoneLogin(String phone, String dialcode) {
LinphoneLoginFragment fragment = new LinphoneLoginFragment();
mNewAccount = true;
Bundle extras = new Bundle();
extras.putString("Phone", phone);
extras.putString("Dialcode", dialcode);
fragment.setArguments(extras);
changeFragment(fragment);
mCurrentFragment = AssistantFragmentsEnum.LINPHONE_LOGIN;
}
public void isAccountVerified() {
Toast.makeText(this, getString(R.string.assistant_account_validated), Toast.LENGTH_LONG)
.show();
hideKeyboard();
success();
}
public void isEchoCalibrationFinished() {
launchDownloadCodec();
}
private Dialog createErrorDialog(ProxyConfig proxy, String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if (message.equals("Forbidden")) {
message = getString(R.string.assistant_error_bad_credentials);
}
builder.setMessage(message)
.setTitle(proxy.getState().toString())
.setPositiveButton(
getString(R.string.continue_text),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
success();
}
})
.setNegativeButton(
getString(R.string.cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
LinphoneManager.getLc()
.removeProxyConfig(
LinphoneManager.getLc().getDefaultProxyConfig());
LinphonePreferences.instance().resetDefaultProxyConfig();
LinphoneManager.getLc().refreshRegisters();
dialog.dismiss();
}
});
return builder.show();
}
public void success() {
boolean needsEchoCalibration = LinphoneManager.getLc().isEchoCancellerCalibrationRequired();
if (needsEchoCalibration && mPrefs.isFirstLaunch()) {
launchEchoCancellerCalibration();
} else {
launchDownloadCodec();
}
}
private void goToLinphoneActivity() {
mPrefs.firstLaunchSuccessful();
startActivity(
new Intent()
.setClass(this, LinphoneActivity.class)
.putExtra("isNewProxyConfig", true));
finish();
}
public void setCoreListener() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
}
if (mStatus != null) {
mStatus.setCoreListener();
}
}
public void restartApplication() {
mPrefs.firstLaunchSuccessful();
Intent mStartActivity = new Intent(this, LinphoneLauncherActivity.class);
PendingIntent mPendingIntent =
PendingIntent.getActivity(
this,
(int) System.currentTimeMillis(),
mStartActivity,
PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 500, mPendingIntent);
finish();
stopService(new Intent(Intent.ACTION_MAIN).setClass(this, LinphoneService.class));
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
am.killBackgroundProcesses(getString(R.string.sync_account_type));
android.os.Process.killProcess(android.os.Process.myPid());
}
@Override
public void onIsAccountExist(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {
if (status.equals(AccountCreator.Status.AccountExistWithAlias)) {
success();
} else {
mIsLink = true;
displayCreateAccount();
}
if (mAccountCreator != null) mAccountCreator.setListener(null);
}
@Override
public void onCreateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onLinkAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAlias(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAccountActivated(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onRecoverAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAccountLinked(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAliasUsed(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onUpdateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
public CountryListAdapter getCountryListAdapter() {
return mCountryListAdapter;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mCurrentFragment == AssistantFragmentsEnum.QRCODE_READER) {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
/**
* This class reads a JSON file containing Country-specific phone number description, and allows
* to present them into a ListView
*/
public class CountryListAdapter extends BaseAdapter implements Filterable {
private LayoutInflater mInflater;
private final DialPlan[] allCountries;
private List<DialPlan> filteredCountries;
private final Context context;
CountryListAdapter(Context ctx) {
context = ctx;
allCountries = Factory.instance().getDialPlans();
filteredCountries = new ArrayList<>(Arrays.asList(allCountries));
}
public void setInflater(LayoutInflater inf) {
mInflater = inf;
}
public DialPlan getCountryFromCountryCode(String countryCode) {
countryCode = (countryCode.startsWith("+")) ? countryCode.substring(1) : countryCode;
for (DialPlan c : allCountries) {
if (c.getCountryCallingCode().compareTo(countryCode) == 0) return c;
}
return null;
}
@Override
public int getCount() {
return filteredCountries.size();
}
@Override
public DialPlan getItem(int position) {
return filteredCountries.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else {
view = mInflater.inflate(R.layout.country_cell, parent, false);
}
DialPlan c = filteredCountries.get(position);
TextView name = view.findViewById(R.id.country_name);
name.setText(c.getCountry());
TextView dial_code = view.findViewById(R.id.country_prefix);
if (context != null)
dial_code.setText(
String.format(
context.getString(R.string.country_code),
c.getCountryCallingCode()));
view.setTag(c);
return view;
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
ArrayList<DialPlan> filteredCountries = new ArrayList<>();
for (DialPlan c : allCountries) {
if (c.getCountry().toLowerCase().contains(constraint)
|| c.getCountryCallingCode().contains(constraint)) {
filteredCountries.add(c);
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = filteredCountries;
return filterResults;
}
@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
filteredCountries = (List<DialPlan>) results.values;
CountryListAdapter.this.notifyDataSetChanged();
}
};
}
}
}

View file

@ -28,5 +28,6 @@ public enum AssistantFragmentsEnum {
LOGIN, LOGIN,
REMOTE_PROVISIONING, REMOTE_PROVISIONING,
ECHO_CANCELLER_CALIBRATION, ECHO_CANCELLER_CALIBRATION,
DOWNLOAD_CODEC; DOWNLOAD_CODEC,
QRCODE_READER
} }

View file

@ -0,0 +1,220 @@
package org.linphone.assistant;
/*
CodecDownloaderFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.PayloadType;
import org.linphone.core.tools.OpenH264DownloadHelper;
import org.linphone.core.tools.OpenH264DownloadHelperListener;
public class CodecDownloaderFragment extends Fragment {
private final Handler mHandler = new Handler();
private TextView mQuestion;
private TextView mDownloading;
private TextView mDownloaded;
private Button mYes;
private Button mNo;
private Button mOk;
private ProgressBar mProgressBar;
private TextView mDownloadingInfo;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.assistant_codec_downloader, container, false);
mQuestion = view.findViewById(R.id.question);
mDownloading = view.findViewById(R.id.downloading);
mDownloaded = view.findViewById(R.id.downloaded);
mYes = view.findViewById(R.id.answerYes);
mNo = view.findViewById(R.id.answerNo);
mOk = view.findViewById(R.id.answerOk);
mProgressBar = view.findViewById(R.id.progressBar);
mDownloadingInfo = view.findViewById(R.id.downloadingInfo);
final OpenH264DownloadHelper codecDownloader =
LinphoneManager.getInstance().getOpenH264DownloadHelper();
final OpenH264DownloadHelperListener codecListener =
new OpenH264DownloadHelperListener() {
@Override
public void OnProgress(final int current, final int max) {
mHandler.post(
new Runnable() {
@Override
public void run() {
if (current <= max) {
hideAllItems();
mDownloadingInfo.setText(current + " / " + max);
mDownloadingInfo.setVisibility(View.VISIBLE);
mDownloading.setVisibility(View.VISIBLE);
mProgressBar.setMax(max);
mProgressBar.setProgress(current);
mProgressBar.setVisibility(View.VISIBLE);
} else {
hideAllItems();
mDownloaded.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT
>= Build.VERSION_CODES.LOLLIPOP_MR1) {
enabledH264(true);
LinphoneManager.getLc()
.reloadMsPlugins(
AssistantActivity.instance()
.getApplicationInfo()
.nativeLibraryDir);
AssistantActivity.instance().endDownloadCodec();
} else {
// We need to restart due to bad android linker
AssistantActivity.instance().restartApplication();
}
}
}
});
}
@Override
public void OnError(final String error) {
mHandler.post(
new Runnable() {
@Override
public void run() {
hideAllItems();
mDownloaded.setText("Sorry an error has occurred.");
mDownloaded.setVisibility(View.VISIBLE);
mOk.setVisibility(View.VISIBLE);
enabledH264(false);
AssistantActivity.instance().endDownloadCodec();
}
});
}
};
codecDownloader.setOpenH264HelperListener(codecListener);
mYes.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
hideAllItems();
mProgressBar.setVisibility(View.VISIBLE);
codecDownloader.downloadCodec();
}
});
mNo.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
enabledH264(false);
AssistantActivity.instance().endDownloadCodec();
}
});
hideAllItems();
if (savedInstanceState != null) {
if (savedInstanceState.containsKey("mQuestion"))
mQuestion.setVisibility((Integer) savedInstanceState.getSerializable("mQuestion"));
else mQuestion.setVisibility(View.VISIBLE);
if (savedInstanceState.containsKey("mYes"))
mYes.setVisibility((Integer) savedInstanceState.getSerializable("mYes"));
else mYes.setVisibility(View.VISIBLE);
if (savedInstanceState.containsKey("mNo"))
mNo.setVisibility((Integer) savedInstanceState.getSerializable("mNo"));
else mNo.setVisibility(View.VISIBLE);
if (savedInstanceState.containsKey("mDownloading"))
mDownloading.setVisibility(
(Integer) savedInstanceState.getSerializable("mDownloading"));
if (savedInstanceState.containsKey("mDownloaded"))
mDownloaded.setVisibility(
(Integer) savedInstanceState.getSerializable("mDownloaded"));
if (savedInstanceState.containsKey("context_bar"))
mProgressBar.setVisibility(
(Integer) savedInstanceState.getSerializable("context_bar"));
if (savedInstanceState.containsKey("mDownloadingInfo"))
mDownloadingInfo.setVisibility(
(Integer) savedInstanceState.getSerializable("mDownloadingInfo"));
if (savedInstanceState.containsKey("mOk"))
mOk.setVisibility((Integer) savedInstanceState.getSerializable("mOk"));
} else {
mYes.setVisibility(View.VISIBLE);
mQuestion.setVisibility(View.VISIBLE);
mNo.setVisibility(View.VISIBLE);
}
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mQuestion != null) outState.putSerializable("mQuestion", mQuestion.getVisibility());
if (mDownloading != null)
outState.putSerializable("mDownloading", mDownloading.getVisibility());
if (mDownloaded != null)
outState.putSerializable("mDownloaded", mDownloaded.getVisibility());
if (mYes != null) outState.putSerializable("mYes", mYes.getVisibility());
if (mNo != null) outState.putSerializable("mNo", mNo.getVisibility());
if (mOk != null) outState.putSerializable("mOk", mOk.getVisibility());
if (mProgressBar != null)
outState.putSerializable("context_bar", mProgressBar.getVisibility());
if (mDownloadingInfo != null)
outState.putSerializable("mDownloadingInfo", mDownloadingInfo.getVisibility());
super.onSaveInstanceState(outState);
}
private void hideAllItems() {
if (mQuestion != null) mQuestion.setVisibility(View.INVISIBLE);
if (mDownloading != null) mDownloading.setVisibility(View.INVISIBLE);
if (mDownloaded != null) mDownloaded.setVisibility(View.INVISIBLE);
if (mYes != null) mYes.setVisibility(View.INVISIBLE);
if (mNo != null) mNo.setVisibility(View.INVISIBLE);
if (mOk != null) mOk.setVisibility(View.INVISIBLE);
if (mProgressBar != null) mProgressBar.setVisibility(View.INVISIBLE);
if (mDownloadingInfo != null) mDownloadingInfo.setVisibility(View.INVISIBLE);
}
private void enabledH264(boolean enable) {
PayloadType h264 = null;
for (PayloadType pt : LinphoneManager.getLc().getVideoPayloadTypes()) {
if (pt.getMimeType().equals("H264")) h264 = pt;
}
if (h264 != null) {
h264.enable(enable);
}
}
}

View file

@ -30,63 +30,61 @@ import android.widget.AdapterView;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.DialPlan; import org.linphone.core.DialPlan;
public class CountryListFragment extends Fragment implements AdapterView.OnItemClickListener, View.OnClickListener { public class CountryListFragment extends Fragment
private ListView list; implements AdapterView.OnItemClickListener, View.OnClickListener {
private EditText search; private ListView mList;
private ImageView clearSearchField; private EditText mSearch;
private AssistantActivity.CountryListAdapter adapter; private ImageView mClearSearchField;
private AssistantActivity.CountryListAdapter mAdapter;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.assistant_country_list, container, false); View view = inflater.inflate(R.layout.assistant_country_list, container, false);
adapter = AssistantActivity.instance().getCountryListAdapter(); mAdapter = AssistantActivity.instance().getCountryListAdapter();
adapter.setInflater(inflater); mAdapter.setInflater(inflater);
search = view.findViewById(R.id.search_country); mSearch = view.findViewById(R.id.search_country);
clearSearchField = view.findViewById(R.id.clearSearchField); mClearSearchField = view.findViewById(R.id.clearSearchField);
clearSearchField.setOnClickListener(this); mClearSearchField.setOnClickListener(this);
list = view.findViewById(R.id.countryList); mList = view.findViewById(R.id.countryList);
list.setAdapter(adapter); mList.setAdapter(mAdapter);
list.setOnItemClickListener(this); mList.setOnItemClickListener(this);
search.addTextChangedListener(new TextWatcher() { mSearch.addTextChangedListener(
@Override new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) { @Override
} public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s); mAdapter.getFilter().filter(s);
} }
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {}
});
} mSearch.setText("");
});
search.setText("");
return view; return view;
} }
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
DialPlan c = (DialPlan) view.getTag(); AssistantActivity.instance().country = (DialPlan) view.getTag();
AssistantActivity.instance().country = c;
AssistantActivity.instance().onBackPressed(); AssistantActivity.instance().onBackPressed();
} }
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v.getId() == R.id.clearSearchField) { if (v.getId() == R.id.clearSearchField) {
search.setText(""); mSearch.setText("");
} }
} }
} }

View file

@ -0,0 +1,138 @@
package org.linphone.assistant;
/*
CreateAccountActivationFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListener;
import org.linphone.settings.LinphonePreferences;
public class CreateAccountActivationFragment extends Fragment
implements OnClickListener, AccountCreatorListener {
private String mUsername, mPassword;
private Button mCheckAccount;
private TextView mEmail;
private AccountCreator mAccountCreator;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view =
inflater.inflate(
R.layout.assistant_account_creation_email_activation, container, false);
mAccountCreator =
LinphoneManager.getLc()
.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(this);
mUsername = getArguments().getString("Username");
mPassword = getArguments().getString("Password");
mAccountCreator.setUsername(mUsername);
mAccountCreator.setPassword(mPassword);
mEmail = view.findViewById(R.id.send_email);
mEmail.setText(getArguments().getString("Email"));
mCheckAccount = view.findViewById(R.id.assistant_check);
mCheckAccount.setOnClickListener(this);
return view;
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.assistant_check) {
mCheckAccount.setEnabled(false);
mAccountCreator.isAccountActivated();
}
}
@Override
public void onIsAccountExist(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onCreateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onLinkAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAlias(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAccountActivated(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(AccountCreator.Status.AccountNotActivated)) {
Toast.makeText(
getActivity(),
getString(R.string.assistant_account_not_validated),
Toast.LENGTH_LONG)
.show();
} else if (status.equals(AccountCreator.Status.AccountActivated)) {
AssistantActivity.instance().linphoneLogIn(accountCreator);
AssistantActivity.instance().isAccountVerified();
} else {
Toast.makeText(
getActivity(),
getString(R.string.wizard_server_unavailable),
Toast.LENGTH_LONG)
.show();
}
mCheckAccount.setEnabled(true);
}
@Override
public void onRecoverAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAccountLinked(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAliasUsed(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onUpdateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
}

View file

@ -0,0 +1,227 @@
package org.linphone.assistant;
/*
CreateAccountCodeActivationFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
import android.app.Fragment;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListener;
import org.linphone.settings.LinphonePreferences;
public class CreateAccountCodeActivationFragment extends Fragment
implements AccountCreatorListener {
private String mUsername, mPhone, mDialcode;
private TextView mTitle, mPhonenumber;
private EditText mCode;
private boolean mRecoverAccount = false, mLinkAccount = false;
private int mCodeLength, mAccountNumber;
private ImageView mBack;
private Button mCheckAccount;
private AccountCreator mAccountCreator;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view =
inflater.inflate(
R.layout.assistant_account_creation_code_activation, container, false);
mUsername = getArguments().getString("Username");
mPhone = getArguments().getString("Phone");
mDialcode = getArguments().getString("Dialcode");
mRecoverAccount = getArguments().getBoolean("RecoverAccount");
mLinkAccount = getArguments().getBoolean("LinkAccount");
mAccountNumber = getArguments().getInt("AccountNumber");
mCodeLength = LinphonePreferences.instance().getCodeLength();
mAccountCreator =
LinphoneManager.getLc()
.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(this);
mAccountCreator.setUsername(mUsername);
mAccountCreator.setPhoneNumber(mPhone, mDialcode);
mBack = view.findViewById(R.id.back);
if (mBack != null) mBack.setVisibility(Button.INVISIBLE);
mTitle = view.findViewById(R.id.title_account_activation);
if (mLinkAccount) {
mTitle.setText(getString(R.string.assistant_link_account));
} else if (mRecoverAccount) {
mTitle.setText(getString(R.string.assistant_linphone_account));
}
mPhonenumber = view.findViewById(R.id.send_phone_number);
mPhonenumber.setText(mAccountCreator.getPhoneNumber());
mCode = view.findViewById(R.id.assistant_code);
mCode.addTextChangedListener(
new TextWatcher() {
@Override
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if (s.length() == mCodeLength) {
mCheckAccount.setEnabled(true);
} else {
mCheckAccount.setEnabled(false);
}
}
});
mCheckAccount = view.findViewById(R.id.assistant_check);
mCheckAccount.setEnabled(false);
mCheckAccount.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
mCheckAccount.setEnabled(false);
mAccountCreator.setActivationCode(mCode.getText().toString());
if (mLinkAccount) {
linkAccount();
} else {
activateAccount();
}
}
});
return view;
}
private void linkAccount() {
mAccountCreator.setUsername(
LinphonePreferences.instance().getAccountUsername(mAccountNumber));
mAccountCreator.setHa1(LinphonePreferences.instance().getAccountHa1(mAccountNumber));
mAccountCreator.activateAlias();
}
private void activateAccount() {
if (mAccountCreator.getUsername() == null) {
mAccountCreator.setUsername(mAccountCreator.getPhoneNumber());
}
mAccountCreator.activateAccount();
}
@Override
public void onIsAccountExist(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onCreateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(AccountCreator.Status.AccountActivated)) {
mCheckAccount.setEnabled(true);
if (accountCreator.getUsername() != null) {
AssistantActivity.instance().linphoneLogIn(accountCreator);
if (!mRecoverAccount) {
AssistantActivity.instance().isAccountVerified();
} else {
AssistantActivity.instance().success();
}
} else {
AssistantActivity.instance().linphoneLogIn(accountCreator);
if (!mRecoverAccount) {
AssistantActivity.instance().isAccountVerified();
} else {
AssistantActivity.instance().success();
}
}
} else if (status.equals(AccountCreator.Status.RequestFailed)) {
Toast.makeText(
getActivity(),
getString(R.string.wizard_server_unavailable),
Toast.LENGTH_LONG)
.show();
} else {
Toast.makeText(
getActivity(),
getString(R.string.assistant_error_confirmation_code),
Toast.LENGTH_LONG)
.show();
AssistantActivity.instance().displayAssistantLinphoneLogin(mPhone, mDialcode);
}
}
@Override
public void onLinkAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAlias(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(AccountCreator.Status.AccountActivated)) {
LinphonePreferences.instance()
.setPrefix(
mAccountNumber,
org.linphone.core.Utils.getPrefixFromE164(
accountCreator.getPhoneNumber()));
LinphonePreferences.instance().setLinkPopupTime("");
AssistantActivity.instance().hideKeyboard();
AssistantActivity.instance().success();
}
}
@Override
public void onIsAccountActivated(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onRecoverAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAccountLinked(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAliasUsed(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onUpdateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
}

View file

@ -0,0 +1,804 @@
package org.linphone.assistant;
/*
CreateAccountFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreator.Status;
import org.linphone.core.AccountCreatorListener;
import org.linphone.core.DialPlan;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
public class CreateAccountFragment extends Fragment
implements CompoundButton.OnCheckedChangeListener, OnClickListener, AccountCreatorListener {
private final Pattern UPPER_CASE_REGEX = Pattern.compile("[A-Z]");
private EditText mPhoneNumberEdit,
mUsernameEdit,
mPasswordEdit,
mPasswordConfirmEdit,
mEmailEdit,
mDialCode;
private TextView mPhoneNumberError,
mPasswordError,
mPasswordConfirmError,
mEmailError,
mAssisstantTitle,
mSipUri,
mSkip,
mInstruction;
private ImageView mPhoneNumberInfo;
private boolean mPasswordOk = false;
private boolean mEmailOk = false;
private boolean mConfirmPasswordOk = false;
private boolean mLinkAccount = false;
private Button mCreateAccount, mSelectCountry;
private CheckBox mUseUsername, mUseEmail;
private String mAddressSip = "";
private int mCountryCode;
private LinearLayout mPhoneNumberLayout,
mUsernameLayout,
mEmailLayout,
mPasswordLayout,
mPasswordConfirmLayout;
private AccountCreator mAccountCreator;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.assistant_account_creation, container, false);
// Initialize mAccountCreator
mAccountCreator =
LinphoneManager.getLc()
.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(this);
mAccountCreator.setDomain(getString(R.string.default_domain));
mInstruction = view.findViewById(R.id.message_create_account);
mCreateAccount = view.findViewById(R.id.assistant_create);
mPhoneNumberLayout = view.findViewById(R.id.phone_number_layout);
mUsernameLayout = view.findViewById(R.id.username_layout);
mEmailLayout = view.findViewById(R.id.email_layout);
mPasswordLayout = view.findViewById(R.id.password_layout);
mPasswordConfirmLayout = view.findViewById(R.id.password_confirm_layout);
mUseUsername = view.findViewById(R.id.use_username);
mUseEmail = view.findViewById(R.id.use_email);
mUsernameEdit = view.findViewById(R.id.username);
mPhoneNumberError = view.findViewById(R.id.phone_number_error);
mPhoneNumberEdit = view.findViewById(R.id.phone_number);
mSipUri = view.findViewById(R.id.sip_uri);
mPhoneNumberInfo = view.findViewById(R.id.info_phone_number);
mSelectCountry = view.findViewById(R.id.select_country);
mDialCode = view.findViewById(R.id.dial_code);
mAssisstantTitle = view.findViewById(R.id.assistant_title);
mPasswordError = view.findViewById(R.id.password_error);
mPasswordEdit = view.findViewById(R.id.password);
mPasswordConfirmError = view.findViewById(R.id.confirm_password_error);
mPasswordConfirmEdit = view.findViewById(R.id.confirm_password);
mEmailError = view.findViewById(R.id.email_error);
mEmailEdit = view.findViewById(R.id.email);
mSkip = view.findViewById(R.id.assistant_skip);
// Phone number
if (getResources().getBoolean(R.bool.use_phone_number_validation)) {
getActivity().getApplicationContext();
// Automatically get the country code from the phone
TelephonyManager tm =
(TelephonyManager)
getActivity()
.getApplicationContext()
.getSystemService(Context.TELEPHONY_SERVICE);
String countryIso = tm.getNetworkCountryIso();
mCountryCode = org.linphone.core.Utils.getCccFromIso(countryIso.toUpperCase());
mPhoneNumberLayout.setVisibility(View.VISIBLE);
mPhoneNumberInfo.setOnClickListener(this);
mSelectCountry.setOnClickListener(this);
DialPlan c = AssistantActivity.instance().country;
if (c != null) {
mSelectCountry.setText(c.getCountry());
mDialCode.setText(
c.getCountryCallingCode().contains("+")
? c.getCountryCallingCode()
: "+" + c.getCountryCallingCode());
} else {
c =
AssistantActivity.instance()
.getCountryListAdapter()
.getCountryFromCountryCode(String.valueOf(mCountryCode));
if (c != null) {
mSelectCountry.setText(c.getCountry());
mDialCode.setText(
c.getCountryCallingCode().contains("+")
? c.getCountryCallingCode()
: "+" + c.getCountryCallingCode());
}
}
// Allow user to enter a username instead use the phone number as username
if (getResources().getBoolean(R.bool.assistant_allow_username)) {
mUseUsername.setVisibility(View.VISIBLE);
mUseUsername.setOnCheckedChangeListener(this);
}
addPhoneNumberHandler(mPhoneNumberEdit);
addPhoneNumberHandler(mDialCode);
}
// Password & email address
if (getResources().getBoolean(R.bool.isTablet)
|| !getResources().getBoolean(R.bool.use_phone_number_validation)) {
mUseEmail.setVisibility(View.VISIBLE);
mUseEmail.setOnCheckedChangeListener(this);
if (getResources().getBoolean(R.bool.pre_fill_email_in_assistant)) {
Account[] accounts =
AccountManager.get(getActivity()).getAccountsByType("com.google");
for (Account account : accounts) {
if (isEmailCorrect(account.name)) {
String possibleEmail = account.name;
mEmailEdit.setText(possibleEmail);
mAccountCreator.setEmail(possibleEmail);
mEmailOk = true;
break;
}
}
}
addPasswordHandler(mPasswordEdit);
addConfirmPasswordHandler(mPasswordEdit, mPasswordConfirmEdit);
addEmailHandler(mEmailEdit);
}
// Hide phone number and display username/email/password
if (!getResources().getBoolean(R.bool.use_phone_number_validation)) {
mUseEmail.setVisibility(View.GONE);
mUseUsername.setVisibility(View.GONE);
mUsernameLayout.setVisibility(View.VISIBLE);
mPasswordLayout.setVisibility(View.VISIBLE);
mPasswordConfirmLayout.setVisibility(View.VISIBLE);
mEmailLayout.setVisibility(View.VISIBLE);
}
// Link account with phone number
if (getArguments().getBoolean("LinkPhoneNumber")) {
mLinkAccount = true;
mUseEmail.setVisibility(View.GONE);
mUseUsername.setVisibility(View.GONE);
mUsernameLayout.setVisibility(View.GONE);
mPasswordLayout.setVisibility(View.GONE);
mPasswordConfirmLayout.setVisibility(View.GONE);
mEmailLayout.setVisibility(View.GONE);
mSkip.setVisibility(View.VISIBLE);
mSkip.setOnClickListener(this);
mCreateAccount.setText(getResources().getString(R.string.link_account));
mAssisstantTitle.setText(getResources().getString(R.string.link_account));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mAccountCreator.setLanguage(Locale.getDefault().toLanguageTag());
}
addUsernameHandler(mUsernameEdit);
mCreateAccount.setEnabled(true);
mCreateAccount.setOnClickListener(this);
return view;
}
@Override
public void onPause() {
super.onPause();
mAccountCreator.setListener(null);
}
private String getUsername() {
if (mUsernameEdit != null) {
String username = mUsernameEdit.getText().toString();
return username.toLowerCase(Locale.getDefault());
}
return null;
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.getId() == R.id.use_username) {
if (isChecked) {
mUsernameLayout.setVisibility(View.VISIBLE);
onTextChanged2();
} else {
mUsernameLayout.setVisibility(View.GONE);
mAccountCreator.setUsername(null);
onTextChanged2();
}
} else if (buttonView.getId() == R.id.use_email) {
if (isChecked) {
mDialCode.setBackgroundResource(R.drawable.resizable_textfield);
mPhoneNumberEdit.setBackgroundResource(R.drawable.resizable_textfield);
mUseUsername.setEnabled(false);
mDialCode.setEnabled(false);
mSelectCountry.setEnabled(false);
mPhoneNumberEdit.setEnabled(false);
mEmailLayout.setVisibility(View.VISIBLE);
mPasswordLayout.setVisibility(View.VISIBLE);
mPasswordConfirmLayout.setVisibility(View.VISIBLE);
mUsernameLayout.setVisibility(View.VISIBLE);
mUseUsername.setVisibility(CheckBox.GONE);
mPhoneNumberLayout.setVisibility(LinearLayout.GONE);
mInstruction.setText(getString(R.string.assistant_create_account_part_email));
} else {
if (!mUseUsername.isChecked()) {
mUsernameLayout.setVisibility(View.GONE);
}
mUseUsername.setEnabled(true);
mDialCode.setEnabled(true);
mSelectCountry.setEnabled(true);
mPhoneNumberEdit.setEnabled(true);
mEmailLayout.setVisibility(View.GONE);
mPasswordLayout.setVisibility(View.GONE);
mPasswordConfirmLayout.setVisibility(View.GONE);
mUseUsername.setVisibility(CheckBox.VISIBLE);
mPhoneNumberLayout.setVisibility(LinearLayout.VISIBLE);
mInstruction.setText(getString(R.string.assistant_create_account_part_1));
}
}
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.select_country) {
AssistantActivity.instance().displayCountryChooser();
} else if (id == R.id.assistant_skip) {
if (getArguments().getBoolean("LinkFromPref")) {
AssistantActivity.instance().finish();
} else {
AssistantActivity.instance().success();
}
} else if (id == R.id.info_phone_number) {
if (mLinkAccount) {
new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.phone_number_info_title))
.setMessage(
getString(R.string.phone_number_link_info_content)
+ "\n"
+ getString(
R.string
.phone_number_link_info_content_already_account))
.show();
} else {
new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.phone_number_info_title))
.setMessage(getString(R.string.phone_number_info_content))
.show();
}
} else if (id == R.id.assistant_create) {
mCreateAccount.setEnabled(false);
if (mLinkAccount) {
addAlias();
} else {
if (mUseEmail.isChecked()) mAccountCreator.setPhoneNumber(null, null);
if (!getResources().getBoolean(R.bool.isTablet) || getUsername().length() > 0) {
mAccountCreator.isAccountExist();
} else {
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForUsernameStatus(
AccountCreator.UsernameStatus.TooShort),
AssistantActivity.instance());
mCreateAccount.setEnabled(true);
}
}
}
}
private boolean isEmailCorrect(String email) {
Pattern emailPattern = Patterns.EMAIL_ADDRESS;
return emailPattern.matcher(email).matches();
}
private boolean isPasswordCorrect(String password) {
return password.length() >= 1;
}
private void addAlias() {
mAccountCreator.setUsername(
LinphonePreferences.instance()
.getAccountUsername(
LinphonePreferences.instance().getDefaultAccountIndex()));
int status =
mAccountCreator.setPhoneNumber(
mPhoneNumberEdit.getText().toString(),
LinphoneUtils.getCountryCode(mDialCode));
boolean isOk = status == AccountCreator.PhoneNumberStatus.Ok.toInt();
if (isOk) {
mAccountCreator.linkAccount();
} else {
mCreateAccount.setEnabled(true);
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForPhoneNumberStatus(status), AssistantActivity.instance());
LinphoneUtils.displayError(
isOk, mPhoneNumberError, LinphoneUtils.errorForPhoneNumberStatus(status));
}
}
private void createAccount() {
if ((getResources().getBoolean(R.bool.isTablet)
|| !getResources().getBoolean(R.bool.use_phone_number_validation))
&& mUseEmail.isChecked()) {
AccountCreator.EmailStatus emailStatus;
AccountCreator.PasswordStatus passwordStatus;
passwordStatus = mAccountCreator.setPassword(mPasswordEdit.getText().toString());
emailStatus = mAccountCreator.setEmail(mEmailEdit.getText().toString());
if (!mEmailOk) {
LinphoneUtils.displayError(
false, mEmailError, LinphoneUtils.errorForEmailStatus(emailStatus));
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForEmailStatus(emailStatus),
AssistantActivity.instance());
} else if (!mPasswordOk) {
LinphoneUtils.displayError(
false,
mPasswordError,
LinphoneUtils.errorForPasswordStatus(passwordStatus));
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForPasswordStatus(passwordStatus),
AssistantActivity.instance());
} else if (!mConfirmPasswordOk) {
String msg;
if (mPasswordConfirmEdit
.getText()
.toString()
.equals(mPasswordEdit.getText().toString())) {
msg = getString(R.string.wizard_password_incorrect);
} else {
msg = getString(R.string.wizard_passwords_unmatched);
}
LinphoneUtils.displayError(false, mPasswordError, msg);
LinphoneUtils.displayErrorAlert(msg, AssistantActivity.instance());
} else {
mAccountCreator.createAccount();
}
} else {
if (mPhoneNumberEdit.length() > 0 || mDialCode.length() > 1) {
int phoneStatus;
boolean isOk;
phoneStatus =
mAccountCreator.setPhoneNumber(
mPhoneNumberEdit.getText().toString(),
LinphoneUtils.getCountryCode(mDialCode));
isOk = phoneStatus == AccountCreator.PhoneNumberStatus.Ok.toInt();
if (!mUseUsername.isChecked() && mAccountCreator.getUsername() == null) {
mAccountCreator.setUsername(mAccountCreator.getPhoneNumber());
} else {
mAccountCreator.setUsername(mUsernameEdit.getText().toString());
mAccountCreator.setPhoneNumber(
mPhoneNumberEdit.getText().toString(), mDialCode.getText().toString());
}
if (isOk) {
mAccountCreator.createAccount();
} else {
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForPhoneNumberStatus(phoneStatus),
AssistantActivity.instance());
LinphoneUtils.displayError(
isOk,
mPhoneNumberError,
LinphoneUtils.errorForPhoneNumberStatus(phoneStatus));
}
} else {
LinphoneUtils.displayErrorAlert(
getString(R.string.assistant_create_account_part_1),
AssistantActivity.instance());
}
}
mCreateAccount.setEnabled(true);
}
private int getPhoneNumberStatus() {
int status =
mAccountCreator.setPhoneNumber(
mPhoneNumberEdit.getText().toString(),
LinphoneUtils.getCountryCode(mDialCode));
mAddressSip = mAccountCreator.getPhoneNumber();
return status;
}
private void onTextChanged2() {
String msg = "";
mAccountCreator.setUsername(getUsername());
if (!mUseEmail.isChecked()
&& getResources().getBoolean(R.bool.use_phone_number_validation)) {
int status = getPhoneNumberStatus();
boolean isOk = (status == AccountCreator.PhoneNumberStatus.Ok.toInt());
LinphoneUtils.displayError(
isOk, mPhoneNumberError, LinphoneUtils.errorForPhoneNumberStatus(status));
// Username or phone number
if (getResources().getBoolean(R.bool.assistant_allow_username)
&& mUseUsername.isChecked()) {
mAddressSip = getUsername();
}
if (!isOk) {
if (status == AccountCreator.PhoneNumberStatus.InvalidCountryCode.toInt()) {
mDialCode.setBackgroundResource(R.drawable.resizable_textfield_error);
mPhoneNumberEdit.setBackgroundResource(R.drawable.resizable_textfield);
} else {
mDialCode.setBackgroundResource(R.drawable.resizable_textfield);
mPhoneNumberEdit.setBackgroundResource(R.drawable.resizable_textfield_error);
}
} else {
mDialCode.setBackgroundResource(R.drawable.resizable_textfield);
mPhoneNumberEdit.setBackgroundResource(R.drawable.resizable_textfield);
if (!mLinkAccount && mAddressSip.length() > 0) {
msg =
getResources()
.getString(
R.string
.assistant_create_account_phone_number_address)
+ " <"
+ mAddressSip
+ "@"
+ getResources().getString(R.string.default_domain)
+ ">";
}
}
} else {
mAddressSip = getUsername();
if (mAddressSip.length() > 0) {
msg =
getResources()
.getString(
R.string
.assistant_create_account_phone_number_address)
+ " <sip:"
+ mAddressSip
+ "@"
+ getResources().getString(R.string.default_domain)
+ ">";
}
}
mSipUri.setText(msg);
}
private void addPhoneNumberHandler(final EditText field) {
field.addTextChangedListener(
new TextWatcher() {
public void afterTextChanged(Editable s) {
if (field.equals(mDialCode)) {
DialPlan c =
AssistantActivity.instance()
.getCountryListAdapter()
.getCountryFromCountryCode(
mDialCode.getText().toString());
if (c != null) {
AssistantActivity.instance().country = c;
mSelectCountry.setText(c.getCountry());
} else {
mSelectCountry.setText(R.string.select_your_country);
}
}
}
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int count, int after) {
onTextChanged2();
}
});
}
private void addUsernameHandler(final EditText field) {
field.addTextChangedListener(
new TextWatcher() {
public void afterTextChanged(Editable s) {
Matcher matcher = UPPER_CASE_REGEX.matcher(s);
while (matcher.find()) {
CharSequence upperCaseRegion =
s.subSequence(matcher.start(), matcher.end());
s.replace(
matcher.start(),
matcher.end(),
upperCaseRegion.toString().toLowerCase());
}
}
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int count, int after) {
onTextChanged2();
}
});
}
private void addEmailHandler(final EditText field) {
field.addTextChangedListener(
new TextWatcher() {
public void afterTextChanged(Editable s) {
mEmailOk = false;
AccountCreator.EmailStatus status =
mAccountCreator.setEmail(field.getText().toString());
if (status.equals(AccountCreator.EmailStatus.Ok)) {
mEmailOk = true;
LinphoneUtils.displayError(mEmailOk, mEmailError, "");
} else {
LinphoneUtils.displayError(
mEmailOk,
mEmailError,
LinphoneUtils.errorForEmailStatus(status));
}
}
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int count, int after) {}
});
}
private void addPasswordHandler(final EditText field1) {
TextWatcher passwordListener =
new TextWatcher() {
public void afterTextChanged(Editable s) {
mPasswordOk = false;
AccountCreator.PasswordStatus status =
mAccountCreator.setPassword(field1.getText().toString());
if (isPasswordCorrect(field1.getText().toString())) {
mPasswordOk = true;
LinphoneUtils.displayError(mPasswordOk, mPasswordError, "");
} else {
LinphoneUtils.displayError(
mPasswordOk,
mPasswordError,
LinphoneUtils.errorForPasswordStatus(status));
}
}
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int count, int after) {}
};
field1.addTextChangedListener(passwordListener);
}
private void addConfirmPasswordHandler(final EditText field1, final EditText field2) {
TextWatcher passwordListener =
new TextWatcher() {
public void afterTextChanged(Editable s) {
mConfirmPasswordOk = false;
if (field1.getText().toString().equals(field2.getText().toString())) {
mConfirmPasswordOk = true;
if (!isPasswordCorrect(field1.getText().toString())) {
LinphoneUtils.displayError(
mPasswordOk,
mPasswordError,
getString(R.string.wizard_password_incorrect));
} else {
LinphoneUtils.displayError(
mConfirmPasswordOk, mPasswordConfirmError, "");
}
} else {
LinphoneUtils.displayError(
mConfirmPasswordOk,
mPasswordConfirmError,
getString(R.string.wizard_passwords_unmatched));
}
}
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int count, int after) {}
};
field1.addTextChangedListener(passwordListener);
field2.addTextChangedListener(passwordListener);
}
@Override
public void onIsAccountExist(AccountCreator accountCreator, final Status status, String resp) {
if (status.equals(Status.AccountExist) || status.equals(Status.AccountExistWithAlias)) {
if (mUseEmail.isChecked()) {
mCreateAccount.setEnabled(true);
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForStatus(status), AssistantActivity.instance());
} else {
accountCreator.isAliasUsed();
}
} else {
createAccount();
}
}
@Override
public void onCreateAccount(AccountCreator accountCreator, Status status, String resp) {
if (status.equals(Status.AccountCreated)) {
if (mUseEmail.isChecked()
|| !getResources().getBoolean(R.bool.use_phone_number_validation)) {
AssistantActivity.instance()
.displayAssistantConfirm(
getUsername(),
mPasswordEdit.getText().toString(),
mEmailEdit.getText().toString());
} else {
AssistantActivity.instance()
.displayAssistantCodeConfirm(
getUsername(),
mPhoneNumberEdit.getText().toString(),
LinphoneUtils.getCountryCode(mDialCode),
false);
}
} else {
mCreateAccount.setEnabled(true);
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForStatus(status), AssistantActivity.instance());
}
}
@Override
public void onActivateAccount(AccountCreator accountCreator, Status status, String resp) {}
@Override
public void onLinkAccount(AccountCreator accountCreator, Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(Status.RequestOk)) {
AssistantActivity.instance()
.displayAssistantCodeConfirm(
getUsername(),
mPhoneNumberEdit.getText().toString(),
LinphoneUtils.getCountryCode(mDialCode),
false);
}
}
@Override
public void onActivateAlias(AccountCreator accountCreator, Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(Status.RequestOk)) {
AssistantActivity.instance()
.displayAssistantCodeConfirm(
getUsername(),
mPhoneNumberEdit.getText().toString(),
LinphoneUtils.getCountryCode(mDialCode),
false);
}
}
@Override
public void onIsAccountActivated(AccountCreator accountCreator, Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(Status.AccountNotActivated)) {
if (getResources().getBoolean(R.bool.isTablet)
|| !getResources().getBoolean(R.bool.use_phone_number_validation)) {
// mAccountCreator.activateAccount(); // Resend email TODO
} else {
accountCreator.recoverAccount(); // Resend SMS
}
} else {
mCreateAccount.setEnabled(true);
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForStatus(status), AssistantActivity.instance());
}
}
@Override
public void onRecoverAccount(AccountCreator accountCreator, Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(Status.RequestOk)) {
AssistantActivity.instance()
.displayAssistantCodeConfirm(
getUsername(),
mPhoneNumberEdit.getText().toString(),
mDialCode.getText().toString(),
false);
} else {
mCreateAccount.setEnabled(true);
// SMS error
LinphoneUtils.displayErrorAlert(
getString(R.string.request_failed), AssistantActivity.instance());
}
}
@Override
public void onIsAccountLinked(AccountCreator accountCreator, Status status, String resp) {}
@Override
public void onIsAliasUsed(AccountCreator ac, Status status, String resp) {
if (AssistantActivity.instance() == null) {
return;
}
if (status.equals(Status.AliasIsAccount) || status.equals(Status.AliasExist)) {
if (mAccountCreator.getPhoneNumber() != null
&& mAccountCreator.getUsername() != null
&& mAccountCreator.getPhoneNumber().compareTo(mAccountCreator.getUsername())
== 0) {
mAccountCreator.isAccountActivated();
} else {
mCreateAccount.setEnabled(true);
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForStatus(status), AssistantActivity.instance());
}
} else {
mAccountCreator.isAccountActivated();
}
}
@Override
public void onUpdateAccount(AccountCreator accountCreator, Status status, String resp) {}
}

View file

@ -0,0 +1,117 @@
package org.linphone.assistant;
/*
EchoCancellerCalibrationFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.EcCalibratorStatus;
import org.linphone.core.XmlRpcArgType;
import org.linphone.core.XmlRpcRequest;
import org.linphone.core.XmlRpcRequestListener;
import org.linphone.core.XmlRpcSession;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
public class EchoCancellerCalibrationFragment extends Fragment implements XmlRpcRequestListener {
private final Handler mHandler = new Handler();
private boolean mSendEcCalibrationResult = false;
private CoreListenerStub mListener;
private XmlRpcSession mXmlRpcSession;
private XmlRpcRequest mXmlRpcRequest;
private Runnable mRunFinished;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.assistant_ec_calibration, container, false);
mListener =
new CoreListenerStub() {
@Override
public void onEcCalibrationResult(
Core lc, EcCalibratorStatus status, int delay_ms) {
lc.removeListener(mListener);
LinphoneManager.getInstance().routeAudioToReceiver();
if (mSendEcCalibrationResult) {
sendEcCalibrationResult(status, delay_ms);
} else {
AssistantActivity.instance().isEchoCalibrationFinished();
}
}
};
mRunFinished =
new Runnable() {
public void run() {
AssistantActivity.instance().isEchoCalibrationFinished();
}
};
mXmlRpcSession =
LinphoneManager.getLcIfManagerNotDestroyedOrNull()
.createXmlRpcSession(LinphonePreferences.instance().getXmlrpcUrl());
mXmlRpcRequest =
mXmlRpcSession.createRequest(XmlRpcArgType.None, "add_ec_calibration_result");
mXmlRpcRequest.setListener(this);
LinphoneManager.getLc().addListener(mListener);
LinphoneManager.getInstance().startEcCalibration();
return view;
}
public void enableEcCalibrationResultSending(boolean enabled) {
mSendEcCalibrationResult = enabled;
}
@Override
public void onResponse(XmlRpcRequest request) {
mHandler.post(mRunFinished);
}
private void sendEcCalibrationResult(EcCalibratorStatus status, int delayMs) {
Boolean hasBuiltInEchoCanceler = LinphoneManager.getLc().hasBuiltinEchoCanceller();
Log.i(
"Add echo canceller calibration result: manufacturer="
+ Build.MANUFACTURER
+ " model="
+ Build.MODEL
+ " status="
+ status
+ " delay="
+ delayMs
+ "ms"
+ " hasBuiltInEchoCanceler "
+ hasBuiltInEchoCanceler);
mXmlRpcRequest.addStringArg(Build.MANUFACTURER);
mXmlRpcRequest.addStringArg(Build.MODEL);
mXmlRpcRequest.addStringArg(status.toString());
mXmlRpcRequest.addIntArg(delayMs);
mXmlRpcRequest.addIntArg(hasBuiltInEchoCanceler ? 1 : 0);
mXmlRpcSession.sendRequest(mXmlRpcRequest);
}
}

View file

@ -0,0 +1,431 @@
package org.linphone.assistant;
/*
LinphoneLoginFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.Locale;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.AccountCreator;
import org.linphone.core.AccountCreatorListener;
import org.linphone.core.DialPlan;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
public class LinphoneLoginFragment extends Fragment
implements CompoundButton.OnCheckedChangeListener,
OnClickListener,
TextWatcher,
AccountCreatorListener {
private EditText mLogin, mPassword, mPhoneNumberEdit, mDialCode;
private Button mApply, mSelectCountry;
private CheckBox mUseUsername;
private LinearLayout mPhoneNumberLayout, mUsernameLayout, mPasswordLayout;
private TextView mForgotPassword, mMessagePhoneNumber, mPhoneNumberError;
private Boolean mRecoverAccount;
private AccountCreator mAccountCreator;
private int mCountryCode;
private String mPhone, mDialcode, mUsername, mPwd;
private ImageView mPhoneNumberInfo;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.assistant_linphone_login, container, false);
mAccountCreator =
LinphoneManager.getLc()
.createAccountCreator(LinphonePreferences.instance().getXmlrpcUrl());
mAccountCreator.setListener(this);
mLogin = view.findViewById(R.id.assistant_username);
mLogin.addTextChangedListener(this);
mRecoverAccount = true;
mDialCode = view.findViewById(R.id.dial_code);
mPhoneNumberEdit = view.findViewById(R.id.phone_number);
mPhoneNumberLayout = view.findViewById(R.id.phone_number_layout);
mPhoneNumberError = view.findViewById(R.id.phone_number_error_2);
mPhoneNumberInfo = view.findViewById(R.id.info_phone_number);
mUseUsername = view.findViewById(R.id.use_username);
mUsernameLayout = view.findViewById(R.id.username_layout);
mPasswordLayout = view.findViewById(R.id.password_layout);
mPassword = view.findViewById(R.id.assistant_password);
mMessagePhoneNumber = view.findViewById(R.id.message_phone_number);
mForgotPassword = view.findViewById(R.id.forgot_password);
mSelectCountry = view.findViewById(R.id.select_country);
mApply = view.findViewById(R.id.assistant_apply);
mApply.setEnabled(true);
mApply.setOnClickListener(this);
// Phone number
if (getResources().getBoolean(R.bool.use_phone_number_validation)) {
mMessagePhoneNumber.setText(getString(R.string.assistant_create_account_part_1));
mPhone = getArguments().getString("Phone");
mDialcode = getArguments().getString("Dialcode");
getActivity().getApplicationContext();
// Automatically get the country code from the mPhone
TelephonyManager tm =
(TelephonyManager)
getActivity()
.getApplicationContext()
.getSystemService(Context.TELEPHONY_SERVICE);
String countryIso = tm.getNetworkCountryIso();
mCountryCode = org.linphone.core.Utils.getCccFromIso(countryIso.toUpperCase());
DialPlan c = AssistantActivity.instance().country;
if (c != null) {
mSelectCountry.setText(c.getCountry());
mDialCode.setText(
c.getCountryCallingCode().contains("+")
? c.getCountryCallingCode()
: "+" + c.getCountryCallingCode());
} else {
c =
AssistantActivity.instance()
.getCountryListAdapter()
.getCountryFromCountryCode(String.valueOf(mCountryCode));
if (c != null) {
mSelectCountry.setText(c.getCountry());
mDialCode.setText(
c.getCountryCallingCode().contains("+")
? c.getCountryCallingCode()
: "+" + c.getCountryCallingCode());
}
}
mPhoneNumberLayout.setVisibility(View.VISIBLE);
mSelectCountry.setOnClickListener(this);
mPhoneNumberInfo.setOnClickListener(this);
// Allow user to enter a mUsername instead use the mPhone number as mUsername
if (getResources().getBoolean(R.bool.assistant_allow_username)) {
mUseUsername.setVisibility(View.VISIBLE);
mUseUsername.setOnCheckedChangeListener(this);
}
if (mPhone != null) mPhoneNumberEdit.setText(mPhone);
if (mDialcode != null) mDialCode.setText("+" + mDialcode);
}
if (getResources().getBoolean(R.bool.assistant_allow_username)) {
mUseUsername.setVisibility(View.VISIBLE);
mUseUsername.setOnCheckedChangeListener(this);
mPassword.addTextChangedListener(this);
mForgotPassword.setText(
Html.fromHtml(
"<a href=\""
+ getString(R.string.recover_password_link)
+ "\"'>"
+ getString(R.string.forgot_password)
+ "</a>"));
mForgotPassword.setMovementMethod(LinkMovementMethod.getInstance());
}
// Hide mPhone number and display mUsername/email/mPassword
if (!getResources().getBoolean(R.bool.use_phone_number_validation)) {
mPhoneNumberLayout.setVisibility(View.GONE);
mUseUsername.setVisibility(View.GONE);
mUsernameLayout.setVisibility(View.VISIBLE);
mPasswordLayout.setVisibility(View.VISIBLE);
}
// When we come from generic mLogin fragment
mUsername = getArguments().getString("Username");
mPwd = getArguments().getString("Password");
if (mUsername != null && mPwd != null) {
mUseUsername.setChecked(true);
onCheckedChanged(mUseUsername, true);
mLogin.setText(mUsername);
mPassword.setText(mPwd);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mAccountCreator.setLanguage(Locale.getDefault().toLanguageTag());
}
addPhoneNumberHandler(mDialCode);
addPhoneNumberHandler(mPhoneNumberEdit);
return view;
}
private void linphoneLogIn() {
if (mLogin.getText() == null
|| mLogin.length() == 0
|| mPassword.getText() == null
|| mPassword.length() == 0) {
LinphoneUtils.displayErrorAlert(
getString(R.string.first_launch_no_login_password),
AssistantActivity.instance());
mApply.setEnabled(true);
return;
}
mAccountCreator.setUsername(mLogin.getText().toString());
mAccountCreator.setPassword(mPassword.getText().toString());
mAccountCreator.setDomain(getString(R.string.default_domain));
mAccountCreator.isAccountExist();
}
private int getPhoneNumberStatus() {
return mAccountCreator.setPhoneNumber(
mPhoneNumberEdit.getText().toString(), LinphoneUtils.getCountryCode(mDialCode));
}
private void addPhoneNumberHandler(final EditText field) {
field.addTextChangedListener(
new TextWatcher() {
public void afterTextChanged(Editable s) {
if (field.equals(mDialCode)) {
DialPlan c =
AssistantActivity.instance()
.getCountryListAdapter()
.getCountryFromCountryCode(
mDialCode.getText().toString());
if (c != null) {
AssistantActivity.instance().country = c;
mSelectCountry.setText(c.getCountry());
} else {
mSelectCountry.setText(R.string.select_your_country);
}
}
}
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int count, int after) {
onTextChanged2();
}
});
}
@Override
public void onResume() {
super.onResume();
mRecoverAccount = mUseUsername == null || !mUseUsername.isChecked();
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.assistant_apply) {
mApply.setEnabled(false);
if (mRecoverAccount) {
recoverAccount();
} else {
linphoneLogIn();
}
} else if (id == R.id.info_phone_number) {
new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.phone_number_info_title))
.setMessage(getString(R.string.phone_number_link_info_content))
.show();
} else if (id == R.id.select_country) {
AssistantActivity.instance().displayCountryChooser();
}
}
private void recoverAccount() {
if (mPhoneNumberEdit.length() > 0 || mDialCode.length() > 1) {
int status = getPhoneNumberStatus();
boolean isOk = status == AccountCreator.PhoneNumberStatus.Ok.toInt();
if (isOk) {
mAccountCreator.isAliasUsed();
} else {
mApply.setEnabled(true);
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForPhoneNumberStatus(status),
AssistantActivity.instance());
LinphoneUtils.displayError(
isOk, mPhoneNumberError, LinphoneUtils.errorForPhoneNumberStatus(status));
}
} else {
mApply.setEnabled(true);
LinphoneUtils.displayErrorAlert(
getString(R.string.assistant_create_account_part_1),
AssistantActivity.instance());
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
private void onTextChanged2() {
int status = getPhoneNumberStatus();
boolean isOk = status == AccountCreator.PhoneNumberStatus.Ok.toInt();
LinphoneUtils.displayError(
isOk, mPhoneNumberError, LinphoneUtils.errorForPhoneNumberStatus(status));
if (!isOk) {
if ((1 == (status & AccountCreator.PhoneNumberStatus.InvalidCountryCode.toInt()))) {
mDialCode.setBackgroundResource(R.drawable.resizable_textfield_error);
mPhoneNumberEdit.setBackgroundResource(R.drawable.resizable_textfield);
} else {
mDialCode.setBackgroundResource(R.drawable.resizable_textfield);
mPhoneNumberEdit.setBackgroundResource(R.drawable.resizable_textfield_error);
}
} else {
mAccountCreator.setPhoneNumber(
mPhoneNumberEdit.getText().toString(), mDialCode.getText().toString());
mDialCode.setBackgroundResource(R.drawable.resizable_textfield);
mPhoneNumberEdit.setBackgroundResource(R.drawable.resizable_textfield);
}
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
onTextChanged2();
}
@Override
public void afterTextChanged(Editable s) {}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.getId() == R.id.use_username) {
if (isChecked) {
mUsernameLayout.setVisibility(View.VISIBLE);
mPasswordLayout.setVisibility(View.VISIBLE);
mPhoneNumberEdit.setVisibility(EditText.GONE);
mPhoneNumberLayout.setVisibility(LinearLayout.GONE);
mMessagePhoneNumber.setText(getString(R.string.assistant_linphone_login_desc));
mRecoverAccount = false;
} else {
mUsernameLayout.setVisibility(View.GONE);
mPasswordLayout.setVisibility(View.GONE);
mPhoneNumberEdit.setVisibility(EditText.VISIBLE);
mPhoneNumberLayout.setVisibility(LinearLayout.VISIBLE);
mMessagePhoneNumber.setText(getString(R.string.assistant_create_account_part_1));
mRecoverAccount = true;
}
}
}
@Override
public void onIsAccountExist(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {
if (AssistantActivity.instance() == null) {
mApply.setEnabled(true);
return;
}
if (status.equals(AccountCreator.Status.AccountExist)
|| status.equals(AccountCreator.Status.AccountExistWithAlias)) {
AssistantActivity.instance().linphoneLogIn(accountCreator);
} else {
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForStatus(status), AssistantActivity.instance());
}
mApply.setEnabled(true);
}
@Override
public void onCreateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onLinkAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onActivateAlias(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAccountActivated(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onRecoverAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {
if (AssistantActivity.instance() == null) {
mApply.setEnabled(true);
return;
}
if (status.equals(AccountCreator.Status.ServerError)) {
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForStatus(AccountCreator.Status.RequestFailed),
AssistantActivity.instance());
mApply.setEnabled(true);
} else {
AssistantActivity.instance()
.displayAssistantCodeConfirm(
accountCreator.getUsername(),
mPhoneNumberEdit.getText().toString(),
LinphoneUtils.getCountryCode(mDialCode),
true);
}
}
@Override
public void onIsAccountLinked(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
@Override
public void onIsAliasUsed(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {
if (AssistantActivity.instance() == null) {
mApply.setEnabled(true);
return;
}
if (status.equals(AccountCreator.Status.AliasIsAccount)
|| status.equals(AccountCreator.Status.AliasExist)) {
accountCreator.recoverAccount();
} else {
mApply.setEnabled(true);
LinphoneUtils.displayErrorAlert(
LinphoneUtils.errorForStatus(status), AssistantActivity.instance());
}
}
@Override
public void onUpdateAccount(
AccountCreator accountCreator, AccountCreator.Status status, String resp) {}
}

View file

@ -0,0 +1,125 @@
package org.linphone.assistant;
/*
LoginFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Toast;
import org.linphone.R;
import org.linphone.core.TransportType;
public class LoginFragment extends Fragment implements OnClickListener, TextWatcher {
private EditText mLogin, mUserid, mPassword, mDomain, mDisplayName;
private RadioGroup mTransports;
private Button mApply;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.assistant_login, container, false);
mLogin = view.findViewById(R.id.assistant_username);
mLogin.addTextChangedListener(this);
mDisplayName = view.findViewById(R.id.assistant_display_name);
mDisplayName.addTextChangedListener(this);
mUserid = view.findViewById(R.id.assistant_userid);
mUserid.addTextChangedListener(this);
mPassword = view.findViewById(R.id.assistant_password);
mPassword.addTextChangedListener(this);
mDomain = view.findViewById(R.id.assistant_domain);
mDomain.addTextChangedListener(this);
mTransports = view.findViewById(R.id.assistant_transports);
mApply = view.findViewById(R.id.assistant_apply);
mApply.setEnabled(false);
mApply.setOnClickListener(this);
return view;
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.assistant_apply) {
if (mLogin.getText() == null
|| mLogin.length() == 0
|| mPassword.getText() == null
|| mPassword.length() == 0
|| mDomain.getText() == null
|| mDomain.length() == 0) {
Toast.makeText(
getActivity(),
getString(R.string.first_launch_no_login_password),
Toast.LENGTH_LONG)
.show();
return;
}
TransportType transport;
if (mTransports.getCheckedRadioButtonId() == R.id.transport_udp) {
transport = TransportType.Udp;
} else {
if (mTransports.getCheckedRadioButtonId() == R.id.transport_tcp) {
transport = TransportType.Tcp;
} else {
transport = TransportType.Tls;
}
}
if (mDomain.getText().toString().compareTo(getString(R.string.default_domain)) == 0) {
AssistantActivity.instance()
.displayLoginLinphone(
mLogin.getText().toString(), mPassword.getText().toString());
} else {
AssistantActivity.instance()
.genericLogIn(
mLogin.getText().toString(),
mUserid.getText().toString(),
mPassword.getText().toString(),
mDisplayName.getText().toString(),
null,
mDomain.getText().toString(),
transport);
}
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mApply.setEnabled(
!mLogin.getText().toString().isEmpty()
&& !mPassword.getText().toString().isEmpty()
&& !mDomain.getText().toString().isEmpty());
}
@Override
public void afterTextChanged(Editable s) {}
}

View file

@ -0,0 +1,99 @@
package org.linphone.assistant;
/*
QrCodeFragment.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration;
public class QrCodeFragment extends Fragment {
private TextureView mQrcodeView;
private CoreListenerStub mListener;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.qrcode, container, false);
mQrcodeView = view.findViewById(R.id.qrcodeCaptureSurface);
LinphoneManager.getLc().setNativePreviewWindowId(mQrcodeView);
mListener =
new CoreListenerStub() {
@Override
public void onQrcodeFound(Core lc, String result) {
enableQrcodeReader(false);
AssistantActivity.instance().displayRemoteProvisioning(result);
}
};
return view;
}
private void enableQrcodeReader(boolean enable) {
LinphoneManager.getLc().enableQrcodeVideoPreview(enable);
LinphoneManager.getLc().enableVideoPreview(enable);
if (enable) {
LinphoneManager.getLc().addListener(mListener);
} else {
LinphoneManager.getLc().removeListener(mListener);
}
}
private void setBackCamera() {
int camId = 0;
AndroidCameraConfiguration.AndroidCamera[] cameras =
AndroidCameraConfiguration.retrieveCameras();
for (AndroidCameraConfiguration.AndroidCamera androidCamera : cameras) {
if (!androidCamera.frontFacing) camId = androidCamera.id;
}
String[] devices = LinphoneManager.getLc().getVideoDevicesList();
String newDevice = devices[camId];
LinphoneManager.getLc().setVideoDevice(newDevice);
}
private void launchQrcodeReader() {
setBackCamera();
enableQrcodeReader(true);
}
@Override
public void onResume() {
launchQrcodeReader();
super.onResume();
}
@Override
public void onPause() {
enableQrcodeReader(false);
// setBackCamera(false);
super.onPause();
}
}

View file

@ -0,0 +1,217 @@
package org.linphone.assistant;
/*
RemoteProvisioningActivity.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import org.linphone.LinphoneLauncherActivity;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.core.ConfiguringState;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.ThemableActivity;
public class RemoteProvisioningActivity extends ThemableActivity {
private final Handler mHandler = new Handler();
private String mConfigUriParam = null;
private ProgressBar mSpinner;
private CoreListenerStub mListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.remote_provisioning);
mSpinner = findViewById(R.id.spinner);
mListener =
new CoreListenerStub() {
@Override
public void onConfiguringStatus(
Core lc, final ConfiguringState state, String message) {
if (mSpinner != null) mSpinner.setVisibility(View.GONE);
if (state == ConfiguringState.Successful) {
goToLinphoneActivity();
} else if (state == ConfiguringState.Failed) {
Toast.makeText(
RemoteProvisioningActivity.this,
R.string.remote_provisioning_failure,
Toast.LENGTH_LONG)
.show();
}
}
};
}
@Override
protected void onResume() {
super.onResume();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
}
LinphonePreferences.instance().setContext(this);
checkIntentForConfigUri(getIntent());
}
@Override
protected void onPause() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
super.onPause();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
checkIntentForConfigUri(intent);
}
private void checkIntentForConfigUri(final Intent intent) {
new Thread(
new Runnable() {
@Override
public void run() {
Uri openUri = intent.getData();
if (openUri != null) {
// We expect something like
// linphone-config://http://linphone.org/config.xml
mConfigUriParam =
openUri.getEncodedSchemeSpecificPart()
.substring(2); // Removes the linphone-config://
try {
mConfigUriParam =
URLDecoder.decode(mConfigUriParam, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(e);
}
Log.d("Using config uri: " + mConfigUriParam);
}
if (mConfigUriParam == null) {
if (!LinphonePreferences.instance()
.isFirstRemoteProvisioning()) {
mHandler.post(
new Runnable() {
@Override
public void run() {
goToLinphoneActivity();
}
});
} else if (!getResources()
.getBoolean(
R.bool.forbid_app_usage_until_remote_provisioning_completed)) {
// Show this view for a few seconds then go to the dialer
mHandler.postDelayed(
new Runnable() {
@Override
public void run() {
goToLinphoneActivity();
}
},
1500);
} // else we do nothing if there is no config uri parameter and
// if user not allowed to leave this screen
} else {
if (getResources()
.getBoolean(
R.bool.display_confirmation_popup_after_first_configuration)
&& !LinphonePreferences.instance()
.isFirstRemoteProvisioning()) {
mHandler.post(
new Runnable() {
@Override
public void run() {
displayDialogConfirmation();
}
});
} else {
mHandler.post(
new Runnable() {
@Override
public void run() {
setRemoteProvisioningAddressAndRestart(
mConfigUriParam);
}
});
}
}
}
})
.start();
}
private void displayDialogConfirmation() {
new AlertDialog.Builder(RemoteProvisioningActivity.this)
.setTitle(getString(R.string.remote_provisioning_again_title))
.setMessage(getString(R.string.remote_provisioning_again_message))
.setPositiveButton(
R.string.accept,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setRemoteProvisioningAddressAndRestart(mConfigUriParam);
}
})
.setNegativeButton(
R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
goToLinphoneActivity();
}
})
.show();
}
private void setRemoteProvisioningAddressAndRestart(final String configUri) {
if (mSpinner != null) mSpinner.setVisibility(View.VISIBLE);
LinphonePreferences.instance().setContext(this); // Needed, else the next call will crash
LinphonePreferences.instance().setRemoteProvisioningUrl(configUri);
LinphoneManager.getLc().getConfig().sync();
LinphoneManager.getInstance().restartCore();
}
private void goToLinphoneActivity() {
if (LinphoneService.isReady()) {
LinphoneService.instance()
.setActivityToLaunchOnIncomingReceived("org.linphone.LinphoneLauncherActivity");
// finish(); // To prevent the user to come back to this page using back button
startActivity(new Intent().setClass(this, LinphoneLauncherActivity.class));
} else {
finish();
}
}
}

View file

@ -28,25 +28,30 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphonePreferences;
import org.linphone.R; import org.linphone.R;
import org.linphone.settings.LinphonePreferences;
public class RemoteProvisioningFragment extends Fragment implements OnClickListener, TextWatcher { public class RemoteProvisioningFragment extends Fragment implements OnClickListener, TextWatcher {
private EditText remoteProvisioningUrl; private EditText mRemoteProvisioningUrl;
private Button apply; private Button mApply, mQrcode;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.assistant_remote_provisioning, container, false); View view = inflater.inflate(R.layout.assistant_remote_provisioning, container, false);
remoteProvisioningUrl = view.findViewById(R.id.assistant_remote_provisioning_url); mRemoteProvisioningUrl = view.findViewById(R.id.assistant_remote_provisioning_url);
remoteProvisioningUrl.addTextChangedListener(this); mRemoteProvisioningUrl.addTextChangedListener(this);
apply = view.findViewById(R.id.assistant_apply); mQrcode = view.findViewById(R.id.assistant_qrcode);
apply.setEnabled(false); mQrcode.setOnClickListener(this);
apply.setOnClickListener(this); mApply = view.findViewById(R.id.assistant_apply);
mApply.setEnabled(false);
mApply.setOnClickListener(this);
if (getArguments() != null && !getArguments().getString("RemoteUrl").isEmpty()) {
mRemoteProvisioningUrl.setText(getArguments().getString("RemoteUrl"));
}
return view; return view;
} }
@ -56,27 +61,25 @@ public class RemoteProvisioningFragment extends Fragment implements OnClickListe
int id = v.getId(); int id = v.getId();
if (id == R.id.assistant_apply) { if (id == R.id.assistant_apply) {
String url = remoteProvisioningUrl.getText().toString(); String url = mRemoteProvisioningUrl.getText().toString();
AssistantActivity.instance().displayRemoteProvisioningInProgressDialog(); AssistantActivity.instance().displayRemoteProvisioningInProgressDialog();
LinphonePreferences.instance().setRemoteProvisioningUrl(url); LinphonePreferences.instance().setRemoteProvisioningUrl(url);
LinphoneManager.getLc().getConfig().sync(); LinphoneManager.getLc().getConfig().sync();
LinphoneManager.getInstance().restartCore(); LinphoneManager.getInstance().restartCore();
AssistantActivity.instance().setCoreListener(); AssistantActivity.instance().setCoreListener();
} else if (id == R.id.assistant_qrcode) {
AssistantActivity.instance().displayQRCodeReader();
} }
} }
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
}
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
apply.setEnabled(!remoteProvisioningUrl.getText().toString().isEmpty()); mApply.setEnabled(!mRemoteProvisioningUrl.getText().toString().isEmpty());
} }
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {}
}
} }

View file

@ -25,19 +25,19 @@ import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphonePreferences;
import org.linphone.R; import org.linphone.R;
import org.linphone.core.ConfiguringState; import org.linphone.core.ConfiguringState;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub; import org.linphone.core.CoreListenerStub;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.ThemableActivity;
import org.linphone.xmlrpc.XmlRpcHelper; import org.linphone.xmlrpc.XmlRpcHelper;
import org.linphone.xmlrpc.XmlRpcListenerBase; import org.linphone.xmlrpc.XmlRpcListenerBase;
public class RemoteProvisioningLoginActivity extends Activity implements OnClickListener { public class RemoteProvisioningLoginActivity extends ThemableActivity implements OnClickListener {
private EditText login, password, domain; private EditText mLogin, mPassword, mDomain;
private Button connect; private Button mConnect;
private CoreListenerStub mListener; private CoreListenerStub mListener;
@Override @Override
@ -45,53 +45,62 @@ public class RemoteProvisioningLoginActivity extends Activity implements OnClick
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.assistant_remote_provisioning_login); setContentView(R.layout.assistant_remote_provisioning_login);
login = findViewById(R.id.assistant_username); mLogin = findViewById(R.id.assistant_username);
password = findViewById(R.id.assistant_password); mPassword = findViewById(R.id.assistant_password);
domain = findViewById(R.id.assistant_domain); mDomain = findViewById(R.id.assistant_domain);
connect = findViewById(R.id.assistant_connect); mConnect = findViewById(R.id.assistant_connect);
connect.setOnClickListener(this); mConnect.setOnClickListener(this);
String defaultDomain = getIntent().getStringExtra("Domain"); String defaultDomain = getIntent().getStringExtra("Domain");
if (defaultDomain != null) { if (defaultDomain != null) {
domain.setText(defaultDomain); mDomain.setText(defaultDomain);
domain.setEnabled(false); mDomain.setEnabled(false);
} }
mListener = new CoreListenerStub() { mListener =
@Override new CoreListenerStub() {
public void onConfiguringStatus(Core lc, final ConfiguringState state, String message) { @Override
if (state == ConfiguringState.Successful) { public void onConfiguringStatus(
//TODO Core lc, final ConfiguringState state, String message) {
} else if (state == ConfiguringState.Failed) { if (state == ConfiguringState.Successful) {
Toast.makeText(RemoteProvisioningLoginActivity.this, R.string.remote_provisioning_failure, Toast.LENGTH_LONG).show(); // TODO
} } else if (state == ConfiguringState.Failed) {
} Toast.makeText(
}; RemoteProvisioningLoginActivity.this,
R.string.remote_provisioning_failure,
Toast.LENGTH_LONG)
.show();
}
}
};
} }
private void cancelWizard(boolean bypassCheck) { private void cancelWizard() {
if (bypassCheck || getResources().getBoolean(R.bool.allow_cancel_remote_provisioning_login_activity)) { if (getResources().getBoolean(R.bool.allow_cancel_remote_provisioning_login_activity)) {
LinphonePreferences.instance().disableProvisioningLoginView(); LinphonePreferences.instance().disableProvisioningLoginView();
setResult(bypassCheck ? Activity.RESULT_OK : Activity.RESULT_CANCELED); setResult(Activity.RESULT_CANCELED);
finish(); finish();
} }
} }
private boolean storeAccount(String username, String password, String domain) { private void storeAccount(String username, String password, String domain) {
XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(); XmlRpcHelper xmlRpcHelper = new XmlRpcHelper();
xmlRpcHelper.getRemoteProvisioningFilenameAsync(new XmlRpcListenerBase() { xmlRpcHelper.getRemoteProvisioningFilenameAsync(
@Override new XmlRpcListenerBase() {
public void onRemoteProvisioningFilenameSent(String result) { @Override
LinphonePreferences.instance().setRemoteProvisioningUrl(result); public void onRemoteProvisioningFilenameSent(String result) {
LinphoneManager.getInstance().restartCore(); LinphonePreferences.instance().setRemoteProvisioningUrl(result);
} LinphoneManager.getInstance().restartCore();
}, username.toString(), password.toString(), domain.toString()); }
},
username,
password,
domain);
LinphonePreferences.instance().firstLaunchSuccessful(); LinphonePreferences.instance().firstLaunchSuccessful();
setResult(Activity.RESULT_OK); setResult(Activity.RESULT_OK);
finish(); finish();
return true;
} }
@Override @Override
@ -117,15 +126,18 @@ public class RemoteProvisioningLoginActivity extends Activity implements OnClick
int id = v.getId(); int id = v.getId();
if (id == R.id.cancel) { if (id == R.id.cancel) {
cancelWizard(false); cancelWizard();
} }
if (id == R.id.assistant_connect) { if (id == R.id.assistant_connect) {
storeAccount(login.getText().toString(), password.getText().toString(), domain.getText().toString()); storeAccount(
mLogin.getText().toString(),
mPassword.getText().toString(),
mDomain.getText().toString());
} }
} }
@Override @Override
public void onBackPressed() { public void onBackPressed() {
cancelWizard(false); cancelWizard();
} }
} }

View file

@ -25,39 +25,38 @@ import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import org.linphone.R; import org.linphone.R;
public class WelcomeFragment extends Fragment implements OnClickListener { public class WelcomeFragment extends Fragment implements OnClickListener {
private Button createAccount, logLinphoneAccount, logGenericAccount, remoteProvisioning; private Button mCreateAccount, mLogLinphoneAccount, mLogGenericAccount, mRemoteProvisioning;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.assistant_welcome, container, false); View view = inflater.inflate(R.layout.assistant_welcome, container, false);
createAccount = view.findViewById(R.id.create_account); mCreateAccount = view.findViewById(R.id.create_account);
createAccount.setOnClickListener(this); mCreateAccount.setOnClickListener(this);
logLinphoneAccount = view.findViewById(R.id.login_linphone); mLogLinphoneAccount = view.findViewById(R.id.login_linphone);
if (getResources().getBoolean(R.bool.hide_linphone_accounts_in_assistant)) { if (getResources().getBoolean(R.bool.hide_linphone_accounts_in_assistant)) {
logLinphoneAccount.setVisibility(View.GONE); mLogLinphoneAccount.setVisibility(View.GONE);
} else { } else {
logLinphoneAccount.setOnClickListener(this); mLogLinphoneAccount.setOnClickListener(this);
} }
logGenericAccount = view.findViewById(R.id.login_generic); mLogGenericAccount = view.findViewById(R.id.login_generic);
if (getResources().getBoolean(R.bool.hide_generic_accounts_in_assistant)) { if (getResources().getBoolean(R.bool.hide_generic_accounts_in_assistant)) {
logGenericAccount.setVisibility(View.GONE); mLogGenericAccount.setVisibility(View.GONE);
} else { } else {
logGenericAccount.setOnClickListener(this); mLogGenericAccount.setOnClickListener(this);
} }
remoteProvisioning = view.findViewById(R.id.remote_provisioning); mRemoteProvisioning = view.findViewById(R.id.remote_provisioning);
if (getResources().getBoolean(R.bool.hide_remote_provisioning_in_assistant)) { if (getResources().getBoolean(R.bool.hide_remote_provisioning_in_assistant)) {
remoteProvisioning.setVisibility(View.GONE); mRemoteProvisioning.setVisibility(View.GONE);
} else { } else {
remoteProvisioning.setOnClickListener(this); mRemoteProvisioning.setOnClickListener(this);
} }
return view; return view;
@ -73,7 +72,7 @@ public class WelcomeFragment extends Fragment implements OnClickListener {
} else if (id == R.id.create_account) { } else if (id == R.id.create_account) {
AssistantActivity.instance().displayCreateAccount(); AssistantActivity.instance().displayCreateAccount();
} else if (id == R.id.remote_provisioning) { } else if (id == R.id.remote_provisioning) {
AssistantActivity.instance().displayRemoteProvisioning(); AssistantActivity.instance().displayRemoteProvisioning("");
} }
} }
} }

View file

@ -1,4 +1,4 @@
package org.linphone; package org.linphone.call;
/* /*
BandwithManager.java BandwithManager.java
@ -20,23 +20,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import org.linphone.core.CallParams; import org.linphone.core.CallParams;
import org.linphone.core.Core;
public class BandwidthManager { public class BandwidthManager {
public static final int HIGH_RESOLUTION = 0; private static final int HIGH_RESOLUTION = 0;
public static final int LOW_RESOLUTION = 1; private static final int LOW_RESOLUTION = 1;
public static final int LOW_BANDWIDTH = 2; private static final int LOW_BANDWIDTH = 2;
private static BandwidthManager instance; private static BandwidthManager sInstance;
private int currentProfile = HIGH_RESOLUTION;
public static final synchronized BandwidthManager getInstance() {
if (instance == null) instance = new BandwidthManager();
return instance;
}
private final int currentProfile = HIGH_RESOLUTION;
private BandwidthManager() { private BandwidthManager() {
// FIXME register a listener on NetworkManager to get notified of network state // FIXME register a listener on NetworkManager to get notified of network state
@ -45,8 +38,12 @@ public class BandwidthManager {
// FIXME initially get those values // FIXME initially get those values
} }
public static synchronized BandwidthManager getInstance() {
if (sInstance == null) sInstance = new BandwidthManager();
return sInstance;
}
public void updateWithProfileSettings(Core lc, CallParams callParams) { public void updateWithProfileSettings(CallParams callParams) {
if (callParams != null) { // in call if (callParams != null) { // in call
// Update video parm if // Update video parm if
if (!isVideoPossible()) { // NO VIDEO if (!isVideoPossible()) { // NO VIDEO
@ -59,7 +56,7 @@ public class BandwidthManager {
} }
} }
public boolean isVideoPossible() { private boolean isVideoPossible() {
return currentProfile != LOW_BANDWIDTH; return currentProfile != LOW_BANDWIDTH;
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -24,30 +24,23 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.linphone.R; import org.linphone.R;
public class CallAudioFragment extends Fragment { public class CallAudioFragment extends Fragment {
private CallActivity incallActvityInstance;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.audio, container, false); return inflater.inflate(R.layout.audio, container, false);
return view;
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
incallActvityInstance = (CallActivity) getActivity(); CallActivity incallActvityInstance = (CallActivity) getActivity();
if (incallActvityInstance != null) { if (incallActvityInstance != null) {
incallActvityInstance.bindAudioFragment(this); incallActvityInstance.bindAudioFragment(this);
} // Just to be sure we have incall controls
// Just to be sure we have incall controls
if (incallActvityInstance != null) {
incallActvityInstance.removeCallbacks(); incallActvityInstance.removeCallbacks();
} }
} }

View file

@ -0,0 +1,324 @@
package org.linphone.call;
/*
CallIncomingActivity.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.Manifest;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import java.util.ArrayList;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.Call.State;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneGenericActivity;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.CallIncomingAnswerButton;
import org.linphone.views.CallIncomingButtonListener;
import org.linphone.views.CallIncomingDeclineButton;
import org.linphone.views.ContactAvatar;
public class CallIncomingActivity extends LinphoneGenericActivity {
private static CallIncomingActivity sInstance;
private TextView mName, mNumber;
private ImageView mAcceptIcon;
private CallIncomingAnswerButton mAccept;
private CallIncomingDeclineButton mDecline;
private Call mCall;
private CoreListenerStub mListener;
private boolean mAlreadyAcceptedOrDeniedCall;
private KeyguardManager mKeyguardManager;
private TextureView mVideoDisplay;
public static CallIncomingActivity instance() {
return sInstance;
}
public static boolean isInstanciated() {
return sInstance != null;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getBoolean(R.bool.orientation_portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
Compatibility.setShowWhenLocked(this, true);
Compatibility.setTurnScreenOn(this, true);
setContentView(R.layout.call_incoming);
mName = findViewById(R.id.contact_name);
mNumber = findViewById(R.id.contact_number);
mVideoDisplay = findViewById(R.id.videoSurface);
mAccept = findViewById(R.id.answer_button);
mDecline = findViewById(R.id.decline_button);
mAcceptIcon = findViewById(R.id.acceptIcon);
lookupCurrentCall();
if (LinphonePreferences.instance() != null
&& mCall != null
&& mCall.getRemoteParams() != null
&& LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()
&& mCall.getRemoteParams().videoEnabled()) {
mAcceptIcon.setImageResource(R.drawable.call_video_start);
}
mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
boolean doNotUseSliders =
getResources()
.getBoolean(
R.bool.do_not_use_sliders_to_answer_hangup_call_if_phone_unlocked);
if (doNotUseSliders && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
mAccept.setSliderMode(false);
mDecline.setSliderMode(false);
} else {
mAccept.setSliderMode(true);
mDecline.setSliderMode(true);
mAccept.setDeclineButton(mDecline);
mDecline.setAnswerButton(mAccept);
}
mAccept.setListener(
new CallIncomingButtonListener() {
@Override
public void onAction() {
answer();
}
});
mDecline.setListener(
new CallIncomingButtonListener() {
@Override
public void onAction() {
decline();
}
});
mListener =
new CoreListenerStub() {
@Override
public void onCallStateChanged(
Core lc, Call call, State state, String message) {
if (call == mCall && State.End == state) {
finish();
} else if (state == State.Connected) {
startActivity(
new Intent(CallIncomingActivity.this, CallActivity.class));
} else if (state == State.StreamsRunning) {
Log.e(
"CallIncommingActivity - onCreate - State.StreamsRunning - speaker = "
+ LinphoneManager.getInstance().isSpeakerEnabled());
// The following should not be needed except some devices need it (e.g.
// Galaxy S).
LinphoneManager.getInstance()
.enableSpeaker(
LinphoneManager.getInstance().isSpeakerEnabled());
}
}
};
sInstance = this;
}
@Override
protected void onResume() {
super.onResume();
sInstance = this;
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
}
mAlreadyAcceptedOrDeniedCall = false;
mCall = null;
// Only one call ringing at a time is allowed
lookupCurrentCall();
if (mCall == null) {
// The incoming call no longer exists.
Log.d("Couldn't find incoming call");
finish();
return;
}
Address address = mCall.getRemoteAddress();
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(address);
if (contact != null) {
ContactAvatar.displayAvatar(contact, findViewById(R.id.avatar_layout), true);
mName.setText(contact.getFullName());
} else {
String displayName = LinphoneUtils.getAddressDisplayName(address);
ContactAvatar.displayAvatar(displayName, findViewById(R.id.avatar_layout), true);
mName.setText(displayName);
}
mNumber.setText(address.asStringUriOnly());
if (LinphonePreferences.instance().acceptIncomingEarlyMedia()) {
if (mCall.getCurrentParams().videoEnabled()) {
findViewById(R.id.avatar_layout).setVisibility(View.GONE);
mCall.getCore().setNativeVideoWindowId(mVideoDisplay);
}
}
}
@Override
protected void onStart() {
super.onStart();
checkAndRequestCallPermissions();
}
@Override
protected void onPause() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
sInstance = null;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (LinphoneManager.isInstanciated()
&& (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME)) {
LinphoneManager.getLc().terminateCall(mCall);
finish();
}
return super.onKeyDown(keyCode, event);
}
private void lookupCurrentCall() {
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) {
for (Call call : LinphoneManager.getLc().getCalls()) {
if (State.IncomingReceived == call.getState()
|| State.IncomingEarlyMedia == call.getState()) {
mCall = call;
break;
}
}
}
}
private void decline() {
if (mAlreadyAcceptedOrDeniedCall) {
return;
}
mAlreadyAcceptedOrDeniedCall = true;
LinphoneManager.getLc().terminateCall(mCall);
finish();
}
private void answer() {
if (mAlreadyAcceptedOrDeniedCall) {
return;
}
mAlreadyAcceptedOrDeniedCall = true;
if (!LinphoneManager.getInstance().acceptCall(mCall)) {
// the above method takes care of Samsung Galaxy S
Toast.makeText(this, R.string.couldnt_accept_call, Toast.LENGTH_LONG).show();
} else {
if (!LinphoneActivity.isInstanciated()) {
return;
}
LinphoneManager.getInstance().routeAudioToReceiver();
LinphoneActivity.instance().startIncallActivity();
}
}
private void checkAndRequestCallPermissions() {
ArrayList<String> permissionsList = new ArrayList<>();
int recordAudio =
getPackageManager()
.checkPermission(Manifest.permission.RECORD_AUDIO, getPackageName());
Log.i(
"[Permission] Record audio permission is "
+ (recordAudio == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
int camera =
getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName());
Log.i(
"[Permission] Camera permission is "
+ (camera == PackageManager.PERMISSION_GRANTED ? "granted" : "denied"));
if (recordAudio != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for record audio");
permissionsList.add(Manifest.permission.RECORD_AUDIO);
}
if (LinphonePreferences.instance().shouldInitiateVideoCall()
|| LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()) {
if (camera != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for camera");
permissionsList.add(Manifest.permission.CAMERA);
}
}
if (permissionsList.size() > 0) {
String[] permissions = new String[permissionsList.size()];
permissions = permissionsList.toArray(permissions);
ActivityCompat.requestPermissions(this, permissions, 0);
}
}
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
for (int i = 0; i < permissions.length; i++) {
Log.i(
"[Permission] "
+ permissions[i]
+ " is "
+ (grantResults[i] == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
}
}
}

View file

@ -19,39 +19,51 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import org.linphone.BandwidthManager;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.CallParams; import org.linphone.core.CallParams;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.CoreException; import org.linphone.core.MediaEncryption;
import org.linphone.mediastream.Log; import org.linphone.core.tools.Log;
import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils;
/** /** Handle call updating, reinvites. */
* Handle call updating, reinvites.
*/
public class CallManager { public class CallManager {
private static CallManager instance; private static CallManager sInstance;
private CallManager() { public static synchronized CallManager getInstance() {
if (sInstance == null) sInstance = new CallManager();
return sInstance;
} }
public static final synchronized CallManager getInstance() { private CallManager() {}
if (instance == null) instance = new CallManager();
return instance;
}
private BandwidthManager bm() { private BandwidthManager getBandwidthManager() {
return BandwidthManager.getInstance(); return BandwidthManager.getInstance();
} }
public void inviteAddress(Address lAddress, boolean videoEnabled, boolean lowBandwidth) throws CoreException { public void inviteAddress(Address lAddress, boolean forceZRTP) {
boolean isLowBandwidthConnection =
!LinphoneUtils.isHighBandwidthConnection(
LinphoneService.instance().getApplicationContext());
inviteAddress(lAddress, false, isLowBandwidthConnection, forceZRTP);
}
public void inviteAddress(Address lAddress) {
inviteAddress(lAddress, false);
}
public void inviteAddress(
Address lAddress, boolean videoEnabled, boolean lowBandwidth, boolean forceZRTP) {
Core lc = LinphoneManager.getLc(); Core lc = LinphoneManager.getLc();
CallParams params = lc.createCallParams(null); CallParams params = lc.createCallParams(null);
bm().updateWithProfileSettings(lc, params); getBandwidthManager().updateWithProfileSettings(params);
if (videoEnabled && params.videoEnabled()) { if (videoEnabled && params.videoEnabled()) {
params.enableVideo(true); params.enableVideo(true);
@ -64,13 +76,25 @@ public class CallManager {
Log.d("Low bandwidth enabled in call params"); Log.d("Low bandwidth enabled in call params");
} }
if (forceZRTP) {
params.setMediaEncryption(MediaEncryption.ZRTP);
}
String recordFile =
FileUtils.getCallRecordingFilename(
LinphoneManager.getInstance().getContext(), lAddress);
params.setRecordFile(recordFile);
lc.inviteAddressWithParams(lAddress, params); lc.inviteAddressWithParams(lAddress, params);
} }
public void inviteAddress(Address lAddress, boolean videoEnabled, boolean lowBandwidth) {
inviteAddress(lAddress, videoEnabled, lowBandwidth, false);
}
/** /**
* Add video to a currently running voice only call. * Add video to a currently running voice only call. No re-invite is sent if the current call is
* No re-invite is sent if the current call is already video * already video or if the bandwidth settings are too low.
* or if the bandwidth settings are too low.
* *
* @return if updateCall called * @return if updateCall called
*/ */
@ -85,9 +109,8 @@ public class CallManager {
if (params.videoEnabled()) return false; if (params.videoEnabled()) return false;
// Check if video possible regarding bandwidth limitations // Check if video possible regarding bandwidth limitations
bm().updateWithProfileSettings(lc, params); getBandwidthManager().updateWithProfileSettings(params);
// Abort if not enough bandwidth... // Abort if not enough bandwidth...
if (!params.videoEnabled()) { if (!params.videoEnabled()) {
@ -101,8 +124,8 @@ public class CallManager {
/** /**
* Change the preferred video size used by linphone core. (impact landscape/portrait buffer). * Change the preferred video size used by linphone core. (impact landscape/portrait buffer).
* Update current call, without reinvite. * Update current call, without reinvite. The camera will be restarted when mediastreamer chain
* The camera will be restarted when mediastreamer chain is recreated and setParameters is called. * is recreated and setParameters is called.
*/ */
public void updateCall() { public void updateCall() {
Core lc = LinphoneManager.getLc(); Core lc = LinphoneManager.getLc();
@ -112,8 +135,7 @@ public class CallManager {
return; return;
} }
CallParams params = lc.createCallParams(lCall); CallParams params = lc.createCallParams(lCall);
bm().updateWithProfileSettings(lc, params); getBandwidthManager().updateWithProfileSettings(params);
lc.updateCall(lCall, null); lc.updateCall(lCall, null);
} }
} }

View file

@ -0,0 +1,303 @@
package org.linphone.call;
/*
CallOutgoingActivity.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.Manifest;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import java.util.ArrayList;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.Call.State;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Reason;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneGenericActivity;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class CallOutgoingActivity extends LinphoneGenericActivity implements OnClickListener {
private TextView mName, mNumber;
private ImageView mMicro, mSpeaker, mHangUp;
private Call mCall;
private CoreListenerStub mListener;
private boolean mIsMicMuted, mIsSpeakerEnabled;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getBoolean(R.bool.orientation_portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.call_outgoing);
mName = findViewById(R.id.contact_name);
mNumber = findViewById(R.id.contact_number);
mIsMicMuted = false;
mIsSpeakerEnabled = false;
mMicro = findViewById(R.id.micro);
mMicro.setOnClickListener(this);
mSpeaker = findViewById(R.id.speaker);
mSpeaker.setOnClickListener(this);
mHangUp = findViewById(R.id.outgoing_hang_up);
mHangUp.setOnClickListener(this);
mListener =
new CoreListenerStub() {
@Override
public void onCallStateChanged(
Core lc, Call call, Call.State state, String message) {
if (call == mCall && State.Connected == state) {
if (!LinphoneActivity.isInstanciated()) {
return;
}
LinphoneActivity.instance().startIncallActivity();
return;
} else if (state == State.Error) {
// Convert Core message for internalization
if (call.getErrorInfo().getReason() == Reason.Declined) {
displayCustomToast(
getString(R.string.error_call_declined),
Toast.LENGTH_SHORT);
decline();
} else if (call.getErrorInfo().getReason() == Reason.NotFound) {
displayCustomToast(
getString(R.string.error_user_not_found),
Toast.LENGTH_SHORT);
decline();
} else if (call.getErrorInfo().getReason() == Reason.NotAcceptable) {
displayCustomToast(
getString(R.string.error_incompatible_media),
Toast.LENGTH_SHORT);
decline();
} else if (call.getErrorInfo().getReason() == Reason.Busy) {
displayCustomToast(
getString(R.string.error_user_busy), Toast.LENGTH_SHORT);
decline();
} else if (message != null) {
displayCustomToast(
getString(R.string.error_unknown) + " - " + message,
Toast.LENGTH_SHORT);
decline();
}
} else if (state == State.End) {
// Convert Core message for internalization
if (call.getErrorInfo().getReason() == Reason.Declined) {
displayCustomToast(
getString(R.string.error_call_declined),
Toast.LENGTH_SHORT);
decline();
}
}
if (LinphoneManager.getLc().getCallsNb() == 0) {
finish();
}
}
};
}
@Override
protected void onResume() {
super.onResume();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
}
mCall = null;
// Only one call ringing at a time is allowed
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) {
for (Call call : LinphoneManager.getLc().getCalls()) {
State cstate = call.getState();
if (State.OutgoingInit == cstate
|| State.OutgoingProgress == cstate
|| State.OutgoingRinging == cstate
|| State.OutgoingEarlyMedia == cstate) {
mCall = call;
break;
}
if (State.StreamsRunning == cstate) {
if (!LinphoneActivity.isInstanciated()) {
return;
}
LinphoneActivity.instance().startIncallActivity();
return;
}
}
}
if (mCall == null) {
Log.e("Couldn't find outgoing call");
finish();
return;
}
Address address = mCall.getRemoteAddress();
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(address);
if (contact != null) {
ContactAvatar.displayAvatar(contact, findViewById(R.id.avatar_layout), true);
mName.setText(contact.getFullName());
} else {
String displayName = LinphoneUtils.getAddressDisplayName(address);
ContactAvatar.displayAvatar(displayName, findViewById(R.id.avatar_layout), true);
mName.setText(displayName);
}
mNumber.setText(LinphoneUtils.getDisplayableAddress(address));
}
@Override
protected void onStart() {
super.onStart();
checkAndRequestCallPermissions();
}
@Override
protected void onPause() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
super.onPause();
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.micro) {
mIsMicMuted = !mIsMicMuted;
mMicro.setSelected(mIsMicMuted);
LinphoneManager.getLc().enableMic(!mIsMicMuted);
}
if (id == R.id.speaker) {
mIsSpeakerEnabled = !mIsSpeakerEnabled;
mSpeaker.setSelected(mIsSpeakerEnabled);
LinphoneManager.getInstance().enableSpeaker(mIsSpeakerEnabled);
}
if (id == R.id.outgoing_hang_up) {
decline();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (LinphoneManager.isInstanciated()
&& (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME)) {
LinphoneManager.getLc().terminateCall(mCall);
finish();
}
return super.onKeyDown(keyCode, event);
}
private void displayCustomToast(final String message, final int duration) {
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.toast, (ViewGroup) findViewById(R.id.toastRoot));
TextView toastText = layout.findViewById(R.id.toastMessage);
toastText.setText(message);
final Toast toast = new Toast(getApplicationContext());
toast.setGravity(Gravity.CENTER, 0, 0);
toast.setDuration(duration);
toast.setView(layout);
toast.show();
}
private void decline() {
LinphoneManager.getLc().terminateCall(mCall);
finish();
}
private void checkAndRequestCallPermissions() {
ArrayList<String> permissionsList = new ArrayList<>();
int recordAudio =
getPackageManager()
.checkPermission(Manifest.permission.RECORD_AUDIO, getPackageName());
Log.i(
"[Permission] Record audio permission is "
+ (recordAudio == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
int camera =
getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName());
Log.i(
"[Permission] Camera permission is "
+ (camera == PackageManager.PERMISSION_GRANTED ? "granted" : "denied"));
if (recordAudio != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for record audio");
permissionsList.add(Manifest.permission.RECORD_AUDIO);
}
if (LinphonePreferences.instance().shouldInitiateVideoCall()
|| LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests()) {
if (camera != PackageManager.PERMISSION_GRANTED) {
Log.i("[Permission] Asking for camera");
permissionsList.add(Manifest.permission.CAMERA);
}
}
if (permissionsList.size() > 0) {
String[] permissions = new String[permissionsList.size()];
permissions = permissionsList.toArray(permissions);
ActivityCompat.requestPermissions(this, permissions, 0);
}
}
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
for (int i = 0; i < permissions.length; i++) {
Log.i(
"[Permission] "
+ permissions[i]
+ " is "
+ (grantResults[i] == PackageManager.PERMISSION_GRANTED
? "granted"
: "denied"));
}
}
}

View file

@ -27,41 +27,39 @@ import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener; import android.view.GestureDetector.OnGestureListener;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView; import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.View.OnTouchListener; import android.view.View.OnTouchListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphonePreferences;
import org.linphone.LinphoneService; import org.linphone.LinphoneService;
import org.linphone.LinphoneUtils;
import org.linphone.R; import org.linphone.R;
import org.linphone.compatibility.Compatibility;
import org.linphone.compatibility.CompatibilityScaleGestureDetector; import org.linphone.compatibility.CompatibilityScaleGestureDetector;
import org.linphone.compatibility.CompatibilityScaleGestureListener; import org.linphone.compatibility.CompatibilityScaleGestureListener;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.VideoDefinition; import org.linphone.core.VideoDefinition;
import org.linphone.mediastream.Log; import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
public class CallVideoFragment extends Fragment implements OnGestureListener, OnDoubleTapListener, CompatibilityScaleGestureListener { public class CallVideoFragment extends Fragment
implements OnGestureListener, OnDoubleTapListener, CompatibilityScaleGestureListener {
private TextureView mVideoView; private TextureView mVideoView;
private TextureView mCaptureView; private TextureView mCaptureView;
private GestureDetector mGestureDetector; private GestureDetector mGestureDetector;
private float mZoomFactor = 1.f; private float mZoomFactor = 1.f;
private float mZoomCenterX, mZoomCenterY; private float mZoomCenterX, mZoomCenterY;
private CompatibilityScaleGestureDetector mScaleDetector; private CompatibilityScaleGestureDetector mScaleDetector;
private CallActivity inCallActivity; private CallActivity mInCallActivity;
private int previewX, previewY; private int mPreviewX, mPreviewY;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
// Warning useless because value is ignored and automatically set by new APIs. // Warning useless because value is ignored and automatically set by new APIs.
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view; View view;
if (LinphoneManager.getLc().hasCrappyOpengl()) { if (LinphoneManager.getLc().hasCrappyOpengl()) {
view = inflater.inflate(R.layout.video_no_opengl, container, false); view = inflater.inflate(R.layout.video_no_opengl, container, false);
@ -71,57 +69,64 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
mVideoView = view.findViewById(R.id.videoSurface); mVideoView = view.findViewById(R.id.videoSurface);
mCaptureView = view.findViewById(R.id.videoCaptureSurface); mCaptureView = view.findViewById(R.id.videoCaptureSurface);
LinphoneManager.getLc().setNativeVideoWindowId(mVideoView); LinphoneManager.getLc().setNativeVideoWindowId(mVideoView);
LinphoneManager.getLc().setNativePreviewWindowId(mCaptureView); LinphoneManager.getLc().setNativePreviewWindowId(mCaptureView);
mVideoView.setOnTouchListener(new OnTouchListener() { mVideoView.setOnTouchListener(
public boolean onTouch(View v, MotionEvent event) { new OnTouchListener() {
if (mScaleDetector != null) { public boolean onTouch(View v, MotionEvent event) {
mScaleDetector.onTouchEvent(event); if (mScaleDetector != null) {
} mScaleDetector.onTouchEvent(event);
}
mGestureDetector.onTouchEvent(event); mGestureDetector.onTouchEvent(event);
if (inCallActivity != null) { if (mInCallActivity != null) {
inCallActivity.displayVideoCallControlsIfHidden(); mInCallActivity.displayVideoCallControlsIfHidden();
} }
return true; return true;
} }
}); });
mCaptureView.setOnTouchListener(new OnTouchListener() { mCaptureView.setOnTouchListener(
@Override new OnTouchListener() {
public boolean onTouch(View view, MotionEvent motionEvent) { @Override
switch (motionEvent.getAction()) { public boolean onTouch(View view, MotionEvent motionEvent) {
case MotionEvent.ACTION_DOWN: switch (motionEvent.getAction()) {
previewX = (int) motionEvent.getX(); case MotionEvent.ACTION_DOWN:
previewY = (int) motionEvent.getY(); mPreviewX = (int) motionEvent.getX();
break; mPreviewY = (int) motionEvent.getY();
case MotionEvent.ACTION_MOVE: break;
int x = (int) motionEvent.getX(); case MotionEvent.ACTION_MOVE:
int y = (int) motionEvent.getY(); int x = (int) motionEvent.getX();
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mCaptureView.getLayoutParams(); int y = (int) motionEvent.getY();
lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0); // Clears the rule, as there is no removeRule until API 17. RelativeLayout.LayoutParams lp =
lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0); (RelativeLayout.LayoutParams)
int left = lp.leftMargin + (x - previewX); mCaptureView.getLayoutParams();
int top = lp.topMargin + (y - previewY); lp.addRule(
lp.leftMargin = left; RelativeLayout.ALIGN_PARENT_BOTTOM,
lp.topMargin = top; 0); // Clears the rule, as there is no removeRule until API
view.setLayoutParams(lp); // 17.
break; lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0);
} int left = lp.leftMargin + (x - mPreviewX);
return true; int top = lp.topMargin + (y - mPreviewY);
} lp.leftMargin = left;
}); lp.topMargin = top;
view.setLayoutParams(lp);
break;
}
return true;
}
});
return view; return view;
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
inCallActivity = (CallActivity) getActivity(); mInCallActivity = (CallActivity) getActivity();
if (inCallActivity != null) { if (mInCallActivity != null) {
inCallActivity.bindVideoFragment(this); mInCallActivity.bindVideoFragment(this);
} }
} }
@ -137,30 +142,36 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
DisplayMetrics metrics = new DisplayMetrics(); DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics); getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenHeight = metrics.heightPixels; int screenHeight = metrics.heightPixels;
int maxHeight = screenHeight / 4; // Let's take at most 1/4 of the screen for the camera preview int maxHeight =
screenHeight / 4; // Let's take at most 1/4 of the screen for the camera preview
VideoDefinition videoSize = call.getCurrentParams().getSentVideoDefinition(); // It already takes care of rotation VideoDefinition videoSize =
call.getCurrentParams()
.getSentVideoDefinition(); // It already takes care of rotation
if (videoSize.getWidth() == 0 || videoSize.getHeight() == 0) { if (videoSize.getWidth() == 0 || videoSize.getHeight() == 0) {
Log.w("Couldn't get sent video definition, using default video definition"); Log.w(
"[Video Fragment] Couldn't get sent video definition, using default video definition");
videoSize = lc.getPreferredVideoDefinition(); videoSize = lc.getPreferredVideoDefinition();
} }
int width = videoSize.getWidth(); int width = videoSize.getWidth();
int height = videoSize.getHeight(); int height = videoSize.getHeight();
Log.d("Video height is " + height + ", width is " + width); Log.d("[Video Fragment] Video height is " + height + ", width is " + width);
width = width * maxHeight / height; width = width * maxHeight / height;
height = maxHeight; height = maxHeight;
if (mCaptureView == null) { if (mCaptureView == null) {
Log.e("mCaptureView is null !"); Log.e("[Video Fragment] mCaptureView is null !");
return; return;
} }
RelativeLayout.LayoutParams newLp = new RelativeLayout.LayoutParams(width, height); RelativeLayout.LayoutParams newLp = new RelativeLayout.LayoutParams(width, height);
newLp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 1); // Clears the rule, as there is no removeRule until API 17. newLp.addRule(
RelativeLayout.ALIGN_PARENT_BOTTOM,
1); // Clears the rule, as there is no removeRule until API 17.
newLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 1); newLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 1);
mCaptureView.setLayoutParams(newLp); mCaptureView.setLayoutParams(newLp);
Log.d("Video preview size set to " + width + "x" + height); Log.d("[Video Fragment] Video preview size set to " + width + "x" + height);
} }
} }
@ -177,17 +188,14 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
} }
String newDevice; String newDevice;
if (index == 1) if (index == 1) newDevice = devices[0];
newDevice = devices[0]; else if (devices.length > 1) newDevice = devices[1];
else if (devices.length > 1) else newDevice = devices[index];
newDevice = devices[1];
else
newDevice = devices[index];
LinphoneManager.getLc().setVideoDevice(newDevice); LinphoneManager.getLc().setVideoDevice(newDevice);
CallManager.getInstance().updateCall(); CallManager.getInstance().updateCall();
} catch (ArithmeticException ae) { } catch (ArithmeticException ae) {
Log.e("Cannot swtich camera : no camera"); Log.e("[Video Fragment] Cannot swtich camera : no camera");
} }
} }
@ -199,16 +207,24 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
LinphoneService.instance().destroyOverlay(); LinphoneService.instance().destroyOverlay();
} }
mGestureDetector = new GestureDetector(inCallActivity, this); mGestureDetector = new GestureDetector(mInCallActivity, this);
mScaleDetector = Compatibility.getScaleGestureDetector(inCallActivity, this); mScaleDetector = new CompatibilityScaleGestureDetector(mInCallActivity);
mScaleDetector.setOnScaleListener(this);
resizePreview(); resizePreview();
} }
@Override @Override
public void onPause() { public void onPause() {
if (LinphonePreferences.instance().isOverlayEnabled()) { Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
LinphoneService.instance().createOverlay(); if (LinphonePreferences.instance().isOverlayEnabled()
&& lc != null
&& lc.getCurrentCall() != null) {
Call call = lc.getCurrentCall();
if (call.getState() == Call.State.StreamsRunning) {
// Prevent overlay creation if video call is paused by remote
LinphoneService.instance().createOverlay();
}
} }
super.onPause(); super.onPause();
@ -218,10 +234,15 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
mZoomFactor *= detector.getScaleFactor(); mZoomFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large. // Don't let the object get too small or too large.
// Zoom to make the video fill the screen vertically // Zoom to make the video fill the screen vertically
float portraitZoomFactor = ((float) mVideoView.getHeight()) / (float) ((3 * mVideoView.getWidth()) / 4); float portraitZoomFactor =
((float) mVideoView.getHeight()) / (float) ((3 * mVideoView.getWidth()) / 4);
// Zoom to make the video fill the screen horizontally // Zoom to make the video fill the screen horizontally
float landscapeZoomFactor = ((float) mVideoView.getWidth()) / (float) ((3 * mVideoView.getHeight()) / 4); float landscapeZoomFactor =
mZoomFactor = Math.max(0.1f, Math.min(mZoomFactor, Math.max(portraitZoomFactor, landscapeZoomFactor))); ((float) mVideoView.getWidth()) / (float) ((3 * mVideoView.getHeight()) / 4);
mZoomFactor =
Math.max(
0.1f,
Math.min(mZoomFactor, Math.max(portraitZoomFactor, landscapeZoomFactor)));
Call currentCall = LinphoneManager.getLc().getCurrentCall(); Call currentCall = LinphoneManager.getLc().getCurrentCall();
if (currentCall != null) { if (currentCall != null) {
@ -247,16 +268,14 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
mZoomCenterY -= 0.01; mZoomCenterY -= 0.01;
} }
if (mZoomCenterX > 1) if (mZoomCenterX > 1) mZoomCenterX = 1;
mZoomCenterX = 1; if (mZoomCenterX < 0) mZoomCenterX = 0;
if (mZoomCenterX < 0) if (mZoomCenterY > 1) mZoomCenterY = 1;
mZoomCenterX = 0; if (mZoomCenterY < 0) mZoomCenterY = 0;
if (mZoomCenterY > 1)
mZoomCenterY = 1;
if (mZoomCenterY < 0)
mZoomCenterY = 0;
LinphoneManager.getLc().getCurrentCall().zoom(mZoomFactor, mZoomCenterX, mZoomCenterY); LinphoneManager.getLc()
.getCurrentCall()
.zoom(mZoomFactor, mZoomCenterX, mZoomCenterY);
return true; return true;
} }
} }
@ -269,9 +288,13 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
if (LinphoneUtils.isCallEstablished(LinphoneManager.getLc().getCurrentCall())) { if (LinphoneUtils.isCallEstablished(LinphoneManager.getLc().getCurrentCall())) {
if (mZoomFactor == 1.f) { if (mZoomFactor == 1.f) {
// Zoom to make the video fill the screen vertically // Zoom to make the video fill the screen vertically
float portraitZoomFactor = ((float) mVideoView.getHeight()) / (float) ((3 * mVideoView.getWidth()) / 4); float portraitZoomFactor =
((float) mVideoView.getHeight())
/ (float) ((3 * mVideoView.getWidth()) / 4);
// Zoom to make the video fill the screen horizontally // Zoom to make the video fill the screen horizontally
float landscapeZoomFactor = ((float) mVideoView.getWidth()) / (float) ((3 * mVideoView.getHeight()) / 4); float landscapeZoomFactor =
((float) mVideoView.getWidth())
/ (float) ((3 * mVideoView.getHeight()) / 4);
mZoomFactor = Math.max(portraitZoomFactor, landscapeZoomFactor); mZoomFactor = Math.max(portraitZoomFactor, landscapeZoomFactor);
} else { } else {
@ -292,7 +315,7 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
@Override @Override
public void onDestroy() { public void onDestroy() {
inCallActivity = null; mInCallActivity = null;
mCaptureView = null; mCaptureView = null;
if (mVideoView != null) { if (mVideoView != null) {
@ -327,20 +350,15 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On
} }
@Override @Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float velocityY) {
return false; return false;
} }
@Override @Override
public void onLongPress(MotionEvent e) { public void onLongPress(MotionEvent e) {}
}
@Override @Override
public void onShowPress(MotionEvent e) { public void onShowPress(MotionEvent e) {}
}
@Override @Override
public boolean onSingleTapUp(MotionEvent e) { public boolean onSingleTapUp(MotionEvent e) {

View file

@ -0,0 +1,107 @@
package org.linphone.chat;
/*
ChatMessageViewHolder.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.linphone.R;
public class ChatMessageOldViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
public final LinearLayout eventLayout;
public final TextView eventMessage;
public final RelativeLayout bubbleLayout;
public final LinearLayout separatorLayout;
public final LinearLayout background;
public final RelativeLayout avatarLayout;
public final TextView contactName;
public final ImageView messageStatus;
public final ProgressBar messageSendingInProgress;
public final LinearLayout imdmLayout;
public final ImageView imdmIcon;
public final TextView imdmLabel;
public final TextView messageText;
public final ImageView messageImage;
public final RelativeLayout fileTransferLayout;
public final ProgressBar fileTransferProgressBar;
public final Button fileTransferAction;
public final TextView fileName;
public final Button openFileButton;
public final CheckBox delete;
private ChatMessageViewHolderClickListener mListener;
public ChatMessageOldViewHolder(View view, ChatMessageViewHolderClickListener listener) {
this(view);
mListener = listener;
view.setOnClickListener(this);
}
public ChatMessageOldViewHolder(View view) {
super(view);
eventLayout = view.findViewById(R.id.event);
// eventTime = view.findViewById(R.id.event_date);
eventMessage = view.findViewById(R.id.event_text);
bubbleLayout = view.findViewById(R.id.bubble);
background = view.findViewById(R.id.background);
avatarLayout = view.findViewById(R.id.avatar_layout);
contactName = view.findViewById(R.id.contact_header);
messageStatus = view.findViewById(R.id.status);
messageSendingInProgress = view.findViewById(R.id.inprogress);
imdmLayout = view.findViewById(R.id.imdmLayout);
imdmIcon = view.findViewById(R.id.imdmIcon);
imdmLabel = view.findViewById(R.id.imdmText);
messageText = view.findViewById(R.id.message);
messageImage = view.findViewById(R.id.image);
separatorLayout = view.findViewById(R.id.separator);
fileTransferLayout = view.findViewById(R.id.file_transfer_layout);
fileTransferProgressBar = view.findViewById(R.id.progress_bar);
fileTransferAction = view.findViewById(R.id.file_transfer_action);
fileName = view.findViewById(R.id.file_name);
openFileButton = view.findViewById(R.id.open_file);
delete = view.findViewById(R.id.delete_message);
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClicked(getAdapterPosition());
}
}
}

View file

@ -0,0 +1,377 @@
package org.linphone.chat;
/*
ChatMessageViewHolder.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.google.android.flexbox.FlexboxLayout;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.Content;
import org.linphone.core.tools.Log;
import org.linphone.utils.FileUtils;
import org.linphone.utils.ImageUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public final LinearLayout eventLayout;
public final TextView eventMessage;
public final LinearLayout securityEventLayout;
public final TextView securityEventMessage;
public final View rightAnchor;
public final RelativeLayout bubbleLayout;
public final LinearLayout background;
public final RelativeLayout avatarLayout;
public final ProgressBar downloadInProgress, sendInProgress;
public final TextView timeText;
public final ImageView outgoingImdn;
public final TextView messageText;
public final FlexboxLayout multiFileContents;
public final RelativeLayout singleFileContent;
public final CheckBox delete;
private Context mContext;
private ChatMessageViewHolderClickListener mListener;
public ChatMessageViewHolder(
Context context, View view, ChatMessageViewHolderClickListener listener) {
this(view);
mContext = context;
mListener = listener;
view.setOnClickListener(this);
}
public ChatMessageViewHolder(View view) {
super(view);
eventLayout = view.findViewById(R.id.event);
eventMessage = view.findViewById(R.id.event_text);
securityEventLayout = view.findViewById(R.id.security_event);
securityEventMessage = view.findViewById(R.id.security_event_text);
rightAnchor = view.findViewById(R.id.rightAnchor);
bubbleLayout = view.findViewById(R.id.bubble);
background = view.findViewById(R.id.background);
avatarLayout = view.findViewById(R.id.avatar_layout);
downloadInProgress = view.findViewById(R.id.download_in_progress);
sendInProgress = view.findViewById(R.id.send_in_progress);
timeText = view.findViewById(R.id.time);
outgoingImdn = view.findViewById(R.id.imdn);
messageText = view.findViewById(R.id.message);
singleFileContent = view.findViewById(R.id.single_content);
multiFileContents = view.findViewById(R.id.multi_content);
delete = view.findViewById(R.id.delete_event);
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClicked(getAdapterPosition());
}
}
public void bindMessage(final ChatMessage message, LinphoneContact contact) {
eventLayout.setVisibility(View.GONE);
securityEventLayout.setVisibility(View.GONE);
rightAnchor.setVisibility(View.VISIBLE);
bubbleLayout.setVisibility(View.VISIBLE);
messageText.setVisibility(View.GONE);
timeText.setVisibility(View.VISIBLE);
outgoingImdn.setVisibility(View.GONE);
avatarLayout.setVisibility(View.GONE);
sendInProgress.setVisibility(View.GONE);
downloadInProgress.setVisibility(View.GONE);
singleFileContent.setVisibility(View.GONE);
multiFileContents.setVisibility(View.GONE);
ChatMessage.State status = message.getState();
Address remoteSender = message.getFromAddress();
String displayName;
String time =
LinphoneUtils.timestampToHumanDate(
mContext, message.getTime(), R.string.messages_date_format);
if (message.isOutgoing()) {
bubbleLayout.setPadding(0, 0, 0, 0); // Reset padding
outgoingImdn.setVisibility(View.INVISIBLE); // For anchoring purposes
if (status == ChatMessage.State.DeliveredToUser) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_received);
} else if (status == ChatMessage.State.Displayed) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_read);
} else if (status == ChatMessage.State.NotDelivered) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_error);
} else if (status == ChatMessage.State.FileTransferError) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_error);
} else if (status == ChatMessage.State.InProgress
|| status == ChatMessage.State.FileTransferInProgress) {
sendInProgress.setVisibility(View.VISIBLE);
}
timeText.setVisibility(View.VISIBLE);
background.setBackgroundResource(R.drawable.chat_bubble_outgoing_full);
} else {
rightAnchor.setVisibility(View.GONE);
avatarLayout.setVisibility(View.VISIBLE);
background.setBackgroundResource(R.drawable.chat_bubble_incoming_full);
// Can't anchor incoming messages, setting this to align max width with LIME icon
bubbleLayout.setPadding(
0, 0, (int) ImageUtils.dpToPixels(LinphoneActivity.instance(), 18), 0);
if (status == ChatMessage.State.FileTransferInProgress) {
downloadInProgress.setVisibility(View.VISIBLE);
}
}
if (contact == null) {
contact = ContactsManager.getInstance().findContactFromAddress(remoteSender);
}
if (contact != null) {
if (contact.getFullName() != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
}
ContactAvatar.displayAvatar(contact, avatarLayout);
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
ContactAvatar.displayAvatar(displayName, avatarLayout);
}
if (message.isOutgoing()) {
timeText.setText(time);
} else {
timeText.setText(time + " - " + displayName);
}
if (message.hasTextContent()) {
String msg = message.getTextContent();
Spanned text = LinphoneUtils.getTextWithHttpLinks(msg);
messageText.setText(text);
messageText.setMovementMethod(LinkMovementMethod.getInstance());
messageText.setVisibility(View.VISIBLE);
}
List<Content> fileContents = new ArrayList<>();
for (Content c : message.getContents()) {
if (c.isFile() || c.isFileTransfer()) {
fileContents.add(c);
}
}
if (fileContents.size() == 1) {
singleFileContent.setVisibility(View.VISIBLE);
displayContent(message, fileContents.get(0), singleFileContent, false);
} else if (fileContents.size() > 1) {
multiFileContents.removeAllViews();
multiFileContents.setVisibility(View.VISIBLE);
for (Content c : fileContents) {
View content =
LayoutInflater.from(mContext)
.inflate(R.layout.chat_bubble_content, null, false);
displayContent(message, c, content, true);
multiFileContents.addView(content);
}
}
}
private void displayContent(
final ChatMessage message, Content c, View content, boolean isMultiContent) {
final Button downloadOrCancel = content.findViewById(R.id.download);
downloadOrCancel.setVisibility(View.GONE);
final ImageView bigImage = content.findViewById(R.id.bigImage);
bigImage.setVisibility(View.GONE);
final ImageView smallImage = content.findViewById(R.id.image);
smallImage.setVisibility(View.GONE);
final TextView fileName = content.findViewById(R.id.file);
fileName.setVisibility(View.GONE);
if (c.isFile() || (c.isFileTransfer() && message.isOutgoing())) {
// If message is outgoing, even if content
// is file transfer we have the file available
final String filePath = c.getFilePath();
View v;
if (FileUtils.isExtensionImage(filePath)) {
if (!isMultiContent
&& mContext.getResources()
.getBoolean(
R.bool.use_big_pictures_to_preview_images_file_transfers)) {
loadBitmap(c.getFilePath(), bigImage);
v = bigImage;
} else {
loadBitmap(c.getFilePath(), smallImage);
v = smallImage;
}
} else {
fileName.setText(FileUtils.getNameFromFilePath(filePath));
v = fileName;
}
v.setVisibility(View.VISIBLE);
v.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
openFile(filePath);
}
});
} else {
downloadOrCancel.setVisibility(View.VISIBLE);
if (mContext.getPackageManager()
.checkPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
mContext.getPackageName())
== PackageManager.PERMISSION_GRANTED) {
String filename = c.getName();
File file = new File(FileUtils.getStorageDirectory(mContext), filename);
int prefix = 1;
while (file.exists()) {
file =
new File(
FileUtils.getStorageDirectory(mContext),
prefix + "_" + filename);
Log.w(
"File with that name already exists, renamed to "
+ prefix
+ "_"
+ filename);
prefix += 1;
}
c.setFilePath(file.getPath());
downloadOrCancel.setTag(c);
if (!message.isFileTransferInProgress()) {
downloadOrCancel.setText(R.string.download_file);
} else {
downloadOrCancel.setText(R.string.cancel);
}
downloadOrCancel.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Content c = (Content) v.getTag();
if (!message.isFileTransferInProgress()) {
message.downloadContent(c);
} else {
message.cancelFileTransfer();
}
}
});
} else {
Log.w(
"WRITE_EXTERNAL_STORAGE permission not granted, won't be able to store the downloaded file");
LinphoneActivity.instance().checkAndRequestExternalStoragePermission();
}
}
}
private void openFile(String path) {
Intent intent = new Intent(Intent.ACTION_VIEW);
File file;
Uri contentUri;
if (path.startsWith("file://")) {
path = path.substring("file://".length());
file = new File(path);
contentUri =
FileProvider.getUriForFile(
mContext,
mContext.getResources().getString(R.string.file_provider),
file);
} else if (path.startsWith("content://")) {
contentUri = Uri.parse(path);
} else {
file = new File(path);
try {
contentUri =
FileProvider.getUriForFile(
mContext,
mContext.getResources().getString(R.string.file_provider),
file);
} catch (Exception e) {
contentUri = Uri.parse(path);
}
}
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(contentUri.toString());
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
if (type != null) {
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(contentUri, "*/*");
}
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
mContext.startActivity(intent);
}
private void loadBitmap(String path, ImageView imageView) {
Glide.with(mContext).load(path).into(imageView);
}
}

View file

@ -0,0 +1,24 @@
package org.linphone.chat;
/*
ChatMessageViewHolderClickListener.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
interface ChatMessageViewHolderClickListener {
void onItemClicked(int position);
}

View file

@ -0,0 +1,366 @@
package org.linphone.chat;
/*
ChatMessagesAdapter.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatMessageListenerStub;
import org.linphone.core.EventLog;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper;
public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder>
implements ChatMessagesGenericAdapter {
private static final int MAX_TIME_TO_GROUP_MESSAGES = 300; // 5 minutes
private final Context mContext;
private List<EventLog> mHistory;
private List<LinphoneContact> mParticipants;
private final int mItemResource;
private final ChatMessagesFragment mFragment;
private final List<ChatMessage> mTransientMessages;
private final ChatMessageViewHolderClickListener mClickListener;
private final ChatMessageListenerStub mListener;
public ChatMessagesAdapter(
ChatMessagesFragment fragment,
SelectableHelper helper,
int itemResource,
EventLog[] history,
ArrayList<LinphoneContact> participants,
ChatMessageViewHolderClickListener clickListener) {
super(helper);
mFragment = fragment;
mContext = mFragment.getActivity();
mItemResource = itemResource;
mHistory = new ArrayList<>(Arrays.asList(history));
Collections.reverse(mHistory);
mParticipants = participants;
mClickListener = clickListener;
mTransientMessages = new ArrayList<>();
mListener =
new ChatMessageListenerStub() {
@Override
public void onMsgStateChanged(ChatMessage message, ChatMessage.State state) {
ChatMessageViewHolder holder =
(ChatMessageViewHolder) message.getUserData();
if (holder != null) {
int position = holder.getAdapterPosition();
if (position >= 0) {
notifyItemChanged(position);
} else {
notifyDataSetChanged();
}
} else {
// Just in case, better to refresh the whole view than to miss
// an update
notifyDataSetChanged();
}
if (state == ChatMessage.State.Displayed) {
mTransientMessages.remove(message);
}
}
};
}
@Override
public ChatMessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(mItemResource, parent, false);
ChatMessageViewHolder VH = new ChatMessageViewHolder(mContext, v, mClickListener);
// Allows onLongClick ContextMenu on bubbles
mFragment.registerForContextMenu(v);
v.setTag(VH);
return VH;
}
@Override
public void onBindViewHolder(@NonNull ChatMessageViewHolder holder, int position) {
if (position < 0) return;
EventLog event = mHistory.get(position);
holder.delete.setVisibility(View.GONE);
holder.eventLayout.setVisibility(View.GONE);
holder.securityEventLayout.setVisibility(View.GONE);
holder.rightAnchor.setVisibility(View.GONE);
holder.bubbleLayout.setVisibility(View.GONE);
holder.sendInProgress.setVisibility(View.GONE);
if (isEditionEnabled()) {
holder.delete.setVisibility(View.VISIBLE);
holder.delete.setChecked(isSelected(position));
holder.delete.setTag(position);
}
if (event.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage message = event.getChatMessage();
if ((message.isOutgoing() && message.getState() != ChatMessage.State.Displayed)
|| (!message.isOutgoing() && message.isFileTransfer())) {
if (!mTransientMessages.contains(message)) {
mTransientMessages.add(message);
}
// This only works if JAVA object is kept, hence the transient list
message.setUserData(holder);
message.addListener(mListener);
}
LinphoneContact contact = null;
Address remoteSender = message.getFromAddress();
if (!message.isOutgoing()) {
for (LinphoneContact c : mParticipants) {
if (c != null && c.hasAddress(remoteSender.asStringUriOnly())) {
contact = c;
break;
}
}
}
holder.bindMessage(message, contact);
changeBackgroundDependingOnPreviousAndNextEvents(message, holder, position);
} else { // Event is not chat message
Address address = event.getParticipantAddress();
if (address == null && event.getType() == EventLog.Type.ConferenceSecurityEvent) {
address = event.getSecurityEventFaultyDeviceAddress();
}
String displayName = "";
if (address != null) {
LinphoneContact contact =
ContactsManager.getInstance().findContactFromAddress(address);
if (contact != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(address);
}
}
switch (event.getType()) {
case ConferenceCreated:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.conference_created));
break;
case ConferenceTerminated:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.conference_destroyed));
break;
case ConferenceParticipantAdded:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.participant_added)
.replace("%s", displayName));
break;
case ConferenceParticipantRemoved:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.participant_removed)
.replace("%s", displayName));
break;
case ConferenceSubjectChanged:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.subject_changed)
.replace("%s", event.getSubject()));
break;
case ConferenceParticipantSetAdmin:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.admin_set).replace("%s", displayName));
break;
case ConferenceParticipantUnsetAdmin:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.admin_unset).replace("%s", displayName));
break;
case ConferenceParticipantDeviceAdded:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.device_added).replace("%s", displayName));
break;
case ConferenceParticipantDeviceRemoved:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.device_removed).replace("%s", displayName));
break;
case ConferenceSecurityEvent:
holder.securityEventLayout.setVisibility(View.VISIBLE);
switch (event.getSecurityEventType()) {
case EncryptionIdentityKeyChanged:
holder.securityEventMessage.setText(
mContext.getString(R.string.lime_identity_key_changed)
.replace("%s", displayName));
break;
case ManInTheMiddleDetected:
holder.securityEventMessage.setText(
mContext.getString(R.string.man_in_the_middle_detected)
.replace("%s", displayName));
break;
case SecurityLevelDowngraded:
holder.securityEventMessage.setText(
mContext.getString(R.string.security_level_downgraded)
.replace("%s", displayName));
break;
case ParticipantMaxDeviceCountExceeded:
holder.securityEventMessage.setText(
mContext.getString(R.string.participant_max_count_exceeded)
.replace("%s", displayName));
break;
case None:
default:
break;
}
break;
case None:
default:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(
mContext.getString(R.string.unexpected_event)
.replace("%s", displayName)
.replace("%i", String.valueOf(event.getType().toInt())));
break;
}
}
}
@Override
public int getItemCount() {
return mHistory.size();
}
public void addToHistory(EventLog log) {
mHistory.add(0, log);
notifyItemInserted(0);
notifyItemChanged(1); // Update second to last item just in case for grouping purposes
}
public void addAllToHistory(ArrayList<EventLog> logs) {
int currentSize = mHistory.size() - 1;
Collections.reverse(logs);
mHistory.addAll(logs);
notifyItemRangeInserted(currentSize + 1, logs.size());
}
public void setContacts(ArrayList<LinphoneContact> participants) {
mParticipants = participants;
}
public void refresh(EventLog[] history) {
mHistory = new ArrayList<>(Arrays.asList(history));
Collections.reverse(mHistory);
notifyDataSetChanged();
}
public void clear() {
for (EventLog event : mHistory) {
if (event.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage message = event.getChatMessage();
message.removeListener(mListener);
}
}
mTransientMessages.clear();
mHistory.clear();
}
public Object getItem(int i) {
return mHistory.get(i);
}
public void removeItem(int i) {
mHistory.remove(i);
notifyItemRemoved(i);
}
private void changeBackgroundDependingOnPreviousAndNextEvents(
ChatMessage message, ChatMessageViewHolder holder, int position) {
boolean hasPrevious = false, hasNext = false;
// Do not forget history is reversed, so previous in order is next in list display and
// chronology !
if (position > 0
&& mContext.getResources()
.getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) {
EventLog previousEvent = (EventLog) getItem(position - 1);
if (previousEvent.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage previousMessage = previousEvent.getChatMessage();
if (previousMessage.getFromAddress().weakEqual(message.getFromAddress())) {
if (previousMessage.getTime() - message.getTime()
< MAX_TIME_TO_GROUP_MESSAGES) {
hasPrevious = true;
}
}
}
}
if (position >= 0
&& position < mHistory.size() - 1
&& mContext.getResources()
.getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) {
EventLog nextEvent = (EventLog) getItem(position + 1);
if (nextEvent.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage nextMessage = nextEvent.getChatMessage();
if (nextMessage.getFromAddress().weakEqual(message.getFromAddress())) {
if (message.getTime() - nextMessage.getTime() < MAX_TIME_TO_GROUP_MESSAGES) {
holder.timeText.setVisibility(View.GONE);
if (!message.isOutgoing()) {
holder.avatarLayout.setVisibility(View.INVISIBLE);
}
hasNext = true;
}
}
}
}
if (message.isOutgoing()) {
if (hasNext && hasPrevious) {
holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_split_2);
} else if (hasNext) {
holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_split_3);
} else if (hasPrevious) {
holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_split_1);
} else {
holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_full);
}
} else {
if (hasNext && hasPrevious) {
holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_split_2);
} else if (hasNext) {
holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_split_3);
} else if (hasPrevious) {
holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_split_1);
} else {
holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_full);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
package org.linphone; package org.linphone.chat;
/* /*
AinitTestEnv.java ChatMessagesGenericAdapter.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -19,22 +19,22 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import junit.framework.Assert; import java.util.ArrayList;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.EventLog;
import android.test.suitebuilder.annotation.LargeTest; interface ChatMessagesGenericAdapter {
import android.test.suitebuilder.annotation.MediumTest; void addToHistory(EventLog log);
import android.test.suitebuilder.annotation.SmallTest;
public class AinitTestEnv extends SampleTest { void addAllToHistory(ArrayList<EventLog> logs);
@SmallTest void setContacts(ArrayList<LinphoneContact> participants);
@MediumTest
@LargeTest
public void testAInitLinphoneCore() {
LinphoneTestManager.createAndStart(aContext, iContext, 1);
solo.sleep(5000); void refresh(EventLog[] history);
Assert.assertEquals(1, LinphoneTestManager.getLc().getProxyConfigList().length);
waitForRegistration(LinphoneTestManager.getLc().getProxyConfigList()[0]); void clear();
}
Object getItem(int i);
void removeItem(int i);
} }

View file

@ -0,0 +1,662 @@
package org.linphone.chat;
/*
ChatMessagesAdapter.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatMessageListenerStub;
import org.linphone.core.Content;
import org.linphone.core.EventLog;
import org.linphone.core.LimeState;
import org.linphone.core.tools.Log;
import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper;
import org.linphone.views.AsyncBitmap;
import org.linphone.views.BitmapWorkerTask;
import org.linphone.views.ContactAvatar;
public class ChatMessagesOldAdapter extends SelectableAdapter<ChatMessageOldViewHolder>
implements ChatMessagesGenericAdapter {
private static final int MARGIN_BETWEEN_MESSAGES = 10;
private static final int SIDE_MARGIN = 100;
private final Context mContext;
private List<EventLog> mHistory;
private List<LinphoneContact> mParticipants;
private final int mItemResource;
private Bitmap mDefaultBitmap;
private final ChatMessagesFragment mFragment;
private final ChatMessageListenerStub mListener;
private final ChatMessageViewHolderClickListener mClickListener;
public ChatMessagesOldAdapter(
ChatMessagesFragment fragment,
SelectableHelper helper,
int itemResource,
EventLog[] history,
ArrayList<LinphoneContact> participants,
ChatMessageViewHolderClickListener clickListener) {
super(helper);
mFragment = fragment;
mContext = mFragment.getActivity();
mItemResource = itemResource;
mHistory = new ArrayList<>(Arrays.asList(history));
Collections.reverse(mHistory);
mParticipants = participants;
mClickListener = clickListener;
mListener =
new ChatMessageListenerStub() {
@Override
public void onFileTransferProgressIndication(
ChatMessage message, Content content, int offset, int total) {
ChatMessageOldViewHolder holder =
(ChatMessageOldViewHolder) message.getUserData();
if (holder == null) return;
if (offset == total) {
holder.fileTransferProgressBar.setVisibility(View.GONE);
holder.fileTransferAction.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.GONE);
displayAttachedFile(message, holder);
} else {
holder.fileTransferProgressBar.setVisibility(View.VISIBLE);
holder.fileTransferProgressBar.setProgress(offset * 100 / total);
}
}
@Override
public void onMsgStateChanged(ChatMessage message, ChatMessage.State state) {
if (state == ChatMessage.State.FileTransferDone) {
if (!message.isOutgoing()) {
message.setAppdata(message.getFileTransferFilepath());
}
message.setFileTransferFilepath(
null); // Not needed anymore, will help differenciate between
// InProgress states for file transfer / message sending
}
for (int i = 0; i < mHistory.size(); i++) {
EventLog log = mHistory.get(i);
if (log.getType() == EventLog.Type.ConferenceChatMessage
&& log.getChatMessage() == message) {
notifyItemChanged(i);
break;
}
}
}
};
}
@Override
public ChatMessageOldViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(mItemResource, parent, false);
ChatMessageOldViewHolder VH = new ChatMessageOldViewHolder(v, mClickListener);
// Allows onLongClick ContextMenu on bubbles
mFragment.registerForContextMenu(v);
v.setTag(VH);
return VH;
}
@Override
public void onBindViewHolder(@NonNull final ChatMessageOldViewHolder holder, int position) {
final EventLog event = mHistory.get(position);
holder.eventLayout.setVisibility(View.GONE);
holder.bubbleLayout.setVisibility(View.GONE);
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE);
holder.messageText.setVisibility(View.GONE);
holder.messageImage.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.GONE);
holder.fileTransferProgressBar.setProgress(0);
holder.fileTransferAction.setEnabled(true);
holder.fileName.setVisibility(View.GONE);
holder.openFileButton.setVisibility(View.GONE);
holder.messageStatus.setVisibility(View.INVISIBLE);
holder.messageSendingInProgress.setVisibility(View.GONE);
holder.imdmLayout.setVisibility(View.INVISIBLE);
if (isEditionEnabled()) {
holder.delete.setOnCheckedChangeListener(null);
holder.delete.setChecked(isSelected(position));
holder.delete.setTag(position);
}
if (event.getType() == EventLog.Type.ConferenceChatMessage) {
holder.bubbleLayout.setVisibility(View.VISIBLE);
final ChatMessage message = event.getChatMessage();
if (position > 0
&& mContext.getResources()
.getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) {
EventLog previousEvent = (EventLog) getItem(position - 1);
if (previousEvent.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage previousMessage = previousEvent.getChatMessage();
if (previousMessage.getFromAddress().weakEqual(message.getFromAddress())) {
holder.separatorLayout.setVisibility(View.GONE);
}
} else {
// No separator if previous event is not a message
holder.separatorLayout.setVisibility(View.GONE);
}
}
message.setUserData(holder);
message.addListener(mListener);
RelativeLayout.LayoutParams layoutParams =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
ChatMessage.State status = message.getState();
Address remoteSender = message.getFromAddress();
String displayName;
LinphoneContact contact = null;
if (message.isOutgoing()) {
if (status == ChatMessage.State.InProgress) {
holder.messageSendingInProgress.setVisibility(View.VISIBLE);
}
if (!message.isSecured()
&& LinphoneManager.getLc().limeEnabled() == LimeState.Mandatory
&& status != ChatMessage.State.InProgress) {
holder.messageStatus.setVisibility(View.VISIBLE);
holder.messageStatus.setImageResource(R.drawable.chat_unsecure);
}
if (status == ChatMessage.State.DeliveredToUser) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.imdn_received);
holder.imdmLabel.setText(R.string.delivered);
holder.imdmLabel.setTextColor(
mContext.getResources().getColor(R.color.grey_color));
} else if (status == ChatMessage.State.Displayed) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.imdn_read);
holder.imdmLabel.setText(R.string.displayed);
holder.imdmLabel.setTextColor(
mContext.getResources().getColor(R.color.imdn_read_color));
} else if (status == ChatMessage.State.NotDelivered) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.imdn_error);
holder.imdmLabel.setText(R.string.error);
holder.imdmLabel.setTextColor(
mContext.getResources().getColor(R.color.red_color));
} else if (status == ChatMessage.State.FileTransferError) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.imdn_error);
holder.imdmLabel.setText(R.string.file_transfer_error);
holder.imdmLabel.setTextColor(
mContext.getResources().getColor(R.color.red_color));
}
// layoutParams allow bubbles alignment during selection mode
if (isEditionEnabled()) {
layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId());
layoutParams.setMargins(
SIDE_MARGIN,
MARGIN_BETWEEN_MESSAGES / 2,
0,
MARGIN_BETWEEN_MESSAGES / 2);
} else {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams.setMargins(
SIDE_MARGIN,
MARGIN_BETWEEN_MESSAGES / 2,
0,
MARGIN_BETWEEN_MESSAGES / 2);
}
holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing);
Compatibility.setTextAppearance(holder.contactName, mContext, R.style.font3);
Compatibility.setTextAppearance(
holder.fileTransferAction, mContext, R.style.font15);
holder.fileTransferAction.setBackgroundResource(
R.drawable.resizable_confirm_delete_button);
} else {
for (LinphoneContact c : mParticipants) {
if (c != null && c.hasAddress(remoteSender.asStringUriOnly())) {
contact = c;
break;
}
}
if (isEditionEnabled()) {
layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId());
layoutParams.setMargins(
SIDE_MARGIN,
MARGIN_BETWEEN_MESSAGES / 2,
0,
MARGIN_BETWEEN_MESSAGES / 2);
} else {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams.setMargins(
0,
MARGIN_BETWEEN_MESSAGES / 2,
SIDE_MARGIN,
MARGIN_BETWEEN_MESSAGES / 2);
}
holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming);
Compatibility.setTextAppearance(
holder.contactName, mContext, R.style.contact_organization_font);
Compatibility.setTextAppearance(
holder.fileTransferAction, mContext, R.style.button_font);
holder.fileTransferAction.setBackgroundResource(
R.drawable.resizable_assistant_button);
}
if (contact == null) {
contact = ContactsManager.getInstance().findContactFromAddress(remoteSender);
}
if (contact != null) {
if (contact.getFullName() != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
}
ContactAvatar.displayAvatar(contact, holder.avatarLayout);
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
ContactAvatar.displayAvatar(displayName, holder.avatarLayout);
}
holder.contactName.setText(
LinphoneUtils.timestampToHumanDate(
mContext, message.getTime(), R.string.messages_date_format)
+ " - "
+ displayName);
if (message.hasTextContent()) {
String msg = message.getTextContent();
Spanned text = LinphoneUtils.getTextWithHttpLinks(msg);
holder.messageText.setText(text);
holder.messageText.setMovementMethod(LinkMovementMethod.getInstance());
holder.messageText.setVisibility(View.VISIBLE);
}
String externalBodyUrl = message.getExternalBodyUrl();
Content fileTransferContent = message.getFileTransferInformation();
boolean hasFile = message.getAppdata() != null;
boolean hasFileTransfer = externalBodyUrl != null;
for (Content c : message.getContents()) {
if (c.isFile()) {
hasFile = true;
} else if (c.isFileTransfer()) {
hasFileTransfer = true;
}
}
if (hasFile) { // Something to display
displayAttachedFile(message, holder);
}
if (hasFileTransfer) { // Incoming file transfer not yet downloaded
holder.fileName.setVisibility(View.VISIBLE);
holder.fileName.setText(fileTransferContent.getName());
holder.fileTransferLayout.setVisibility(View.VISIBLE);
holder.fileTransferProgressBar.setVisibility(View.GONE);
if (message.isFileTransferInProgress()) { // Incoming file transfer in progress
holder.fileTransferAction.setVisibility(View.GONE);
} else {
holder.fileTransferAction.setText(mContext.getString(R.string.accept));
holder.fileTransferAction.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mContext.getPackageManager()
.checkPermission(
Manifest.permission
.WRITE_EXTERNAL_STORAGE,
mContext.getPackageName())
== PackageManager.PERMISSION_GRANTED) {
v.setEnabled(false);
String filename =
message.getFileTransferInformation().getName();
File file =
new File(
FileUtils.getStorageDirectory(mContext),
filename);
int prefix = 1;
while (file.exists()) {
file =
new File(
FileUtils.getStorageDirectory(mContext),
prefix + "_" + filename);
Log.w(
"File with that name already exists, renamed to "
+ prefix
+ "_"
+ filename);
prefix += 1;
}
message.setFileTransferFilepath(file.getPath());
message.downloadFile();
} else {
Log.w(
"WRITE_EXTERNAL_STORAGE permission not granted, won't be able to store the downloaded file");
LinphoneActivity.instance()
.checkAndRequestExternalStoragePermission();
}
}
});
}
} else if (message.isFileTransferInProgress()) { // Outgoing file transfer in progress
holder.messageSendingInProgress.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.VISIBLE);
holder.fileTransferAction.setText(mContext.getString(R.string.cancel));
holder.fileTransferAction.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
message.cancelFileTransfer();
notifyItemChanged(holder.getAdapterPosition());
}
});
}
holder.bubbleLayout.setLayoutParams(layoutParams);
} else { // Event is not chat message
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setTextColor(
mContext.getResources().getColor(R.color.light_grey_color));
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_gray);
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_gray);
Address address = event.getParticipantAddress();
if (address == null && event.getType() == EventLog.Type.ConferenceSecurityEvent) {
address = event.getSecurityEventFaultyDeviceAddress();
}
String displayName = "";
if (address != null) {
LinphoneContact contact =
ContactsManager.getInstance().findContactFromAddress(address);
if (contact != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(address);
}
}
switch (event.getType()) {
case ConferenceCreated:
holder.eventMessage.setText(mContext.getString(R.string.conference_created));
break;
case ConferenceTerminated:
holder.eventMessage.setText(mContext.getString(R.string.conference_destroyed));
break;
case ConferenceParticipantAdded:
holder.eventMessage.setText(
mContext.getString(R.string.participant_added)
.replace("%s", displayName));
break;
case ConferenceParticipantRemoved:
holder.eventMessage.setText(
mContext.getString(R.string.participant_removed)
.replace("%s", displayName));
break;
case ConferenceSubjectChanged:
holder.eventMessage.setText(
mContext.getString(R.string.subject_changed)
.replace("%s", event.getSubject()));
break;
case ConferenceParticipantSetAdmin:
holder.eventMessage.setText(
mContext.getString(R.string.admin_set).replace("%s", displayName));
break;
case ConferenceParticipantUnsetAdmin:
holder.eventMessage.setText(
mContext.getString(R.string.admin_unset).replace("%s", displayName));
break;
case ConferenceParticipantDeviceAdded:
holder.eventMessage.setText(
mContext.getString(R.string.device_added).replace("%s", displayName));
break;
case ConferenceParticipantDeviceRemoved:
holder.eventMessage.setText(
mContext.getString(R.string.device_removed).replace("%s", displayName));
break;
case ConferenceSecurityEvent:
holder.eventMessage.setTextColor(
mContext.getResources().getColor(R.color.red_color));
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_red);
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_red);
switch (event.getSecurityEventType()) {
case EncryptionIdentityKeyChanged:
holder.eventMessage.setText(
mContext.getString(R.string.lime_identity_key_changed)
.replace("%s", displayName));
break;
case ManInTheMiddleDetected:
holder.eventMessage.setText(
mContext.getString(R.string.man_in_the_middle_detected)
.replace("%s", displayName));
break;
case SecurityLevelDowngraded:
holder.eventMessage.setText(
mContext.getString(R.string.security_level_downgraded)
.replace("%s", displayName));
break;
case ParticipantMaxDeviceCountExceeded:
holder.eventMessage.setText(
mContext.getString(R.string.participant_max_count_exceeded)
.replace("%s", displayName));
break;
case None:
default:
break;
}
break;
case None:
default:
holder.eventMessage.setText(
mContext.getString(R.string.unexpected_event)
.replace("%s", displayName)
.replace("%i", String.valueOf(event.getType().toInt())));
break;
}
}
}
@Override
public int getItemCount() {
return mHistory.size();
}
public void addToHistory(EventLog log) {
mHistory.add(0, log);
notifyItemInserted(0);
}
public void addAllToHistory(ArrayList<EventLog> logs) {
int currentSize = mHistory.size() - 1;
Collections.reverse(logs);
mHistory.addAll(logs);
notifyItemRangeInserted(currentSize + 1, logs.size());
}
public void setContacts(ArrayList<LinphoneContact> participants) {
mParticipants = participants;
}
public void refresh(EventLog[] history) {
mHistory = new ArrayList<>(Arrays.asList(history));
Collections.reverse(mHistory);
notifyDataSetChanged();
}
public void clear() {
for (EventLog event : mHistory) {
if (event.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage message = event.getChatMessage();
message.removeListener(mListener);
}
}
mHistory.clear();
}
public Object getItem(int i) {
return mHistory.get(i);
}
public void removeItem(int i) {
mHistory.remove(i);
notifyItemRemoved(i);
}
private void loadBitmap(String path, ImageView imageView) {
if (cancelPotentialWork(path, imageView)) {
mDefaultBitmap =
BitmapFactory.decodeResource(mContext.getResources(), R.drawable.chat_file);
BitmapWorkerTask task = new BitmapWorkerTask(mContext, imageView, mDefaultBitmap);
final AsyncBitmap asyncBitmap =
new AsyncBitmap(mContext.getResources(), mDefaultBitmap, task);
imageView.setImageDrawable(asyncBitmap);
task.execute(path);
}
}
private void openFile(String path) {
Intent intent = new Intent(Intent.ACTION_VIEW);
File file;
Uri contentUri;
if (path.startsWith("file://")) {
path = path.substring("file://".length());
file = new File(path);
contentUri =
FileProvider.getUriForFile(
mContext,
mContext.getResources().getString(R.string.file_provider),
file);
} else if (path.startsWith("content://")) {
contentUri = Uri.parse(path);
} else {
file = new File(path);
try {
contentUri =
FileProvider.getUriForFile(
mContext,
mContext.getResources().getString(R.string.file_provider),
file);
} catch (Exception e) {
contentUri = Uri.parse(path);
}
}
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(contentUri.toString());
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
if (type != null) {
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(contentUri, "*/*");
}
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
mContext.startActivity(intent);
}
private void displayAttachedFile(ChatMessage message, ChatMessageOldViewHolder holder) {
holder.fileName.setVisibility(View.VISIBLE);
String appData = message.getAppdata();
if (appData == null) {
for (Content c : message.getContents()) {
if (c.isFile()) {
appData = c.getFilePath();
}
}
}
if (appData != null) {
FileUtils.scanFile(message);
holder.fileName.setText(FileUtils.getNameFromFilePath(appData));
if (FileUtils.isExtensionImage(appData)) {
holder.messageImage.setVisibility(View.VISIBLE);
loadBitmap(appData, holder.messageImage);
holder.messageImage.setTag(appData);
} else {
holder.openFileButton.setVisibility(View.VISIBLE);
holder.openFileButton.setTag(appData);
holder.openFileButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
openFile((String) v.getTag());
}
});
}
}
}
private boolean cancelPotentialWork(String path, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = BitmapWorkerTask.getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final String bitmapData = bitmapWorkerTask.path;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == null || !bitmapData.equals(path)) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
}

View file

@ -0,0 +1,665 @@
/*
ChatRoomCreationFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.chat;
import static android.content.Context.INPUT_METHOD_SERVICE;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.CompoundButton;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SearchView;
import android.widget.Switch;
import android.widget.TextView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.SearchContactViewHolder;
import org.linphone.contacts.SearchContactsAdapter;
import org.linphone.core.Address;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.ChatRoomParams;
import org.linphone.core.Core;
import org.linphone.core.FriendCapability;
import org.linphone.core.ProxyConfig;
import org.linphone.core.SearchResult;
import org.linphone.core.tools.Log;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.settings.LinphonePreferences;
import org.linphone.views.ContactSelectView;
public class ChatRoomCreationFragment extends Fragment
implements View.OnClickListener,
SearchContactViewHolder.ClickListener,
ContactsUpdatedListener {
private RecyclerView mContactsList;
private LinearLayout mContactsSelectedLayout;
private HorizontalScrollView mContactsSelectLayout;
private ImageView mAllContactsButton, mLinphoneContactsButton, mBackButton, mNextButton;
private boolean mOnlyDisplayLinphoneContacts;
private View mAllContactsSelected, mLinphoneContactsSelected;
private RelativeLayout mSearchLayout, mWaitLayout, mLinphoneContactsToggle, mAllContactsToggle;
private SearchView mSearchField;
private ProgressBar mContactsFetchInProgress;
private SearchContactsAdapter mSearchAdapter;
private String mChatRoomSubject, mChatRoomAddress;
private ChatRoom mChatRoom;
private ChatRoomListenerStub mChatRoomCreationListener;
private Bundle mShareInfos;
private ImageView mSecurityToggleOff, mSecurityToggleOn;
private Switch mSecurityToggle;
private boolean mCreateGroupChatRoom;
private boolean mChatRoomEncrypted;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = inflater.inflate(R.layout.chat_create, container, false);
ArrayList<ContactAddress> selectedContacts = new ArrayList<>();
mChatRoomSubject = null;
mChatRoomAddress = null;
mCreateGroupChatRoom = false;
if (getArguments() != null) {
if (getArguments().getSerializable("selectedContacts") != null) {
selectedContacts =
(ArrayList<ContactAddress>)
getArguments().getSerializable("selectedContacts");
}
mChatRoomSubject = getArguments().getString("subject");
mChatRoomAddress = getArguments().getString("groupChatRoomAddress");
mCreateGroupChatRoom = getArguments().getBoolean("createGroupChatRoom", false);
mChatRoomEncrypted = getArguments().getBoolean("encrypted", false);
}
mWaitLayout = view.findViewById(R.id.waitScreen);
mWaitLayout.setVisibility(View.GONE);
mContactsList = view.findViewById(R.id.contactsList);
mContactsSelectedLayout = view.findViewById(R.id.contactsSelected);
mContactsSelectLayout = view.findViewById(R.id.layoutContactsSelected);
mAllContactsButton = view.findViewById(R.id.all_contacts);
mAllContactsButton.setOnClickListener(this);
mLinphoneContactsButton = view.findViewById(R.id.linphone_contacts);
mLinphoneContactsButton.setOnClickListener(this);
mAllContactsSelected = view.findViewById(R.id.all_contacts_select);
mLinphoneContactsSelected = view.findViewById(R.id.linphone_contacts_select);
mBackButton = view.findViewById(R.id.back);
mBackButton.setOnClickListener(this);
mNextButton = view.findViewById(R.id.next);
mNextButton.setOnClickListener(this);
mNextButton.setEnabled(false);
mSearchLayout = view.findViewById(R.id.layoutSearchField);
mContactsFetchInProgress = view.findViewById(R.id.contactsFetchInProgress);
mContactsFetchInProgress.setVisibility(View.GONE);
mSearchAdapter = new SearchContactsAdapter(this, !mCreateGroupChatRoom, mChatRoomEncrypted);
mSearchField = view.findViewById(R.id.searchField);
mSearchField.setOnQueryTextListener(
new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
mSearchAdapter.searchContacts(newText);
return true;
}
});
mLinphoneContactsToggle = view.findViewById(R.id.layout_linphone_contacts);
mAllContactsToggle = view.findViewById(R.id.layout_all_contacts);
mSecurityToggle = view.findViewById(R.id.security_toogle);
mSecurityToggle.setOnCheckedChangeListener(
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setSecurityEnabled(isChecked);
}
});
mSecurityToggleOn = view.findViewById(R.id.security_toogle_on);
mSecurityToggleOff = view.findViewById(R.id.security_toogle_off);
mSecurityToggleOn.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
setSecurityEnabled(true);
}
});
mSecurityToggleOff.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
setSecurityEnabled(false);
}
});
mSecurityToggle.setChecked(mChatRoomEncrypted);
mSearchAdapter.setSecurityEnabled(mChatRoomEncrypted);
ProxyConfig lpc = LinphoneManager.getLc().getDefaultProxyConfig();
if ((mChatRoomSubject != null && mChatRoomAddress != null)
|| (lpc == null || lpc.getConferenceFactoryUri() == null)) {
mSecurityToggle.setVisibility(View.GONE);
mSecurityToggleOn.setVisibility(View.GONE);
mSecurityToggleOff.setVisibility(View.GONE);
}
LinearLayoutManager layoutManager =
new LinearLayoutManager(getActivity().getApplicationContext());
mContactsList.setAdapter(mSearchAdapter);
DividerItemDecoration dividerItemDecoration =
new DividerItemDecoration(
mContactsList.getContext(), layoutManager.getOrientation());
dividerItemDecoration.setDrawable(
getActivity()
.getApplicationContext()
.getResources()
.getDrawable(R.drawable.divider));
mContactsList.addItemDecoration(dividerItemDecoration);
mContactsList.setLayoutManager(layoutManager);
if (savedInstanceState != null
&& savedInstanceState.getStringArrayList("selectedContacts") != null) {
mContactsSelectedLayout.removeAllViews();
// We need to get all contacts not only sip
selectedContacts =
(ArrayList<ContactAddress>)
savedInstanceState.getSerializable("selectedContacts");
}
if (selectedContacts.size() != 0) {
mSearchAdapter.setContactsSelectedList(selectedContacts);
updateList();
updateListSelected();
}
mOnlyDisplayLinphoneContacts =
ContactsManager.getInstance().isLinphoneContactsPrefered()
|| getResources().getBoolean(R.bool.hide_non_linphone_contacts);
if (savedInstanceState != null) {
mOnlyDisplayLinphoneContacts =
savedInstanceState.getBoolean("onlySipContact", mOnlyDisplayLinphoneContacts);
}
mSearchAdapter.setOnlySipContact(mOnlyDisplayLinphoneContacts);
updateList();
displayChatCreation();
mChatRoomCreationListener =
new ChatRoomListenerStub() {
@Override
public void onStateChanged(ChatRoom cr, ChatRoom.State newState) {
if (newState == ChatRoom.State.Created) {
mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance()
.goToChat(
cr.getLocalAddress().asStringUriOnly(),
cr.getPeerAddress().asStringUriOnly(),
mShareInfos);
} else if (newState == ChatRoom.State.CreationFailed) {
mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance().displayChatRoomError();
Log.e(
"[Chat Room Creation] Group chat room for address "
+ cr.getPeerAddress()
+ " has failed !");
}
}
};
if (getArguments() != null) {
String fileSharedUri = getArguments().getString("fileSharedUri");
String messageDraft = getArguments().getString("messageDraft");
if (fileSharedUri != null || messageDraft != null) {
Log.i("[ChatRoomCreation] Forwarding arguments to new chat room");
mShareInfos = new Bundle();
}
if (fileSharedUri != null) {
LinphoneActivity.instance().checkAndRequestPermissionsToSendImage();
mShareInfos.putString("fileSharedUri", fileSharedUri);
}
if (messageDraft != null) mShareInfos.putString("messageDraft", messageDraft);
}
return view;
}
@Override
public void onResume() {
ContactsManager.getInstance().addContactsListener(this);
super.onResume();
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.CREATE_CHAT);
}
InputMethodManager inputMethodManager =
(InputMethodManager) getActivity().getSystemService(INPUT_METHOD_SERVICE);
if (getActivity().getCurrentFocus() != null) {
inputMethodManager.hideSoftInputFromWindow(
getActivity().getCurrentFocus().getWindowToken(), 0);
}
}
@Override
public void onPause() {
if (mChatRoom != null) {
mChatRoom.removeListener(mChatRoomCreationListener);
}
ContactsManager.getInstance().removeContactsListener(this);
super.onPause();
}
private void setSecurityEnabled(boolean enabled) {
mChatRoomEncrypted = enabled;
mSecurityToggle.setChecked(mChatRoomEncrypted);
mSearchAdapter.setSecurityEnabled(mChatRoomEncrypted);
if (enabled) {
// Remove all contacts added before LIME switch was set
// and that can stay because they don't have the capability
mContactsSelectedLayout.removeAllViews();
List<ContactAddress> toToggle = new ArrayList<>();
for (ContactAddress ca : mSearchAdapter.getContactsSelectedList()) {
// If the ContactAddress doesn't have a contact keep it anyway
if (ca.getContact() != null && !ca.hasCapability(FriendCapability.LimeX3Dh)) {
toToggle.add(ca);
} else {
if (ca.getView() != null) {
mContactsSelectedLayout.addView(ca.getView());
}
}
}
for (ContactAddress ca : toToggle) {
mSearchAdapter.toggleContactSelection(ca);
}
mContactsSelectedLayout.invalidate();
}
}
private void displayChatCreation() {
mNextButton.setVisibility(View.VISIBLE);
mNextButton.setEnabled(mSearchAdapter.getContactsSelectedList().size() > 0);
mContactsList.setVisibility(View.VISIBLE);
mSearchLayout.setVisibility(View.VISIBLE);
if (mCreateGroupChatRoom) {
mLinphoneContactsToggle.setVisibility(View.GONE);
mAllContactsToggle.setVisibility(View.GONE);
mContactsSelectLayout.setVisibility(View.VISIBLE);
mNextButton.setVisibility(View.VISIBLE);
} else {
mLinphoneContactsToggle.setVisibility(View.VISIBLE);
mAllContactsToggle.setVisibility(View.VISIBLE);
mContactsSelectLayout.setVisibility(View.GONE);
mNextButton.setVisibility(View.GONE);
}
if (getResources().getBoolean(R.bool.hide_non_linphone_contacts)) {
mLinphoneContactsToggle.setVisibility(View.GONE);
mLinphoneContactsButton.setVisibility(View.INVISIBLE);
mAllContactsButton.setEnabled(false);
mLinphoneContactsButton.setEnabled(false);
mOnlyDisplayLinphoneContacts = true;
mAllContactsButton.setOnClickListener(null);
mLinphoneContactsButton.setOnClickListener(null);
mLinphoneContactsSelected.setVisibility(View.INVISIBLE);
mLinphoneContactsSelected.setVisibility(View.INVISIBLE);
} else {
mAllContactsButton.setVisibility(View.VISIBLE);
mLinphoneContactsButton.setVisibility(View.VISIBLE);
if (mOnlyDisplayLinphoneContacts) {
mAllContactsSelected.setVisibility(View.INVISIBLE);
mLinphoneContactsSelected.setVisibility(View.VISIBLE);
} else {
mAllContactsSelected.setVisibility(View.VISIBLE);
mLinphoneContactsSelected.setVisibility(View.INVISIBLE);
}
mAllContactsButton.setEnabled(mOnlyDisplayLinphoneContacts);
mLinphoneContactsButton.setEnabled(!mAllContactsButton.isEnabled());
}
mContactsSelectedLayout.removeAllViews();
if (mSearchAdapter.getContactsSelectedList().size() > 0) {
for (ContactAddress ca : mSearchAdapter.getContactsSelectedList()) {
addSelectedContactAddress(ca);
}
}
}
private void updateList() {
mSearchAdapter.searchContacts(mSearchField.getQuery().toString());
mSearchAdapter.notifyDataSetChanged();
}
private void updateListSelected() {
if (mSearchAdapter.getContactsSelectedList().size() > 0) {
mContactsSelectLayout.invalidate();
mNextButton.setEnabled(true);
} else {
mNextButton.setEnabled(false);
}
}
private void resetAndResearch() {
ContactsManager.getInstance().getMagicSearch().resetSearchCache();
mSearchAdapter.searchContacts(mSearchField.getQuery().toString());
}
private void addSelectedContactAddress(ContactAddress ca) {
View viewContact =
LayoutInflater.from(LinphoneActivity.instance())
.inflate(R.layout.contact_selected, null);
if (ca.getContact() != null) {
String name =
(ca.getContact().getFullName() != null
&& !ca.getContact().getFullName().isEmpty())
? ca.getContact().getFullName()
: (ca.getDisplayName() != null)
? ca.getDisplayName()
: (ca.getUsername() != null) ? ca.getUsername() : "";
((TextView) viewContact.findViewById(R.id.sipUri)).setText(name);
} else {
((TextView) viewContact.findViewById(R.id.sipUri))
.setText(ca.getAddressAsDisplayableString());
}
View removeContact = viewContact.findViewById(R.id.contactChatDelete);
removeContact.setTag(ca);
removeContact.setOnClickListener(this);
viewContact.setOnClickListener(this);
ca.setView(viewContact);
mContactsSelectedLayout.addView(viewContact);
mContactsSelectedLayout.invalidate();
}
private void updateContactsClick(ContactAddress ca) {
boolean isSelected = mSearchAdapter.toggleContactSelection(ca);
if (isSelected) {
ContactSelectView csv = new ContactSelectView(LinphoneActivity.instance());
csv.setListener(this);
csv.setContactName(ca);
addSelectedContactAddress(ca);
} else {
mContactsSelectedLayout.removeAllViews();
for (ContactAddress contactAddress : mSearchAdapter.getContactsSelectedList()) {
if (contactAddress.getView() != null)
mContactsSelectedLayout.addView(contactAddress.getView());
}
}
mContactsSelectedLayout.invalidate();
}
private void addOrRemoveContactFromSelection(ContactAddress ca) {
updateContactsClick(ca);
mSearchAdapter.notifyDataSetChanged();
updateListSelected();
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mSearchAdapter.getContactsSelectedList().size() > 0) {
outState.putSerializable("selectedContacts", mSearchAdapter.getContactsSelectedList());
}
outState.putBoolean("onlySipContact", mOnlyDisplayLinphoneContacts);
super.onSaveInstanceState(outState);
}
@Override
public void onClick(View view) {
int id = view.getId();
if (id == R.id.all_contacts) {
mOnlyDisplayLinphoneContacts = false;
mSearchAdapter.setOnlySipContact(mOnlyDisplayLinphoneContacts);
mAllContactsSelected.setVisibility(View.VISIBLE);
mAllContactsButton.setEnabled(false);
mLinphoneContactsButton.setEnabled(true);
mLinphoneContactsSelected.setVisibility(View.INVISIBLE);
updateList();
resetAndResearch();
} else if (id == R.id.linphone_contacts) {
mSearchAdapter.setOnlySipContact(true);
mLinphoneContactsSelected.setVisibility(View.VISIBLE);
mLinphoneContactsButton.setEnabled(false);
mOnlyDisplayLinphoneContacts = true;
mAllContactsButton.setEnabled(mOnlyDisplayLinphoneContacts);
mAllContactsSelected.setVisibility(View.INVISIBLE);
updateList();
resetAndResearch();
} else if (id == R.id.back) {
if (LinphoneActivity.instance().isTablet()) {
LinphoneActivity.instance().goToChatList();
} else {
mContactsSelectedLayout.removeAllViews();
LinphoneActivity.instance().popBackStack();
}
} else if (id == R.id.next) {
if (mChatRoomAddress == null && mChatRoomSubject == null) {
mContactsSelectedLayout.removeAllViews();
LinphoneActivity.instance()
.goToChatGroupInfos(
null,
mSearchAdapter.getContactsSelectedList(),
null,
true,
false,
mShareInfos,
mSecurityToggle.isChecked());
} else {
LinphoneActivity.instance()
.goToChatGroupInfos(
mChatRoomAddress,
mSearchAdapter.getContactsSelectedList(),
mChatRoomSubject,
true,
true,
mShareInfos,
mSecurityToggle.isChecked());
}
} else if (id == R.id.clearSearchField) {
mSearchField.setQuery("", false);
mSearchAdapter.searchContacts("");
} else if (id == R.id.contactChatDelete) {
ContactAddress ca = (ContactAddress) view.getTag();
addOrRemoveContactFromSelection(ca);
}
}
@Override
public void onItemClicked(int position) {
SearchResult searchResult = mSearchAdapter.getContacts().get(position);
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
ProxyConfig lpc = lc.getDefaultProxyConfig();
boolean createEncryptedChatRoom = mSecurityToggle.isChecked();
if (createEncryptedChatRoom && !searchResult.hasCapability(FriendCapability.LimeX3Dh)) {
Log.w(
"[Chat Room Creation] Contact "
+ searchResult.getFriend()
+ " doesn't have LIME X3DH capability !");
return;
} else if (mCreateGroupChatRoom
&& !searchResult.hasCapability(FriendCapability.GroupChat)) {
Log.w(
"[Chat Room Creation] Contact "
+ searchResult.getFriend()
+ " doesn't have group chat capability !");
return;
}
if (lpc == null || lpc.getConferenceFactoryUri() == null || !mCreateGroupChatRoom) {
Address address = searchResult.getAddress();
if (address == null) {
Log.w(
"[Chat Room Creation] Using search result without an address, trying with phone number...");
address = lc.interpretUrl(searchResult.getPhoneNumber());
}
if (address == null) {
Log.e("[Chat Room Creation] Can't create a chat room without a valid address !");
return;
}
if (lpc != null && lpc.getIdentityAddress().weakEqual(address)) {
Log.e("[Chat Room Creation] Can't create a 1-to-1 chat room with myself !");
return;
}
if (createEncryptedChatRoom && lpc != null && lpc.getConferenceFactoryUri() != null) {
mChatRoom = lc.findOneToOneChatRoom(lpc.getIdentityAddress(), address, true);
if (mChatRoom != null) {
LinphoneActivity.instance()
.goToChat(
mChatRoom.getLocalAddress().asStringUriOnly(),
mChatRoom.getPeerAddress().asStringUriOnly(),
mShareInfos);
} else {
ChatRoomParams params = lc.createDefaultChatRoomParams();
// This will set the backend to FlexisipChat automatically
params.enableEncryption(true);
params.enableGroup(false);
Address participants[] = new Address[1];
participants[0] = address;
mChatRoom =
lc.createChatRoom(
params,
getString(R.string.dummy_group_chat_subject),
participants);
if (mChatRoom != null) {
mChatRoom.addListener(mChatRoomCreationListener);
} else {
Log.w("[Chat Room Creation Fragment] createChatRoom returned null...");
mWaitLayout.setVisibility(View.GONE);
}
}
} else {
if (lpc != null
&& lpc.getConferenceFactoryUri() != null
&& !LinphonePreferences.instance().useBasicChatRoomFor1To1()) {
mChatRoom = lc.findOneToOneChatRoom(lpc.getIdentityAddress(), address, false);
if (mChatRoom == null) {
mWaitLayout.setVisibility(View.VISIBLE);
ChatRoomParams params = lc.createDefaultChatRoomParams();
params.enableEncryption(false);
params.enableGroup(false);
// We don't want a basic chat room
params.setBackend(ChatRoomBackend.FlexisipChat);
Address participants[] = new Address[1];
participants[0] = address;
mChatRoom =
lc.createChatRoom(
params,
getString(R.string.dummy_group_chat_subject),
participants);
if (mChatRoom != null) {
mChatRoom.addListener(mChatRoomCreationListener);
} else {
Log.w("[Chat Room Creation Fragment] createChatRoom returned null...");
mWaitLayout.setVisibility(View.GONE);
}
} else {
LinphoneActivity.instance()
.goToChat(
mChatRoom.getLocalAddress().asStringUriOnly(),
mChatRoom.getPeerAddress().asStringUriOnly(),
mShareInfos);
}
} else {
ChatRoom chatRoom = lc.getChatRoom(address);
LinphoneActivity.instance()
.goToChat(
chatRoom.getLocalAddress().asStringUriOnly(),
chatRoom.getPeerAddress().asStringUriOnly(),
mShareInfos);
}
}
} else {
LinphoneContact c =
searchResult.getFriend() != null
? (LinphoneContact) searchResult.getFriend().getUserData()
: null;
if (c == null) {
c = ContactsManager.getInstance().findContactFromAddress(searchResult.getAddress());
if (c == null) {
c =
ContactsManager.getInstance()
.findContactFromPhoneNumber(searchResult.getPhoneNumber());
}
}
addOrRemoveContactFromSelection(
new ContactAddress(
c,
searchResult.getAddress().asStringUriOnly(),
searchResult.getPhoneNumber(),
searchResult.getFriend() != null));
}
}
@Override
public void onContactsUpdated() {
updateList();
}
}

View file

@ -0,0 +1,198 @@
/*
ChatRoomViewHolder.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.chat;
import android.content.Context;
import android.view.View;
import android.widget.CheckBox;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomCapabilities;
import org.linphone.core.Content;
import org.linphone.core.Participant;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ChatRoomViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnLongClickListener {
public final TextView lastMessageView;
public final TextView date;
public final TextView displayName;
public final TextView unreadMessages;
public final CheckBox delete;
public final RelativeLayout avatarLayout;
private final Context mContext;
private final ClickListener mListener;
public ChatRoomViewHolder(Context context, View itemView, ClickListener listener) {
super(itemView);
mContext = context;
lastMessageView = itemView.findViewById(R.id.lastMessage);
date = itemView.findViewById(R.id.date);
displayName = itemView.findViewById(R.id.sipUri);
unreadMessages = itemView.findViewById(R.id.unreadMessages);
delete = itemView.findViewById(R.id.delete_chatroom);
avatarLayout = itemView.findViewById(R.id.avatar_layout);
mListener = listener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
public void bindChatRoom(ChatRoom room) {
ChatMessage lastMessage = room.getLastMessageInHistory();
if (lastMessage != null) {
StringBuilder messageContent = new StringBuilder();
for (Content c : lastMessage.getContents()) {
if (c.isFile() || c.isFileTransfer()) {
messageContent.append(c.getName()).append(" ");
} else if (c.isText()) {
messageContent.insert(0, c.getStringBuffer() + " ");
}
}
lastMessageView.setText(getSender(room) + messageContent);
date.setText(
LinphoneUtils.timestampToHumanDate(
mContext,
room.getLastUpdateTime(),
R.string.messages_list_date_format));
} else {
date.setText("");
lastMessageView.setText("");
}
displayName.setText(getContact(room));
unreadMessages.setText(String.valueOf(room.getUnreadMessagesCount()));
getAvatar(room);
}
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClicked(getAdapterPosition());
}
}
public boolean onLongClick(View v) {
if (mListener != null) {
return mListener.onItemLongClicked(getAdapterPosition());
}
return false;
}
public String getSender(ChatRoom mRoom) {
if (mRoom.getLastMessageInHistory() != null) {
LinphoneContact contact =
ContactsManager.getInstance()
.findContactFromAddress(
mRoom.getLastMessageInHistory().getFromAddress());
if (contact != null) {
return (contact.getFullName() + mContext.getString(R.string.separator));
}
return (LinphoneUtils.getAddressDisplayName(
mRoom.getLastMessageInHistory().getFromAddress())
+ mContext.getString(R.string.separator));
}
return null;
}
public String getContact(ChatRoom mRoom) {
Address contactAddress = mRoom.getPeerAddress();
if (mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())
&& mRoom.getParticipants().length > 0) {
contactAddress = mRoom.getParticipants()[0].getAddress();
}
if (mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
LinphoneContact contact;
contact = ContactsManager.getInstance().findContactFromAddress(contactAddress);
if (contact != null) {
return contact.getFullName();
}
return LinphoneUtils.getAddressDisplayName(contactAddress);
}
return mRoom.getSubject();
}
public void getAvatar(ChatRoom mRoom) {
if (mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
LinphoneContact contact = null;
if (mRoom.hasCapability(ChatRoomCapabilities.Basic.toInt())) {
contact =
ContactsManager.getInstance()
.findContactFromAddress(mRoom.getPeerAddress());
} else {
Participant[] participants = mRoom.getParticipants();
if (participants != null && participants.length > 0) {
contact =
ContactsManager.getInstance()
.findContactFromAddress(participants[0].getAddress());
}
}
if (contact != null) {
if (mRoom.hasCapability(ChatRoomCapabilities.Encrypted.toInt())) {
ContactAvatar.displayAvatar(contact, mRoom.getSecurityLevel(), avatarLayout);
} else {
ContactAvatar.displayAvatar(contact, avatarLayout);
}
} else {
Address remoteAddr = null;
if (mRoom.hasCapability(ChatRoomCapabilities.Encrypted.toInt())) {
Participant[] participants = mRoom.getParticipants();
if (participants.length > 0) {
remoteAddr = participants[0].getAddress();
} else {
// TODO: error
}
} else {
remoteAddr = mRoom.getPeerAddress();
}
String username = LinphoneUtils.getAddressDisplayName(remoteAddr);
if (mRoom.hasCapability(ChatRoomCapabilities.Encrypted.toInt())) {
ContactAvatar.displayAvatar(username, mRoom.getSecurityLevel(), avatarLayout);
} else {
ContactAvatar.displayAvatar(username, avatarLayout);
}
}
} else {
if (mRoom.hasCapability(ChatRoomCapabilities.Encrypted.toInt())) {
ContactAvatar.displayGroupChatAvatar(mRoom.getSecurityLevel(), avatarLayout);
} else {
ContactAvatar.displayGroupChatAvatar(avatarLayout);
}
}
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}

View file

@ -23,25 +23,29 @@ import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.linphone.LinphoneManager;
import org.linphone.core.ChatRoom;
import org.linphone.ui.SelectableAdapter;
import org.linphone.ui.SelectableHelper;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.ChatRoom;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper;
public class ChatRoomsAdapter extends SelectableAdapter<ChatRoomViewHolder> { public class ChatRoomsAdapter extends SelectableAdapter<ChatRoomViewHolder> {
private Context mContext; private final Context mContext;
public List<ChatRoom> mRooms; private List<ChatRoom> mRooms;
private int mItemResource; private final int mItemResource;
private ChatRoomViewHolder.ClickListener mClickListener; private final ChatRoomViewHolder.ClickListener mClickListener;
public ChatRoomsAdapter(Context context, int itemResource, List<ChatRoom> rooms, ChatRoomViewHolder.ClickListener clickListener, SelectableHelper helper) { public ChatRoomsAdapter(
Context context,
int itemResource,
List<ChatRoom> rooms,
ChatRoomViewHolder.ClickListener clickListener,
SelectableHelper helper) {
super(helper); super(helper);
mClickListener = clickListener; mClickListener = clickListener;
mRooms = rooms; mRooms = rooms;
@ -51,8 +55,7 @@ public class ChatRoomsAdapter extends SelectableAdapter<ChatRoomViewHolder> {
@Override @Override
public ChatRoomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ChatRoomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext()).inflate(mItemResource, parent, false);
.inflate(mItemResource, parent, false);
return new ChatRoomViewHolder(mContext, view, mClickListener); return new ChatRoomViewHolder(mContext, view, mClickListener);
} }
@ -60,21 +63,33 @@ public class ChatRoomsAdapter extends SelectableAdapter<ChatRoomViewHolder> {
public void onBindViewHolder(ChatRoomViewHolder holder, int position) { public void onBindViewHolder(ChatRoomViewHolder holder, int position) {
ChatRoom room = mRooms.get(position); ChatRoom room = mRooms.get(position);
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.INVISIBLE); holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.INVISIBLE);
holder.unreadMessages.setVisibility(isEditionEnabled() ? View.INVISIBLE : (room.getUnreadMessagesCount() > 0 ? View.VISIBLE : View.INVISIBLE)); holder.unreadMessages.setVisibility(
isEditionEnabled()
? View.INVISIBLE
: (room.getUnreadMessagesCount() > 0 ? View.VISIBLE : View.INVISIBLE));
holder.delete.setChecked(isSelected(position)); holder.delete.setChecked(isSelected(position));
holder.bindChatRoom(room); holder.bindChatRoom(room);
} }
public void refresh() { public void refresh() {
mRooms = new ArrayList<>(Arrays.asList(LinphoneManager.getLc().getChatRooms())); ChatRoom[] rooms = LinphoneManager.getLc().getChatRooms();
Collections.sort(mRooms, new Comparator<ChatRoom>() { if (mContext.getResources().getBoolean(R.bool.hide_empty_one_to_one_chat_rooms)) {
public int compare(ChatRoom cr1, ChatRoom cr2) { mRooms = LinphoneUtils.removeEmptyOneToOneChatRooms(rooms);
long timeDiff = cr1.getLastUpdateTime() - cr2.getLastUpdateTime(); } else {
if (timeDiff > 0) return -1; mRooms = Arrays.asList(rooms);
else if (timeDiff == 0) return 0; }
return 1;
} Collections.sort(
}); mRooms,
new Comparator<ChatRoom>() {
public int compare(ChatRoom cr1, ChatRoom cr2) {
long timeDiff = cr1.getLastUpdateTime() - cr2.getLastUpdateTime();
if (timeDiff > 0) return -1;
else if (timeDiff == 0) return 0;
return 1;
}
});
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -83,10 +98,7 @@ public class ChatRoomsAdapter extends SelectableAdapter<ChatRoomViewHolder> {
notifyDataSetChanged(); notifyDataSetChanged();
} }
/** /** Adapter's methods */
* Adapter's methods
*/
@Override @Override
public int getItemCount() { public int getItemCount() {
return mRooms.size(); return mRooms.size();

View file

@ -0,0 +1,345 @@
/*
ChatRoomsFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.chat;
import static org.linphone.fragments.FragmentsAvailable.CHAT_LIST;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.EventLog;
import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.SelectableHelper;
public class ChatRoomsFragment extends Fragment
implements ContactsUpdatedListener,
ChatRoomViewHolder.ClickListener,
SelectableHelper.DeleteListener {
private RecyclerView mChatRoomsList;
private ImageView mNewDiscussionButton, mNewGroupDiscussionButton, mBackToCallButton;
private ChatRoomsAdapter mChatRoomsAdapter;
private CoreListenerStub mListener;
private RelativeLayout mWaitLayout;
private int mChatRoomDeletionPendingCount;
private ChatRoomListenerStub mChatRoomListener;
private Context mContext;
private List<ChatRoom> mRooms;
private SelectableHelper mSelectionHelper;
private TextView mNoChatHistory;
@Override
public View onCreateView(
final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity().getApplicationContext();
View view = inflater.inflate(R.layout.chatlist, container, false);
mChatRoomsList = view.findViewById(R.id.chatList);
mWaitLayout = view.findViewById(R.id.waitScreen);
mNewDiscussionButton = view.findViewById(R.id.new_discussion);
mNewGroupDiscussionButton = view.findViewById(R.id.new_group_discussion);
mBackToCallButton = view.findViewById(R.id.back_in_call);
mNoChatHistory = view.findViewById(R.id.noChatHistory);
ChatRoom[] rooms = LinphoneManager.getLc().getChatRooms();
if (mContext.getResources().getBoolean(R.bool.hide_empty_one_to_one_chat_rooms)) {
mRooms = LinphoneUtils.removeEmptyOneToOneChatRooms(rooms);
} else {
mRooms = Arrays.asList(rooms);
}
mSelectionHelper = new SelectableHelper(view, this);
mChatRoomsAdapter =
new ChatRoomsAdapter(
mContext, R.layout.chatlist_cell, mRooms, this, mSelectionHelper);
mChatRoomsList.setAdapter(mChatRoomsAdapter);
mSelectionHelper.setAdapter(mChatRoomsAdapter);
mSelectionHelper.setDialogMessage(R.string.chat_room_delete_dialog);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(mContext);
mChatRoomsList.setLayoutManager(layoutManager);
DividerItemDecoration dividerItemDecoration =
new DividerItemDecoration(
mChatRoomsList.getContext(),
((LinearLayoutManager) layoutManager).getOrientation());
dividerItemDecoration.setDrawable(
getActivity()
.getApplicationContext()
.getResources()
.getDrawable(R.drawable.divider));
mChatRoomsList.addItemDecoration(dividerItemDecoration);
mWaitLayout.setVisibility(View.GONE);
mNewDiscussionButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle extras = null;
if (getArguments() != null) {
Log.i("[ChatRooms] Forwarding arguments to new chat room");
extras = (Bundle) getArguments().clone();
getArguments().clear();
}
LinphoneActivity.instance()
.goToChatCreator(null, null, null, false, extras, false, false);
}
});
mNewGroupDiscussionButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle extras = null;
if (getArguments() != null) {
Log.i("[ChatRooms] Forwarding arguments to new group chat room");
extras = (Bundle) getArguments().clone();
getArguments().clear();
}
LinphoneActivity.instance()
.goToChatCreator(null, null, null, false, extras, true, false);
}
});
mBackToCallButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
LinphoneActivity.instance()
.resetClassicMenuLayoutAndGoBackToCallIfStillRunning();
}
});
mListener =
new CoreListenerStub() {
@Override
public void onMessageReceived(Core lc, ChatRoom cr, ChatMessage message) {
refreshChatRoomsList();
}
@Override
public void onChatRoomStateChanged(Core lc, ChatRoom cr, ChatRoom.State state) {
if (state == ChatRoom.State.Created) {
refreshChatRoomsList();
}
}
};
mChatRoomListener =
new ChatRoomListenerStub() {
@Override
public void onStateChanged(ChatRoom room, ChatRoom.State state) {
super.onStateChanged(room, state);
if (state == ChatRoom.State.Deleted
|| state == ChatRoom.State.TerminationFailed) {
mChatRoomDeletionPendingCount -= 1;
if (state == ChatRoom.State.TerminationFailed) {
// TODO error message
}
if (mChatRoomDeletionPendingCount == 0) {
mWaitLayout.setVisibility(View.GONE);
refreshChatRoomsList();
}
}
}
};
if (getArguments() != null) {
String fileSharedUri = getArguments().getString("fileSharedUri");
String messageSharedUri = getArguments().getString("messageDraft");
if (fileSharedUri != null || messageSharedUri != null) {
Toast.makeText(
LinphoneActivity.instance(),
R.string.toast_choose_chat_room_for_sharing,
Toast.LENGTH_LONG)
.show();
}
Log.i("[ChatRooms] Arguments found: " + messageSharedUri + " / " + fileSharedUri);
}
return view;
}
@Override
public void onItemClicked(int position) {
if (mChatRoomsAdapter.isEditionEnabled()) {
mChatRoomsAdapter.toggleSelection(position);
} else {
ChatRoom room = (ChatRoom) mChatRoomsAdapter.getItem(position);
Bundle extras = null;
if (getArguments() != null) {
Log.i("[ChatRooms] Forwarding arguments to existing chat room");
extras = (Bundle) getArguments().clone();
getArguments().clear();
}
LinphoneActivity.instance()
.goToChat(
room.getLocalAddress().asStringUriOnly(),
room.getPeerAddress().asString(),
extras);
}
}
@Override
public boolean onItemLongClicked(int position) {
if (!mChatRoomsAdapter.isEditionEnabled()) {
mSelectionHelper.enterEditionMode();
}
mChatRoomsAdapter.toggleSelection(position);
return true;
}
private void refreshChatRoomsList() {
mChatRoomsAdapter.refresh();
mNoChatHistory.setVisibility(
mChatRoomsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
public void displayFirstChat() {
ChatRoomsAdapter adapter = (ChatRoomsAdapter) mChatRoomsList.getAdapter();
if (adapter != null && adapter.getItemCount() > 0) {
ChatRoom room = (ChatRoom) adapter.getItem(0);
LinphoneActivity.instance()
.goToChat(
room.getLocalAddress().asStringUriOnly(),
room.getPeerAddress().asStringUriOnly(),
null);
} else {
LinphoneActivity.instance().displayEmptyFragment();
}
}
public void invalidate() {
if (mChatRoomsAdapter != null) {
mChatRoomsAdapter.notifyDataSetChanged();
}
}
@Override
public void onResume() {
super.onResume();
ContactsManager.getInstance().addContactsListener(this);
if (LinphoneManager.getLc().getCallsNb() > 0) {
mBackToCallButton.setVisibility(View.VISIBLE);
} else {
mBackToCallButton.setVisibility(View.INVISIBLE);
}
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.CHAT_LIST);
}
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
}
refreshChatRoomsList();
ProxyConfig lpc = lc.getDefaultProxyConfig();
mNewGroupDiscussionButton.setVisibility(
(lpc != null && lpc.getConferenceFactoryUri() != null) ? View.VISIBLE : View.GONE);
}
@Override
public void onPause() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
ContactsManager.getInstance().removeContactsListener(this);
mChatRoomsAdapter.clear();
super.onPause();
}
@Override
public void onDeleteSelection(Object[] objectsToDelete) {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
mChatRoomDeletionPendingCount = objectsToDelete.length;
for (Object obj : objectsToDelete) {
ChatRoom room = (ChatRoom) obj;
for (EventLog eventLog : room.getHistoryEvents(0)) {
if (eventLog.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage message = eventLog.getChatMessage();
if (message.getAppdata() != null && !message.isOutgoing()) {
File file = new File(message.getAppdata());
if (file.exists()) {
file.delete(); // Delete downloaded file from incoming message that
// will be deleted
}
}
}
}
room.addListener(mChatRoomListener);
lc.deleteChatRoom(room);
}
if (mChatRoomDeletionPendingCount > 0) {
mWaitLayout.setVisibility(View.VISIBLE);
}
LinphoneActivity.instance()
.displayMissedChats(LinphoneManager.getInstance().getUnreadMessageCount());
}
@Override
public void onContactsUpdated() {
if (!LinphoneActivity.isInstanciated()
|| LinphoneActivity.instance().getCurrentFragment() != CHAT_LIST) return;
ChatRoomsAdapter adapter = (ChatRoomsAdapter) mChatRoomsList.getAdapter();
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
}

View file

@ -0,0 +1,78 @@
package org.linphone.chat;
/*
ChatScrollListener.java
Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
abstract class ChatScrollListener extends RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before mLoading more.
private final int mVisibleThreshold = 5;
// The total number of items in the dataset after the last load
private int mPreviousTotalItemCount = 0;
// True if we are still waiting for the last set of data to load.
private boolean mLoading = true;
private final LinearLayoutManager mLayoutManager;
public ChatScrollListener(LinearLayoutManager layoutManager) {
mLayoutManager = layoutManager;
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
@Override
public void onScrolled(RecyclerView view, int dx, int dy) {
int lastVisibleItemPosition;
int totalItemCount = mLayoutManager.getItemCount();
lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < mPreviousTotalItemCount) {
this.mPreviousTotalItemCount = totalItemCount;
if (totalItemCount == 0) {
this.mLoading = true;
}
}
// If its still mLoading, we check to see if the dataset count has
// changed, if so we conclude it has finished mLoading and update the current page
// number and total item count.
if (mLoading && (totalItemCount > mPreviousTotalItemCount)) {
mLoading = false;
mPreviousTotalItemCount = totalItemCount;
}
// If it isnt currently mLoading, we check to see if we have breached
// the mVisibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!mLoading && (lastVisibleItemPosition + mVisibleThreshold) > totalItemCount) {
onLoadMore(totalItemCount);
mLoading = true;
}
}
// Defines the process for actually mLoading more data based on page
protected abstract void onLoadMore(int totalItemsCount);
}

View file

@ -1,11 +1,8 @@
package org.linphone.compatibility; package org.linphone.chat;
import android.annotation.TargetApi; /*
import android.app.AlarmManager; DeviceChildViewHolder.java
import android.app.PendingIntent; Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France
/*ApiNineteenPlus.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -22,13 +19,17 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
@TargetApi(19) import android.view.View;
public class ApiNineteenPlus { import android.widget.ImageView;
public static void scheduleAlarm(AlarmManager alarmManager, int type, long triggerAtMillis, PendingIntent operation) { import android.widget.TextView;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { import org.linphone.R;
alarmManager.setExact(type, triggerAtMillis, operation);
} else { class DeviceChildViewHolder {
alarmManager.set(type, triggerAtMillis, operation); public final TextView deviceName;
} public final ImageView securityLevel;
public DeviceChildViewHolder(View v) {
deviceName = v.findViewById(R.id.name);
securityLevel = v.findViewById(R.id.security_level);
} }
} }

View file

@ -0,0 +1,40 @@
package org.linphone.chat;
/*
DeviceGroupViewHolder.java
Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.linphone.R;
class DeviceGroupViewHolder {
public final RelativeLayout avatarLayout;
public final TextView participantName, sipUri;
public final ImageView groupExpander, securityLevel;
public DeviceGroupViewHolder(View v) {
avatarLayout = v.findViewById(R.id.avatar_layout);
participantName = v.findViewById(R.id.name);
sipUri = v.findViewById(R.id.sipUri);
groupExpander = v.findViewById(R.id.dropdown);
securityLevel = v.findViewById(R.id.device_security_level);
}
}

View file

@ -0,0 +1,249 @@
package org.linphone.chat;
/*
DevicesAdapter.java
Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import java.util.ArrayList;
import java.util.List;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatRoomSecurityLevel;
import org.linphone.core.Participant;
import org.linphone.core.ParticipantDevice;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
class DevicesAdapter extends BaseExpandableListAdapter {
private final Context mContext;
private List<Participant> mParticipants;
private boolean mOnlyDisplayChildsAsGroups;
public DevicesAdapter(Context context) {
mContext = context;
mParticipants = new ArrayList<>();
mOnlyDisplayChildsAsGroups = false;
}
public void updateListItems(List<Participant> participants, boolean childsAsGroups) {
mOnlyDisplayChildsAsGroups = childsAsGroups;
mParticipants = participants;
notifyDataSetChanged();
}
@Override
public View getGroupView(
int groupPosition, boolean isExpanded, View view, ViewGroup viewGroup) {
if (mOnlyDisplayChildsAsGroups) {
ParticipantDevice device = (ParticipantDevice) getGroup(groupPosition);
DeviceChildViewHolder holder = null;
if (view != null) {
Object possibleHolder = view.getTag();
if (possibleHolder instanceof DeviceChildViewHolder) {
holder = (DeviceChildViewHolder) possibleHolder;
}
} else {
LayoutInflater inflater = LayoutInflater.from(mContext);
view = inflater.inflate(R.layout.chat_device_cell_as_group, viewGroup, false);
}
if (holder == null) {
holder = new DeviceChildViewHolder(view);
view.setTag(holder);
}
holder.deviceName.setText(device.getName());
ChatRoomSecurityLevel level = device.getSecurityLevel();
switch (level) {
case Safe:
holder.securityLevel.setImageResource(R.drawable.security_2_indicator);
break;
case Encrypted:
holder.securityLevel.setImageResource(R.drawable.security_1_indicator);
break;
case ClearText:
case Unsafe:
default:
holder.securityLevel.setImageResource(R.drawable.security_alert_indicator);
break;
}
} else {
Participant participant = (Participant) getGroup(groupPosition);
DeviceGroupViewHolder holder = null;
if (view != null) {
Object possibleHolder = view.getTag();
if (possibleHolder instanceof DeviceGroupViewHolder) {
holder = (DeviceGroupViewHolder) possibleHolder;
}
} else {
LayoutInflater inflater = LayoutInflater.from(mContext);
view = inflater.inflate(R.layout.chat_device_group, viewGroup, false);
}
if (holder == null) {
holder = new DeviceGroupViewHolder(view);
view.setTag(holder);
}
Address participantAddress = participant.getAddress();
LinphoneContact contact =
ContactsManager.getInstance().findContactFromAddress(participantAddress);
if (contact != null) {
ContactAvatar.displayAvatar(
contact, participant.getSecurityLevel(), holder.avatarLayout);
holder.participantName.setText(contact.getFullName());
} else {
String displayName = LinphoneUtils.getAddressDisplayName(participantAddress);
ContactAvatar.displayAvatar(
displayName, participant.getSecurityLevel(), holder.avatarLayout);
holder.participantName.setText(displayName);
}
holder.sipUri.setText(participantAddress.asStringUriOnly());
if (!mContext.getResources().getBoolean(R.bool.show_sip_uri_in_chat)) {
holder.sipUri.setVisibility(View.GONE);
}
if (getChildrenCount(groupPosition) == 1) {
holder.securityLevel.setVisibility(View.VISIBLE);
holder.groupExpander.setVisibility(View.GONE);
ParticipantDevice device = (ParticipantDevice) getChild(groupPosition, 0);
ChatRoomSecurityLevel level = device.getSecurityLevel();
switch (level) {
case Safe:
holder.securityLevel.setImageResource(R.drawable.security_2_indicator);
break;
case Encrypted:
holder.securityLevel.setImageResource(R.drawable.security_1_indicator);
break;
case ClearText:
case Unsafe:
default:
holder.securityLevel.setImageResource(R.drawable.security_alert_indicator);
break;
}
} else {
holder.securityLevel.setVisibility(View.GONE);
holder.groupExpander.setVisibility(View.VISIBLE);
holder.groupExpander.setImageResource(
isExpanded ? R.drawable.chevron_list_open : R.drawable.chevron_list_close);
}
}
return view;
}
@Override
public View getChildView(
int groupPosition, int childPosition, boolean b, View view, ViewGroup viewGroup) {
ParticipantDevice device = (ParticipantDevice) getChild(groupPosition, childPosition);
DeviceChildViewHolder holder = null;
if (view != null) {
Object possibleHolder = view.getTag();
if (possibleHolder instanceof DeviceChildViewHolder) {
holder = (DeviceChildViewHolder) possibleHolder;
}
} else {
LayoutInflater inflater = LayoutInflater.from(mContext);
view = inflater.inflate(R.layout.chat_device_cell, viewGroup, false);
}
if (holder == null) {
holder = new DeviceChildViewHolder(view);
view.setTag(holder);
}
holder.deviceName.setText(device.getName());
ChatRoomSecurityLevel level = device.getSecurityLevel();
switch (level) {
case Safe:
holder.securityLevel.setImageResource(R.drawable.security_2_indicator);
break;
case Encrypted:
holder.securityLevel.setImageResource(R.drawable.security_1_indicator);
break;
case ClearText:
case Unsafe:
default:
holder.securityLevel.setImageResource(R.drawable.security_alert_indicator);
break;
}
return view;
}
@Override
public int getGroupCount() {
if (mParticipants.size() == 0) return 0;
return mOnlyDisplayChildsAsGroups
? mParticipants.get(0).getDevices().length
: mParticipants.size();
}
@Override
public int getChildrenCount(int groupPosition) {
if (mParticipants.size() == 0) return 0;
return mOnlyDisplayChildsAsGroups
? 0
: mParticipants.get(groupPosition).getDevices().length;
}
@Override
public Object getGroup(int groupPosition) {
if (mParticipants.size() == 0) return null;
return mOnlyDisplayChildsAsGroups
? mParticipants.get(0).getDevices()[groupPosition]
: mParticipants.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
if (mParticipants.size() == 0) return null;
return mParticipants.get(groupPosition).getDevices()[childPosition];
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
@Override
public boolean hasStableIds() {
return false;
}
}

View file

@ -0,0 +1,187 @@
/*
DevicesFragment.java
Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.chat;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import java.util.Arrays;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.call.CallManager;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomCapabilities;
import org.linphone.core.Core;
import org.linphone.core.ParticipantDevice;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.utils.LinphoneUtils;
public class DevicesFragment extends Fragment {
private LayoutInflater mInflater;
private ImageView mBackButton;
private TextView mTitle;
private ExpandableListView mExpandableList;
private DevicesAdapter mAdapter;
private String mLocalSipUri, mRoomUri;
private Address mLocalSipAddr, mRoomAddr;
private ChatRoom mRoom;
private boolean mOnlyDisplayChilds;
@Nullable
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mLocalSipUri = getArguments().getString("LocalSipUri");
mLocalSipAddr = LinphoneManager.getLc().createAddress(mLocalSipUri);
mRoomUri = getArguments().getString("RemoteSipUri");
mRoomAddr = LinphoneManager.getLc().createAddress(mRoomUri);
}
mInflater = inflater;
View view = mInflater.inflate(R.layout.chat_devices, container, false);
mOnlyDisplayChilds = false;
mExpandableList = view.findViewById(R.id.devices_list);
mExpandableList.setOnChildClickListener(
new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(
ExpandableListView expandableListView,
View view,
int groupPosition,
int childPosition,
long l) {
ParticipantDevice device =
(ParticipantDevice) mAdapter.getChild(groupPosition, childPosition);
CallManager.getInstance().inviteAddress(device.getAddress(), true);
return false;
}
});
mExpandableList.setOnGroupClickListener(
new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(
ExpandableListView expandableListView,
View view,
int groupPosition,
long l) {
if (mOnlyDisplayChilds) {
// in this case groups are childs, so call on click
ParticipantDevice device =
(ParticipantDevice) mAdapter.getGroup(groupPosition);
CallManager.getInstance().inviteAddress(device.getAddress(), true);
return true;
} else {
if (mAdapter.getChildrenCount(groupPosition) == 1) {
ParticipantDevice device =
(ParticipantDevice) mAdapter.getChild(groupPosition, 0);
CallManager.getInstance().inviteAddress(device.getAddress(), true);
return true;
}
}
return false;
}
});
initChatRoom();
mTitle = view.findViewById(R.id.title);
initHeader();
mBackButton = view.findViewById(R.id.back);
mBackButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (LinphoneActivity.instance().isTablet()) {
LinphoneActivity.instance().goToChat(mLocalSipUri, mRoomUri, null);
} else {
LinphoneActivity.instance().onBackPressed();
}
}
});
return view;
}
@Override
public void onResume() {
super.onResume();
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.CONTACT_DEVICES);
}
initValues();
}
private void initChatRoom() {
Core core = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
mRoom = core.getChatRoom(mRoomAddr, mLocalSipAddr);
}
private void initHeader() {
if (mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
Address remoteParticipantAddr = mRoomAddr;
if (mRoom.getParticipants().length > 0) {
remoteParticipantAddr = mRoom.getParticipants()[0].getAddress();
}
LinphoneContact c =
ContactsManager.getInstance().findContactFromAddress(remoteParticipantAddr);
String displayName;
if (c != null) {
displayName = c.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteParticipantAddr);
}
mTitle.setText(getString(R.string.chat_room_devices).replace("%s", displayName));
}
}
private void initValues() {
if (mAdapter == null) {
mAdapter = new DevicesAdapter(getActivity());
mExpandableList.setAdapter(mAdapter);
}
if (mRoom == null) {
initChatRoom();
}
if (mRoom != null && mRoom.getNbParticipants() > 0) {
mOnlyDisplayChilds = mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt());
mAdapter.updateListItems(Arrays.asList(mRoom.getParticipants()), mOnlyDisplayChilds);
}
}
}

View file

@ -0,0 +1,178 @@
/*
GroupInfoAdapter.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.chat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.R;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.ChatRoom;
import org.linphone.core.Participant;
import org.linphone.views.ContactAvatar;
class GroupInfoAdapter extends RecyclerView.Adapter<GroupInfoViewHolder> {
private List<ContactAddress> mItems;
private View.OnClickListener mDeleteListener;
private boolean mHideAdminFeatures;
private ChatRoom mChatRoom;
public GroupInfoAdapter(
List<ContactAddress> items, boolean hideAdminFeatures, boolean isCreation) {
mItems = items;
mHideAdminFeatures = hideAdminFeatures || isCreation;
}
@NonNull
@Override
public GroupInfoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.chat_infos_cell, parent, false);
return new GroupInfoViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull final GroupInfoViewHolder holder, int position) {
final ContactAddress ca = (ContactAddress) getItem(position);
LinphoneContact c = ca.getContact();
holder.name.setText(
(c != null && c.getFullName() != null)
? c.getFullName()
: (ca.getDisplayName() != null) ? ca.getDisplayName() : ca.getUsername());
if (c != null) {
ContactAvatar.displayAvatar(c, holder.avatarLayout);
} else {
ContactAvatar.displayAvatar(holder.name.getText().toString(), holder.avatarLayout);
}
holder.sipUri.setText(ca.getAddressAsDisplayableString());
if (!LinphoneActivity.instance().getResources().getBoolean(R.bool.show_sip_uri_in_chat)) {
holder.sipUri.setVisibility(View.GONE);
holder.name.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.sipUri.setVisibility(
holder.sipUri.getVisibility() == View.VISIBLE
? View.GONE
: View.VISIBLE);
}
});
}
holder.delete.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mDeleteListener != null) {
mDeleteListener.onClick(view);
}
}
});
holder.delete.setTag(ca);
holder.isAdmin.setVisibility(ca.isAdmin() ? View.VISIBLE : View.GONE);
holder.isNotAdmin.setVisibility(ca.isAdmin() ? View.GONE : View.VISIBLE);
holder.isAdmin.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
holder.isNotAdmin.setVisibility(View.VISIBLE);
holder.isAdmin.setVisibility(View.GONE);
ca.setAdmin(false);
}
});
holder.isNotAdmin.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
holder.isNotAdmin.setVisibility(View.GONE);
holder.isAdmin.setVisibility(View.VISIBLE);
ca.setAdmin(true);
}
});
holder.delete.setVisibility(View.VISIBLE);
if (mHideAdminFeatures) {
holder.delete.setVisibility(View.INVISIBLE);
holder.isAdmin.setOnClickListener(
null); // Do not allow not admin to remove it's rights but display admins
holder.isNotAdmin.setVisibility(
View.GONE); // Hide not admin button for not admin participants
} else if (mChatRoom != null) {
boolean found = false;
for (Participant p : mChatRoom.getParticipants()) {
if (p.getAddress().weakEqual(ca.getAddress())) {
found = true;
break;
}
}
if (!found) {
holder.isNotAdmin.setVisibility(
View.GONE); // Hide not admin button for participant not yet added so
// even if user click it it won't have any effect
}
}
}
@Override
public int getItemCount() {
return mItems.size();
}
public void setChatRoom(ChatRoom room) {
mChatRoom = room;
}
private Object getItem(int i) {
return mItems.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
public void setOnDeleteClickListener(View.OnClickListener onClickListener) {
mDeleteListener = onClickListener;
}
public void updateDataSet(ArrayList<ContactAddress> mParticipants) {
mItems = mParticipants;
notifyDataSetChanged();
}
public void setAdminFeaturesVisible(boolean visible) {
mHideAdminFeatures = !visible;
notifyDataSetChanged();
}
}

View file

@ -0,0 +1,590 @@
/*
InfoGroupChatFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.chat;
import static android.content.Context.INPUT_METHOD_SERVICE;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomListener;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.ChatRoomParams;
import org.linphone.core.Core;
import org.linphone.core.EventLog;
import org.linphone.core.Participant;
import org.linphone.core.tools.Log;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.utils.LinphoneUtils;
public class GroupInfoFragment extends Fragment implements ChatRoomListener {
private ImageView mBackButton, mConfirmButton, mAddParticipantsButton;
private RelativeLayout mAddParticipantsLayout;
private Address mGroupChatRoomAddress;
private EditText mSubjectField;
private RecyclerView mParticipantsList;
private LinearLayout mLeaveGroupButton;
private RelativeLayout mWaitLayout;
private GroupInfoAdapter mAdapter;
private boolean mIsAlreadyCreatedGroup;
private boolean mIsEditionEnabled;
private ArrayList<ContactAddress> mParticipants;
private String mSubject;
private ChatRoom mChatRoom, mTempChatRoom;
private Dialog mAdminStateChangedDialog;
private ChatRoomListenerStub mChatRoomCreationListener;
private Bundle mShareInfos;
private Context mContext;
private LinearLayoutManager layoutManager;
private boolean mIsEncryptionEnabled;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.chat_infos, container, false);
if (getArguments() == null || getArguments().isEmpty()) {
return null;
}
mContext = getActivity().getApplicationContext();
mParticipants =
(ArrayList<ContactAddress>) getArguments().getSerializable("ContactAddress");
mGroupChatRoomAddress = null;
mChatRoom = null;
String address = getArguments().getString("groupChatRoomAddress");
if (address != null && address.length() > 0) {
mGroupChatRoomAddress = LinphoneManager.getLc().createAddress(address);
}
mIsAlreadyCreatedGroup = mGroupChatRoomAddress != null;
if (mIsAlreadyCreatedGroup) {
mChatRoom = LinphoneManager.getLc().getChatRoom(mGroupChatRoomAddress);
}
if (mChatRoom == null) mIsAlreadyCreatedGroup = false;
mIsEditionEnabled = getArguments().getBoolean("isEditionEnabled");
mSubject = getArguments().getString("subject");
if (mChatRoom != null && mChatRoom.hasBeenLeft()) {
mIsEditionEnabled = false;
}
mIsEncryptionEnabled = getArguments().getBoolean("encryptionEnabled", false);
mParticipantsList = view.findViewById(R.id.chat_room_participants);
mAdapter = new GroupInfoAdapter(mParticipants, !mIsEditionEnabled, !mIsAlreadyCreatedGroup);
mAdapter.setOnDeleteClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
ContactAddress ca = (ContactAddress) view.getTag();
mParticipants.remove(ca);
mAdapter.updateDataSet(mParticipants);
mParticipantsList.setAdapter(mAdapter);
mConfirmButton.setEnabled(
mSubjectField.getText().length() > 0 && mParticipants.size() > 0);
}
});
mParticipantsList.setAdapter(mAdapter);
mAdapter.setChatRoom(mChatRoom);
layoutManager = new LinearLayoutManager(mContext);
mParticipantsList.setLayoutManager(layoutManager);
// Divider between items
DividerItemDecoration dividerItemDecoration =
new DividerItemDecoration(
mParticipantsList.getContext(), layoutManager.getOrientation());
dividerItemDecoration.setDrawable(mContext.getResources().getDrawable(R.drawable.divider));
mParticipantsList.addItemDecoration(dividerItemDecoration);
String fileSharedUri = getArguments().getString("fileSharedUri");
String messageDraft = getArguments().getString("messageDraft");
if (fileSharedUri != null || messageDraft != null) {
Log.i("[GroupInfo] Forwarding arguments to group chat room");
mShareInfos = new Bundle();
}
if (fileSharedUri != null) mShareInfos.putString("fileSharedUri", fileSharedUri);
if (messageDraft != null) mShareInfos.putString("messageDraft", messageDraft);
mBackButton = view.findViewById(R.id.back);
mBackButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mIsAlreadyCreatedGroup) {
if (LinphoneActivity.instance().isTablet()) {
LinphoneActivity.instance()
.goToChat(
mChatRoom.getLocalAddress().asStringUriOnly(),
mGroupChatRoomAddress.asStringUriOnly(),
mShareInfos);
} else {
getFragmentManager().popBackStack();
}
} else {
LinphoneActivity.instance()
.goToChatCreator(
null,
mParticipants,
null,
true,
mShareInfos,
true,
mIsEncryptionEnabled);
}
}
});
mConfirmButton = view.findViewById(R.id.confirm);
mLeaveGroupButton = view.findViewById(R.id.leaveGroupLayout);
mLeaveGroupButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
final Dialog dialog =
LinphoneActivity.instance()
.displayDialog(getString(R.string.chat_room_leave_dialog));
Button delete = dialog.findViewById(R.id.dialog_delete_button);
delete.setText(getString(R.string.chat_room_leave_button));
Button cancel = dialog.findViewById(R.id.dialog_cancel_button);
delete.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mChatRoom != null) {
mChatRoom.leave();
LinphoneActivity.instance()
.goToChat(
mChatRoom
.getLocalAddress()
.asStringUriOnly(),
mGroupChatRoomAddress.asString(),
null);
} else {
Log.e(
"Can't leave, chatRoom for address "
+ mGroupChatRoomAddress.asString()
+ " is null...");
}
dialog.dismiss();
}
});
cancel.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
}
});
dialog.show();
}
});
mLeaveGroupButton.setVisibility(
mIsAlreadyCreatedGroup && mChatRoom.hasBeenLeft()
? View.GONE
: mIsAlreadyCreatedGroup ? View.VISIBLE : View.GONE);
mAddParticipantsLayout = view.findViewById(R.id.addParticipantsLayout);
mAddParticipantsLayout.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mIsEditionEnabled && mIsAlreadyCreatedGroup) {
LinphoneActivity.instance()
.goToChatCreator(
mGroupChatRoomAddress != null
? mGroupChatRoomAddress.asString()
: null,
mParticipants,
mSubject,
!mIsAlreadyCreatedGroup,
null,
true,
mIsEncryptionEnabled);
}
}
});
mAddParticipantsButton = view.findViewById(R.id.addParticipants);
mAddParticipantsButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mIsEditionEnabled && mIsAlreadyCreatedGroup) {
LinphoneActivity.instance()
.goToChatCreator(
mGroupChatRoomAddress != null
? mGroupChatRoomAddress.asString()
: null,
mParticipants,
mSubject,
!mIsAlreadyCreatedGroup,
null,
true,
mIsEncryptionEnabled);
}
}
});
mAddParticipantsButton.setVisibility(mIsAlreadyCreatedGroup ? View.VISIBLE : View.GONE);
mSubjectField = view.findViewById(R.id.subjectField);
mSubjectField.addTextChangedListener(
new TextWatcher() {
@Override
public void beforeTextChanged(
CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mConfirmButton.setEnabled(
mSubjectField.getText().length() > 0 && mParticipants.size() > 0);
}
});
mSubjectField.setText(mSubject);
mChatRoomCreationListener =
new ChatRoomListenerStub() {
@Override
public void onStateChanged(ChatRoom cr, ChatRoom.State newState) {
if (newState == ChatRoom.State.Created) {
mWaitLayout.setVisibility(View.GONE);
// This will remove both the creation fragment and the group info
// fragment from the back stack
getFragmentManager().popBackStack();
getFragmentManager().popBackStack();
LinphoneActivity.instance()
.goToChat(
cr.getLocalAddress().asStringUriOnly(),
cr.getPeerAddress().asStringUriOnly(),
mShareInfos);
} else if (newState == ChatRoom.State.CreationFailed) {
mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance().displayChatRoomError();
Log.e(
"Group chat room for address "
+ cr.getPeerAddress()
+ " has failed !");
}
}
};
mConfirmButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!mIsAlreadyCreatedGroup) {
mWaitLayout.setVisibility(View.VISIBLE);
Core core = LinphoneManager.getLc();
int i = 0;
Address[] participants = new Address[mParticipants.size()];
for (ContactAddress ca : mParticipants) {
participants[i] = ca.getAddress();
i++;
}
ChatRoomParams params = core.createDefaultChatRoomParams();
params.enableEncryption(mIsEncryptionEnabled);
params.enableGroup(true);
mTempChatRoom =
core.createChatRoom(
params,
mSubjectField.getText().toString(),
participants);
if (mTempChatRoom != null) {
mTempChatRoom.addListener(mChatRoomCreationListener);
} else {
Log.w("[Group Info Fragment] createChatRoom returned null...");
mWaitLayout.setVisibility(View.GONE);
}
} else {
// Subject
String newSubject = mSubjectField.getText().toString();
if (!newSubject.equals(mSubject)) {
mChatRoom.setSubject(newSubject);
}
// Participants removed
ArrayList<Participant> toRemove = new ArrayList<>();
for (Participant p : mChatRoom.getParticipants()) {
boolean found = false;
for (ContactAddress c : mParticipants) {
if (c.getAddress().weakEqual(p.getAddress())) {
found = true;
break;
}
}
if (!found) {
toRemove.add(p);
}
}
Participant[] participantsToRemove = new Participant[toRemove.size()];
toRemove.toArray(participantsToRemove);
mChatRoom.removeParticipants(participantsToRemove);
// Participants added
ArrayList<Address> toAdd = new ArrayList<>();
for (ContactAddress c : mParticipants) {
boolean found = false;
for (Participant p : mChatRoom.getParticipants()) {
if (p.getAddress().weakEqual(c.getAddress())) {
// Admin rights
if (c.isAdmin() != p.isAdmin()) {
mChatRoom.setParticipantAdminStatus(p, c.isAdmin());
}
found = true;
break;
}
}
if (!found) {
Address addr = c.getAddress();
if (addr != null) {
toAdd.add(addr);
} else {
// TODO error
}
}
}
Address[] participantsToAdd = new Address[toAdd.size()];
toAdd.toArray(participantsToAdd);
mChatRoom.addParticipants(participantsToAdd);
LinphoneActivity.instance()
.goToChat(
mChatRoom.getLocalAddress().asStringUriOnly(),
mGroupChatRoomAddress.asString(),
null);
}
}
});
mConfirmButton.setEnabled(mSubjectField.getText().length() > 0 && mParticipants.size() > 0);
if (!mIsEditionEnabled) {
mSubjectField.setEnabled(false);
mConfirmButton.setVisibility(View.INVISIBLE);
mAddParticipantsButton.setVisibility(View.GONE);
}
mWaitLayout = view.findViewById(R.id.waitScreen);
mWaitLayout.setVisibility(View.GONE);
if (mChatRoom != null) {
mChatRoom.addListener(this);
}
return view;
}
@Override
public void onResume() {
super.onResume();
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.INFO_GROUP_CHAT);
}
InputMethodManager inputMethodManager =
(InputMethodManager) getActivity().getSystemService(INPUT_METHOD_SERVICE);
if (getActivity().getCurrentFocus() != null) {
inputMethodManager.hideSoftInputFromWindow(
getActivity().getCurrentFocus().getWindowToken(), 0);
}
}
@Override
public void onPause() {
if (mTempChatRoom != null) {
mTempChatRoom.removeListener(mChatRoomCreationListener);
}
super.onPause();
}
@Override
public void onDestroy() {
if (mChatRoom != null) {
mChatRoom.removeListener(this);
}
super.onDestroy();
}
private void refreshParticipantsList() {
if (mChatRoom == null) return;
mParticipants = new ArrayList<>();
for (Participant p : mChatRoom.getParticipants()) {
Address a = p.getAddress();
LinphoneContact c = ContactsManager.getInstance().findContactFromAddress(a);
if (c == null) {
c = new LinphoneContact();
String displayName = LinphoneUtils.getAddressDisplayName(a);
c.setFullName(displayName);
}
ContactAddress ca = new ContactAddress(c, a.asString(), "", c.isFriend(), p.isAdmin());
mParticipants.add(ca);
}
mAdapter.updateDataSet(mParticipants);
mAdapter.setChatRoom(mChatRoom);
}
private void refreshAdminRights() {
mAdapter.setAdminFeaturesVisible(mIsEditionEnabled);
mAdapter.setChatRoom(mChatRoom);
mSubjectField.setEnabled(mIsEditionEnabled);
mConfirmButton.setVisibility(mIsEditionEnabled ? View.VISIBLE : View.INVISIBLE);
mAddParticipantsButton.setVisibility(mIsEditionEnabled ? View.VISIBLE : View.GONE);
}
private void displayMeAdminStatusUpdated() {
if (mAdminStateChangedDialog != null) mAdminStateChangedDialog.dismiss();
mAdminStateChangedDialog =
LinphoneActivity.instance()
.displayDialog(
getString(
mIsEditionEnabled
? R.string.chat_room_you_are_now_admin
: R.string.chat_room_you_are_no_longer_admin));
Button cancel = mAdminStateChangedDialog.findViewById(R.id.dialog_cancel_button);
mAdminStateChangedDialog.findViewById(R.id.dialog_delete_button).setVisibility(View.GONE);
cancel.setText(getString(R.string.ok));
cancel.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
mAdminStateChangedDialog.dismiss();
}
});
mAdminStateChangedDialog.show();
}
@Override
public void onParticipantAdminStatusChanged(ChatRoom cr, EventLog event_log) {
if (mChatRoom.getMe().isAdmin() != mIsEditionEnabled) {
// Either we weren't admin and we are now or the other way around
mIsEditionEnabled = mChatRoom.getMe().isAdmin();
displayMeAdminStatusUpdated();
refreshAdminRights();
}
refreshParticipantsList();
}
@Override
public void onSubjectChanged(ChatRoom cr, EventLog event_log) {
mSubjectField.setText(event_log.getSubject());
}
@Override
public void onConferenceJoined(ChatRoom cr, EventLog event_log) {}
@Override
public void onConferenceLeft(ChatRoom cr, EventLog event_log) {}
@Override
public void onParticipantAdded(ChatRoom cr, EventLog event_log) {
refreshParticipantsList();
}
@Override
public void onParticipantRemoved(ChatRoom cr, EventLog event_log) {
refreshParticipantsList();
}
@Override
public void onChatMessageShouldBeStored(ChatRoom cr, ChatMessage msg) {}
@Override
public void onIsComposingReceived(ChatRoom cr, Address remoteAddr, boolean isComposing) {}
@Override
public void onChatMessageSent(ChatRoom cr, EventLog event_log) {}
@Override
public void onConferenceAddressGeneration(ChatRoom cr) {}
@Override
public void onChatMessageReceived(ChatRoom cr, EventLog event_log) {}
@Override
public void onMessageReceived(ChatRoom cr, ChatMessage msg) {}
@Override
public void onParticipantDeviceRemoved(ChatRoom cr, EventLog event_log) {}
@Override
public void onParticipantDeviceAdded(ChatRoom cr, EventLog event_log) {}
@Override
public void onSecurityEvent(ChatRoom cr, EventLog eventLog) {
refreshParticipantsList();
}
@Override
public void onUndecryptableMessageReceived(ChatRoom cr, ChatMessage msg) {}
@Override
public void onStateChanged(ChatRoom cr, ChatRoom.State newState) {}
@Override
public void onParticipantRegistrationSubscriptionRequested(
ChatRoom cr, Address participantAddr) {}
@Override
public void onParticipantRegistrationUnsubscriptionRequested(
ChatRoom cr, Address participantAddr) {}
}

View file

@ -0,0 +1,46 @@
package org.linphone.chat;
/*
GroupInfoViewHolder.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.linphone.R;
public class GroupInfoViewHolder extends RecyclerView.ViewHolder {
public final TextView name, sipUri;
public final RelativeLayout avatarLayout;
public final ImageView delete;
public final LinearLayout isAdmin;
public final LinearLayout isNotAdmin;
public GroupInfoViewHolder(View view) {
super(view);
name = view.findViewById(R.id.name);
sipUri = view.findViewById(R.id.sipUri);
avatarLayout = view.findViewById(R.id.avatar_layout);
delete = view.findViewById(R.id.delete);
isAdmin = view.findViewById(R.id.isAdminLayout);
isNotAdmin = view.findViewById(R.id.isNotAdminLayout);
}
}

View file

@ -0,0 +1,370 @@
/*
ImdnOldFragment.java
Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.chat;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatMessageListenerStub;
import org.linphone.core.ChatRoom;
import org.linphone.core.Core;
import org.linphone.core.ParticipantImdnState;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ImdnFragment extends Fragment {
private LayoutInflater mInflater;
private LinearLayout mRead,
mReadHeader,
mDelivered,
mDeliveredHeader,
mSent,
mSentHeader,
mUndelivered,
mUndeliveredHeader;
private ImageView mBackButton;
private ChatMessageViewHolder mBubble;
private ViewGroup mContainer;
private String mLocalSipuri, mRoomUri, mMessageId;
private Address mLocalSipAddr, mRoomAddr;
private ChatRoom mRoom;
private ChatMessage mMessage;
private ChatMessageListenerStub mListener;
@Nullable
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mLocalSipuri = getArguments().getString("LocalSipUri");
mLocalSipAddr = LinphoneManager.getLc().createAddress(mLocalSipuri);
mRoomUri = getArguments().getString("RemoteSipUri");
mRoomAddr = LinphoneManager.getLc().createAddress(mRoomUri);
mMessageId = getArguments().getString("MessageId");
}
Core core = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
mRoom = core.getChatRoom(mRoomAddr, mLocalSipAddr);
mInflater = inflater;
mContainer = container;
View view = mInflater.inflate(R.layout.chat_imdn, container, false);
mBackButton = view.findViewById(R.id.back);
mBackButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (LinphoneActivity.instance().isTablet()) {
LinphoneActivity.instance().goToChat(mLocalSipuri, mRoomUri, null);
} else {
LinphoneActivity.instance().onBackPressed();
}
}
});
mRead = view.findViewById(R.id.read_layout);
mDelivered = view.findViewById(R.id.delivered_layout);
mSent = view.findViewById(R.id.sent_layout);
mUndelivered = view.findViewById(R.id.undelivered_layout);
mReadHeader = view.findViewById(R.id.read_layout_header);
mDeliveredHeader = view.findViewById(R.id.delivered_layout_header);
mSentHeader = view.findViewById(R.id.sent_layout_header);
mUndeliveredHeader = view.findViewById(R.id.undelivered_layout_header);
mBubble = new ChatMessageViewHolder(getActivity(), view.findViewById(R.id.bubble), null);
mMessage = mRoom.findMessage(mMessageId);
mListener =
new ChatMessageListenerStub() {
@Override
public void onParticipantImdnStateChanged(
ChatMessage msg, ParticipantImdnState state) {
refreshInfo();
}
};
return view;
}
@Override
public void onResume() {
super.onResume();
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.MESSAGE_IMDN);
}
refreshInfo();
if (mMessage != null) {
mMessage.addListener(mListener);
}
}
@Override
public void onPause() {
if (mMessage != null) {
mMessage.removeListener(mListener);
}
super.onPause();
}
private void refreshInfo() {
if (mMessage == null) {
// TODO: error
return;
}
Address remoteSender = mMessage.getFromAddress();
LinphoneContact contact =
ContactsManager.getInstance().findContactFromAddress(remoteSender);
mBubble.delete.setVisibility(View.GONE);
mBubble.eventLayout.setVisibility(View.GONE);
mBubble.securityEventLayout.setVisibility(View.GONE);
mBubble.rightAnchor.setVisibility(View.GONE);
mBubble.bubbleLayout.setVisibility(View.GONE);
mBubble.bindMessage(mMessage, contact);
mRead.removeAllViews();
mDelivered.removeAllViews();
mSent.removeAllViews();
mUndelivered.removeAllViews();
ParticipantImdnState[] participants =
mMessage.getParticipantsByImdnState(ChatMessage.State.Displayed);
mReadHeader.setVisibility(participants.length == 0 ? View.GONE : View.VISIBLE);
boolean first = true;
for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact =
ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
((TextView) v.findViewById(R.id.time))
.setText(
LinphoneUtils.timestampToHumanDate(
getActivity(),
participant.getStateChangeTime(),
R.string.messages_date_format));
TextView name = v.findViewById(R.id.name);
name.setText(participantDisplayName);
if (participantContact != null) {
ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else {
ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
}
final TextView sipUri = v.findViewById(R.id.sipUri);
sipUri.setText(address.asStringUriOnly());
if (!LinphoneActivity.instance()
.getResources()
.getBoolean(R.bool.show_sip_uri_in_chat)) {
sipUri.setVisibility(View.GONE);
name.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
sipUri.setVisibility(
sipUri.getVisibility() == View.VISIBLE
? View.GONE
: View.VISIBLE);
}
});
}
mRead.addView(v);
first = false;
}
participants = mMessage.getParticipantsByImdnState(ChatMessage.State.DeliveredToUser);
mDeliveredHeader.setVisibility(participants.length == 0 ? View.GONE : View.VISIBLE);
first = true;
for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact =
ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
((TextView) v.findViewById(R.id.time))
.setText(
LinphoneUtils.timestampToHumanDate(
getActivity(),
participant.getStateChangeTime(),
R.string.messages_date_format));
TextView name = v.findViewById(R.id.name);
name.setText(participantDisplayName);
if (participantContact != null) {
ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else {
ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
}
final TextView sipUri = v.findViewById(R.id.sipUri);
sipUri.setText(address.asStringUriOnly());
if (!LinphoneActivity.instance()
.getResources()
.getBoolean(R.bool.show_sip_uri_in_chat)) {
sipUri.setVisibility(View.GONE);
name.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
sipUri.setVisibility(
sipUri.getVisibility() == View.VISIBLE
? View.GONE
: View.VISIBLE);
}
});
}
mDelivered.addView(v);
first = false;
}
participants = mMessage.getParticipantsByImdnState(ChatMessage.State.Delivered);
mSentHeader.setVisibility(participants.length == 0 ? View.GONE : View.VISIBLE);
first = true;
for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact =
ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
((TextView) v.findViewById(R.id.time))
.setText(
LinphoneUtils.timestampToHumanDate(
getActivity(),
participant.getStateChangeTime(),
R.string.messages_date_format));
TextView name = v.findViewById(R.id.name);
name.setText(participantDisplayName);
if (participantContact != null) {
ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else {
ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
}
final TextView sipUri = v.findViewById(R.id.sipUri);
sipUri.setText(address.asStringUriOnly());
if (!LinphoneActivity.instance()
.getResources()
.getBoolean(R.bool.show_sip_uri_in_chat)) {
sipUri.setVisibility(View.GONE);
name.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
sipUri.setVisibility(
sipUri.getVisibility() == View.VISIBLE
? View.GONE
: View.VISIBLE);
}
});
}
mSent.addView(v);
first = false;
}
participants = mMessage.getParticipantsByImdnState(ChatMessage.State.NotDelivered);
mUndeliveredHeader.setVisibility(participants.length == 0 ? View.GONE : View.VISIBLE);
first = true;
for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact =
ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
TextView name = v.findViewById(R.id.name);
name.setText(participantDisplayName);
if (participantContact != null) {
ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else {
ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
}
final TextView sipUri = v.findViewById(R.id.sipUri);
sipUri.setText(address.asStringUriOnly());
if (!LinphoneActivity.instance()
.getResources()
.getBoolean(R.bool.show_sip_uri_in_chat)) {
sipUri.setVisibility(View.GONE);
name.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
sipUri.setVisibility(
sipUri.getVisibility() == View.VISIBLE
? View.GONE
: View.VISIBLE);
}
});
}
mUndelivered.addView(v);
first = false;
}
}
}

View file

@ -1,5 +1,7 @@
package org.linphone.chat;
/* /*
ImdnFragment.java ImdnOldFragment.java
Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France Copyright (C) 2010-2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
@ -17,11 +19,8 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
package org.linphone.chat;
import android.app.Fragment; import android.app.Fragment;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.Spanned; import android.text.Spanned;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -31,11 +30,10 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneUtils;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.compatibility.Compatibility; import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
@ -45,54 +43,64 @@ import org.linphone.core.ChatMessageListenerStub;
import org.linphone.core.ChatRoom; import org.linphone.core.ChatRoom;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.core.ParticipantImdnState; import org.linphone.core.ParticipantImdnState;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ImdnFragment extends Fragment { public class ImdnOldFragment extends Fragment {
private LayoutInflater mInflater; private LayoutInflater mInflater;
private LinearLayout mRead, mReadHeader, mDelivered, mDeliveredHeader, mSent, mSentHeader, mUndelivered, mUndeliveredHeader; private LinearLayout mRead,
mReadHeader,
mDelivered,
mDeliveredHeader,
mSent,
mSentHeader,
mUndelivered,
mUndeliveredHeader;
private ImageView mBackButton; private ImageView mBackButton;
private ChatBubbleViewHolder mBubble; private ChatMessageOldViewHolder mBubble;
private ViewGroup mContainer; private ViewGroup mContainer;
private String mRoomUri, mMessageId; private String mLocalSipUri, mRoomUri, mMessageId;
private Address mRoomAddr; private Address mLocalAddr, mRoomAddr;
private ChatRoom mRoom; private ChatRoom mRoom;
private ChatMessage mMessage; private ChatMessage mMessage;
private ChatMessageListenerStub mListener; private ChatMessageListenerStub mListener;
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (getArguments() != null) { if (getArguments() != null) {
mRoomUri = getArguments().getString("SipUri"); mLocalSipUri = getArguments().getString("LocalSipUri");
mLocalAddr = LinphoneManager.getLc().createAddress(mLocalSipUri);
mRoomUri = getArguments().getString("RemoteSipUri");
mRoomAddr = LinphoneManager.getLc().createAddress(mRoomUri); mRoomAddr = LinphoneManager.getLc().createAddress(mRoomUri);
mMessageId = getArguments().getString("MessageId"); mMessageId = getArguments().getString("MessageId");
} }
Core core = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); Core core = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
Address proxyConfigContact = core.getDefaultProxyConfig().getContact(); mRoom = core.getChatRoom(mRoomAddr, mLocalAddr);
if (proxyConfigContact != null) {
mRoom = core.findOneToOneChatRoom(proxyConfigContact, mRoomAddr);
}
if (mRoom == null) {
mRoom = core.getChatRoomFromUri(mRoomAddr.asStringUriOnly());
}
mInflater = inflater; mInflater = inflater;
mContainer = container; mContainer = container;
View view = mInflater.inflate(R.layout.chat_imdn, container, false); View view = mInflater.inflate(R.layout.chat_imdn_old, container, false);
mBackButton = view.findViewById(R.id.back); mBackButton = view.findViewById(R.id.back);
mBackButton.setOnClickListener(new View.OnClickListener() { mBackButton.setOnClickListener(
@Override new View.OnClickListener() {
public void onClick(View view) { @Override
if (LinphoneActivity.instance().isTablet()) { public void onClick(View view) {
LinphoneActivity.instance().goToChat(mRoomUri, null, mRoom.getLocalAddress().asString()); if (LinphoneActivity.instance().isTablet()) {
} else { LinphoneActivity.instance().goToChat(mLocalSipUri, mRoomUri, null);
LinphoneActivity.instance().onBackPressed(); } else {
} LinphoneActivity.instance().onBackPressed();
} }
}); }
});
mRead = view.findViewById(R.id.read_layout); mRead = view.findViewById(R.id.read_layout);
mDelivered = view.findViewById(R.id.delivered_layout); mDelivered = view.findViewById(R.id.delivered_layout);
@ -103,7 +111,7 @@ public class ImdnFragment extends Fragment {
mSentHeader = view.findViewById(R.id.sent_layout_header); mSentHeader = view.findViewById(R.id.sent_layout_header);
mUndeliveredHeader = view.findViewById(R.id.undelivered_layout_header); mUndeliveredHeader = view.findViewById(R.id.undelivered_layout_header);
mBubble = new ChatBubbleViewHolder(view.findViewById(R.id.bubble)); mBubble = new ChatMessageOldViewHolder(view.findViewById(R.id.bubble));
mBubble.eventLayout.setVisibility(View.GONE); mBubble.eventLayout.setVisibility(View.GONE);
mBubble.bubbleLayout.setVisibility(View.VISIBLE); mBubble.bubbleLayout.setVisibility(View.VISIBLE);
mBubble.delete.setVisibility(View.GONE); mBubble.delete.setVisibility(View.GONE);
@ -115,32 +123,38 @@ public class ImdnFragment extends Fragment {
mBubble.messageStatus.setVisibility(View.INVISIBLE); mBubble.messageStatus.setVisibility(View.INVISIBLE);
mBubble.messageSendingInProgress.setVisibility(View.GONE); mBubble.messageSendingInProgress.setVisibility(View.GONE);
mBubble.imdmLayout.setVisibility(View.INVISIBLE); mBubble.imdmLayout.setVisibility(View.INVISIBLE);
mBubble.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
mMessage = mRoom.findMessage(mMessageId); mMessage = mRoom.findMessage(mMessageId);
mListener = new ChatMessageListenerStub() { mListener =
@Override new ChatMessageListenerStub() {
public void onParticipantImdnStateChanged(ChatMessage msg, ParticipantImdnState state) { @Override
refreshInfo(); public void onParticipantImdnStateChanged(
} ChatMessage msg, ParticipantImdnState state) {
}; refreshInfo();
mMessage.setListener(mListener); }
};
if (mMessage == null) return null;
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); RelativeLayout.LayoutParams layoutParams =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams.setMargins(100, 10, 10, 10); layoutParams.setMargins(100, 10, 10, 10);
if (mMessage.isOutgoing()) { if (mMessage.isOutgoing()) {
mBubble.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing); mBubble.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing);
Compatibility.setTextAppearance(mBubble.contactName, getActivity(), R.style.font3); Compatibility.setTextAppearance(mBubble.contactName, getActivity(), R.style.font3);
Compatibility.setTextAppearance(mBubble.fileTransferAction, getActivity(), R.style.font15); Compatibility.setTextAppearance(
mBubble.fileTransferAction.setBackgroundResource(R.drawable.resizable_confirm_delete_button); mBubble.fileTransferAction, getActivity(), R.style.font15);
mBubble.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask_outgoing); mBubble.fileTransferAction.setBackgroundResource(
R.drawable.resizable_confirm_delete_button);
} else { } else {
mBubble.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming); mBubble.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming);
Compatibility.setTextAppearance(mBubble.contactName, getActivity(), R.style.font9); Compatibility.setTextAppearance(
Compatibility.setTextAppearance(mBubble.fileTransferAction, getActivity(), R.style.font8); mBubble.contactName, getActivity(), R.style.contact_organization_font);
Compatibility.setTextAppearance(
mBubble.fileTransferAction, getActivity(), R.style.button_font);
mBubble.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button); mBubble.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button);
mBubble.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask);
} }
return view; return view;
@ -150,12 +164,29 @@ public class ImdnFragment extends Fragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.MESSAGE_IMDN);
}
if (mMessage != null) {
mMessage.addListener(mListener);
}
refreshInfo(); refreshInfo();
} }
@Override
public void onPause() {
if (mMessage != null) {
mMessage.removeListener(mListener);
}
super.onPause();
}
private void refreshInfo() { private void refreshInfo() {
Address remoteSender = mMessage.getFromAddress(); Address remoteSender = mMessage.getFromAddress();
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(remoteSender); LinphoneContact contact =
ContactsManager.getInstance().findContactFromAddress(remoteSender);
String displayName; String displayName;
if (contact != null) { if (contact != null) {
@ -165,15 +196,16 @@ public class ImdnFragment extends Fragment {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender); displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
} }
mBubble.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap()); ContactAvatar.displayAvatar(contact, mBubble.avatarLayout);
if (contact.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(getActivity(), mBubble.contactPicture, contact.getThumbnailUri());
}
} else { } else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender); displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
mBubble.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap()); ContactAvatar.displayAvatar(displayName, mBubble.avatarLayout);
} }
mBubble.contactName.setText(LinphoneUtils.timestampToHumanDate(getActivity(), mMessage.getTime(), R.string.messages_date_format) + " - " + displayName); mBubble.contactName.setText(
LinphoneUtils.timestampToHumanDate(
getActivity(), mMessage.getTime(), R.string.messages_date_format)
+ " - "
+ displayName);
if (mMessage.hasTextContent()) { if (mMessage.hasTextContent()) {
String msg = mMessage.getTextContent(); String msg = mMessage.getTextContent();
@ -186,7 +218,7 @@ public class ImdnFragment extends Fragment {
String appData = mMessage.getAppdata(); String appData = mMessage.getAppdata();
if (appData != null) { // Something to display if (appData != null) { // Something to display
mBubble.fileName.setVisibility(View.VISIBLE); mBubble.fileName.setVisibility(View.VISIBLE);
mBubble.fileName.setText(LinphoneUtils.getNameFromFilePath(appData)); mBubble.fileName.setText(FileUtils.getNameFromFilePath(appData));
// We purposely chose not to display the image // We purposely chose not to display the image
} }
@ -195,23 +227,34 @@ public class ImdnFragment extends Fragment {
mSent.removeAllViews(); mSent.removeAllViews();
mUndelivered.removeAllViews(); mUndelivered.removeAllViews();
ParticipantImdnState[] participants = mMessage.getParticipantsByImdnState(ChatMessage.State.Displayed); ParticipantImdnState[] participants =
mMessage.getParticipantsByImdnState(ChatMessage.State.Displayed);
mReadHeader.setVisibility(participants.length == 0 ? View.GONE : View.VISIBLE); mReadHeader.setVisibility(participants.length == 0 ? View.GONE : View.VISIBLE);
boolean first = true; boolean first = true;
for (ParticipantImdnState participant : participants) { for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress(); Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact = ContactsManager.getInstance().findContactFromAddress(address); LinphoneContact participantContact =
String participantDisplayName = participantContact != null ? participantContact.getFullName() : LinphoneUtils.getAddressDisplayName(address); ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false); View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE); v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
((TextView) v.findViewById(R.id.time)).setText(LinphoneUtils.timestampToHumanDate(getActivity(), participant.getStateChangeTime(), R.string.messages_date_format)); ((TextView) v.findViewById(R.id.time))
.setText(
LinphoneUtils.timestampToHumanDate(
getActivity(),
participant.getStateChangeTime(),
R.string.messages_date_format));
((TextView) v.findViewById(R.id.name)).setText(participantDisplayName); ((TextView) v.findViewById(R.id.name)).setText(participantDisplayName);
if (participantContact != null && participantContact.hasPhoto()) { if (participantContact != null) {
LinphoneUtils.setThumbnailPictureFromUri(getActivity(), ((ImageView) v.findViewById(R.id.contact_picture)), participantContact.getThumbnailUri()); ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else { } else {
((ImageView) v.findViewById(R.id.contact_picture)).setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap()); ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
} }
mRead.addView(v); mRead.addView(v);
@ -224,17 +267,27 @@ public class ImdnFragment extends Fragment {
for (ParticipantImdnState participant : participants) { for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress(); Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact = ContactsManager.getInstance().findContactFromAddress(address); LinphoneContact participantContact =
String participantDisplayName = participantContact != null ? participantContact.getFullName() : LinphoneUtils.getAddressDisplayName(address); ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false); View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE); v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
((TextView) v.findViewById(R.id.time)).setText(LinphoneUtils.timestampToHumanDate(getActivity(), participant.getStateChangeTime(), R.string.messages_date_format)); ((TextView) v.findViewById(R.id.time))
.setText(
LinphoneUtils.timestampToHumanDate(
getActivity(),
participant.getStateChangeTime(),
R.string.messages_date_format));
((TextView) v.findViewById(R.id.name)).setText(participantDisplayName); ((TextView) v.findViewById(R.id.name)).setText(participantDisplayName);
if (participantContact != null && participantContact.hasPhoto()) { if (participantContact != null) {
LinphoneUtils.setThumbnailPictureFromUri(getActivity(), ((ImageView) v.findViewById(R.id.contact_picture)), participantContact.getThumbnailUri()); ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else { } else {
((ImageView) v.findViewById(R.id.contact_picture)).setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap()); ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
} }
mDelivered.addView(v); mDelivered.addView(v);
@ -247,17 +300,27 @@ public class ImdnFragment extends Fragment {
for (ParticipantImdnState participant : participants) { for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress(); Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact = ContactsManager.getInstance().findContactFromAddress(address); LinphoneContact participantContact =
String participantDisplayName = participantContact != null ? participantContact.getFullName() : LinphoneUtils.getAddressDisplayName(address); ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false); View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE); v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
((TextView) v.findViewById(R.id.time)).setText(LinphoneUtils.timestampToHumanDate(getActivity(), participant.getStateChangeTime(), R.string.messages_date_format)); ((TextView) v.findViewById(R.id.time))
.setText(
LinphoneUtils.timestampToHumanDate(
getActivity(),
participant.getStateChangeTime(),
R.string.messages_date_format));
((TextView) v.findViewById(R.id.name)).setText(participantDisplayName); ((TextView) v.findViewById(R.id.name)).setText(participantDisplayName);
if (participantContact != null && participantContact.hasPhoto()) { if (participantContact != null) {
LinphoneUtils.setThumbnailPictureFromUri(getActivity(), ((ImageView) v.findViewById(R.id.contact_picture)), participantContact.getThumbnailUri()); ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else { } else {
((ImageView) v.findViewById(R.id.contact_picture)).setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap()); ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
} }
mSent.addView(v); mSent.addView(v);
@ -270,16 +333,21 @@ public class ImdnFragment extends Fragment {
for (ParticipantImdnState participant : participants) { for (ParticipantImdnState participant : participants) {
Address address = participant.getParticipant().getAddress(); Address address = participant.getParticipant().getAddress();
LinphoneContact participantContact = ContactsManager.getInstance().findContactFromAddress(address); LinphoneContact participantContact =
String participantDisplayName = participantContact != null ? participantContact.getFullName() : LinphoneUtils.getAddressDisplayName(address); ContactsManager.getInstance().findContactFromAddress(address);
String participantDisplayName =
participantContact != null
? participantContact.getFullName()
: LinphoneUtils.getAddressDisplayName(address);
View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false); View v = mInflater.inflate(R.layout.chat_imdn_cell, mContainer, false);
v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE); v.findViewById(R.id.separator).setVisibility(first ? View.GONE : View.VISIBLE);
((TextView) v.findViewById(R.id.name)).setText(participantDisplayName); ((TextView) v.findViewById(R.id.name)).setText(participantDisplayName);
if (participantContact != null && participantContact.hasPhoto()) { if (participantContact != null) {
LinphoneUtils.setThumbnailPictureFromUri(getActivity(), ((ImageView) v.findViewById(R.id.contact_picture)), participantContact.getThumbnailUri()); ContactAvatar.displayAvatar(participantContact, v.findViewById(R.id.avatar_layout));
} else { } else {
((ImageView) v.findViewById(R.id.contact_picture)).setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap()); ContactAvatar.displayAvatar(
participantDisplayName, v.findViewById(R.id.avatar_layout));
} }
mUndelivered.addView(v); mUndelivered.addView(v);

View file

@ -0,0 +1,109 @@
package org.linphone.compatibility;
/*
ApiTwentyEightPlus.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static org.linphone.compatibility.Compatibility.CHAT_NOTIFICATIONS_GROUP;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import org.linphone.R;
import org.linphone.notifications.Notifiable;
import org.linphone.notifications.NotifiableMessage;
@TargetApi(28)
class ApiTwentyEightPlus {
public static Notification createMessageNotification(
Context context, Notifiable notif, Bitmap contactIcon, PendingIntent intent) {
Person me = new Person.Builder().setName(notif.getMyself()).build();
Notification.MessagingStyle style = new Notification.MessagingStyle(me);
for (NotifiableMessage message : notif.getMessages()) {
Icon userIcon = Icon.createWithBitmap(message.getSenderBitmap());
Person user =
new Person.Builder().setName(message.getSender()).setIcon(userIcon).build();
Notification.MessagingStyle.Message msg =
new Notification.MessagingStyle.Message(
message.getMessage(), message.getTime(), user);
if (message.getFilePath() != null)
msg.setData(message.getFileMime(), message.getFilePath());
style.addMessage(msg);
}
if (notif.isGroup()) {
style.setConversationTitle(notif.getGroupTitle());
}
style.setGroupConversation(notif.isGroup());
return new Notification.Builder(
context, context.getString(R.string.notification_channel_id))
.setSmallIcon(R.drawable.topbar_chat_notification)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(
Notification.DEFAULT_SOUND
| Notification.DEFAULT_VIBRATE
| Notification.DEFAULT_LIGHTS)
.setLargeIcon(contactIcon)
.setCategory(Notification.CATEGORY_MESSAGE)
.setGroup(CHAT_NOTIFICATIONS_GROUP)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setNumber(notif.getMessages().size())
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.setStyle(style)
.addAction(ApiTwentyFourPlus.getReplyMessageAction(context, notif))
.addAction(ApiTwentyFourPlus.getMarkMessageAsReadAction(context, notif))
.build();
}
public static boolean isAppUserRestricted(Context context) {
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
return activityManager.isBackgroundRestricted();
}
public static int getAppStandbyBucket(Context context) {
UsageStatsManager usageStatsManager =
(UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
return usageStatsManager.getAppStandbyBucket();
}
public static String getAppStandbyBucketNameFromValue(int bucket) {
switch (bucket) {
case UsageStatsManager.STANDBY_BUCKET_ACTIVE:
return "STANDBY_BUCKET_ACTIVE";
case UsageStatsManager.STANDBY_BUCKET_FREQUENT:
return "STANDBY_BUCKET_FREQUENT";
case UsageStatsManager.STANDBY_BUCKET_RARE:
return "STANDBY_BUCKET_RARE";
case UsageStatsManager.STANDBY_BUCKET_WORKING_SET:
return "STANDBY_BUCKET_WORKING_SET";
}
return null;
}
}

View file

@ -0,0 +1,219 @@
package org.linphone.compatibility;
/*
ApiTwentyFourPlus.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static org.linphone.compatibility.Compatibility.CHAT_NOTIFICATIONS_GROUP;
import static org.linphone.compatibility.Compatibility.INTENT_ANSWER_CALL_NOTIF_ACTION;
import static org.linphone.compatibility.Compatibility.INTENT_HANGUP_CALL_NOTIF_ACTION;
import static org.linphone.compatibility.Compatibility.INTENT_LOCAL_IDENTITY;
import static org.linphone.compatibility.Compatibility.INTENT_MARK_AS_READ_ACTION;
import static org.linphone.compatibility.Compatibility.INTENT_NOTIF_ID;
import static org.linphone.compatibility.Compatibility.INTENT_REPLY_NOTIF_ACTION;
import static org.linphone.compatibility.Compatibility.KEY_TEXT_REPLY;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import org.linphone.R;
import org.linphone.notifications.Notifiable;
import org.linphone.notifications.NotifiableMessage;
import org.linphone.notifications.NotificationBroadcastReceiver;
@TargetApi(24)
class ApiTwentyFourPlus {
public static Notification createRepliedNotification(Context context, String reply) {
return new Notification.Builder(context)
.setSmallIcon(R.drawable.topbar_chat_notification)
.setContentText(
context.getString(R.string.notification_replied_label).replace("%s", reply))
.build();
}
public static Notification createMessageNotification(
Context context, Notifiable notif, Bitmap contactIcon, PendingIntent intent) {
Notification.MessagingStyle style = new Notification.MessagingStyle(notif.getMyself());
for (NotifiableMessage message : notif.getMessages()) {
Notification.MessagingStyle.Message msg =
new Notification.MessagingStyle.Message(
message.getMessage(), message.getTime(), message.getSender());
if (message.getFilePath() != null)
msg.setData(message.getFileMime(), message.getFilePath());
style.addMessage(msg);
}
if (notif.isGroup()) {
style.setConversationTitle(notif.getGroupTitle());
}
return new Notification.Builder(context)
.setSmallIcon(R.drawable.topbar_chat_notification)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(
Notification.DEFAULT_SOUND
| Notification.DEFAULT_VIBRATE
| Notification.DEFAULT_LIGHTS)
.setLargeIcon(contactIcon)
.setCategory(Notification.CATEGORY_MESSAGE)
.setGroup(CHAT_NOTIFICATIONS_GROUP)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setNumber(notif.getMessages().size())
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.setStyle(style)
.addAction(getReplyMessageAction(context, notif))
.addAction(getMarkMessageAsReadAction(context, notif))
.build();
}
public static Notification createInCallNotification(
Context context,
int callId,
boolean showAnswerAction,
String msg,
int iconID,
Bitmap contactIcon,
String contactName,
PendingIntent intent) {
Notification.Builder builder =
new Notification.Builder(context)
.setContentTitle(contactName)
.setContentText(msg)
.setSmallIcon(iconID)
.setAutoCancel(false)
.setContentIntent(intent)
.setLargeIcon(contactIcon)
.setCategory(Notification.CATEGORY_CALL)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.addAction(getCallDeclineAction(context, callId));
if (showAnswerAction) {
builder.addAction(getCallAnswerAction(context, callId));
}
return builder.build();
}
public static Notification.Action getReplyMessageAction(Context context, Notifiable notif) {
String replyLabel = context.getResources().getString(R.string.notification_reply_label);
RemoteInput remoteInput =
new RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build();
Intent replyIntent = new Intent(context, NotificationBroadcastReceiver.class);
replyIntent.setAction(INTENT_REPLY_NOTIF_ACTION);
replyIntent.putExtra(INTENT_NOTIF_ID, notif.getNotificationId());
replyIntent.putExtra(INTENT_LOCAL_IDENTITY, notif.getLocalIdentity());
PendingIntent replyPendingIntent =
PendingIntent.getBroadcast(
context,
notif.getNotificationId(),
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action replyAction =
new Notification.Action.Builder(
R.drawable.chat_send_over,
context.getString(R.string.notification_reply_label),
replyPendingIntent)
.addRemoteInput(remoteInput)
.setAllowGeneratedReplies(true)
.build();
return replyAction;
}
public static Notification.Action getMarkMessageAsReadAction(
Context context, Notifiable notif) {
Intent markAsReadIntent = new Intent(context, NotificationBroadcastReceiver.class);
markAsReadIntent.setAction(INTENT_MARK_AS_READ_ACTION);
markAsReadIntent.putExtra(INTENT_NOTIF_ID, notif.getNotificationId());
markAsReadIntent.putExtra(INTENT_LOCAL_IDENTITY, notif.getLocalIdentity());
PendingIntent markAsReadPendingIntent =
PendingIntent.getBroadcast(
context,
notif.getNotificationId(),
markAsReadIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action markAsReadAction =
new Notification.Action.Builder(
R.drawable.chat_send_over,
context.getString(R.string.notification_mark_as_read_label),
markAsReadPendingIntent)
.build();
return markAsReadAction;
}
public static Notification.Action getCallAnswerAction(Context context, int callId) {
Intent answerIntent = new Intent(context, NotificationBroadcastReceiver.class);
answerIntent.setAction(INTENT_ANSWER_CALL_NOTIF_ACTION);
answerIntent.putExtra(INTENT_NOTIF_ID, callId);
PendingIntent answerPendingIntent =
PendingIntent.getBroadcast(
context, callId, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action answerAction =
new Notification.Action.Builder(
R.drawable.call_audio_start,
context.getString(R.string.notification_call_answer_label),
answerPendingIntent)
.build();
return answerAction;
}
public static Notification.Action getCallDeclineAction(Context context, int callId) {
Intent hangupIntent = new Intent(context, NotificationBroadcastReceiver.class);
hangupIntent.setAction(INTENT_HANGUP_CALL_NOTIF_ACTION);
hangupIntent.putExtra(INTENT_NOTIF_ID, callId);
PendingIntent hangupPendingIntent =
PendingIntent.getBroadcast(
context, callId, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action declineAction =
new Notification.Action.Builder(
R.drawable.call_hangup,
context.getString(R.string.notification_call_hangup_label),
hangupPendingIntent)
.build();
return declineAction;
}
public static void closeContentProviderClient(ContentProviderClient client) {
client.close();
}
}

View file

@ -0,0 +1,215 @@
package org.linphone.compatibility;
/*
ApiTwentyOnePlus.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ContentProviderClient;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.WindowManager;
import androidx.core.content.ContextCompat;
import org.linphone.R;
@TargetApi(21)
class ApiTwentyOnePlus {
@SuppressWarnings("deprecation")
public static Notification createMessageNotification(
Context context,
int msgCount,
String msgSender,
String msg,
Bitmap contactIcon,
PendingIntent intent) {
String title;
if (msgCount == 1) {
title = msgSender;
} else {
title =
context.getString(R.string.unread_messages)
.replace("%i", String.valueOf(msgCount));
}
return new Notification.Builder(context)
.setContentTitle(title)
.setContentText(msg)
.setSmallIcon(R.drawable.topbar_chat_notification)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(
Notification.DEFAULT_SOUND
| Notification.DEFAULT_VIBRATE
| Notification.DEFAULT_LIGHTS)
.setLargeIcon(contactIcon)
.setLights(
ContextCompat.getColor(context, R.color.notification_led_color),
context.getResources().getInteger(R.integer.notification_ms_on),
context.getResources().getInteger(R.integer.notification_ms_off))
.setCategory(Notification.CATEGORY_MESSAGE)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setNumber(msgCount)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.build();
}
public static Notification createInCallNotification(
Context context,
String msg,
int iconID,
Bitmap contactIcon,
String contactName,
PendingIntent intent) {
return new Notification.Builder(context)
.setContentTitle(contactName)
.setContentText(msg)
.setSmallIcon(iconID)
.setAutoCancel(false)
.setContentIntent(intent)
.setLargeIcon(contactIcon)
.setCategory(Notification.CATEGORY_CALL)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setPriority(Notification.PRIORITY_HIGH)
.setLights(
ContextCompat.getColor(context, R.color.notification_led_color),
context.getResources().getInteger(R.integer.notification_ms_on),
context.getResources().getInteger(R.integer.notification_ms_off))
.setShowWhen(true)
.build();
}
public static Notification createNotification(
Context context,
String title,
String message,
int icon,
int level,
Bitmap largeIcon,
PendingIntent intent,
int priority) {
Notification notif;
if (largeIcon != null) {
notif =
new Notification.Builder(context)
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(icon, level)
.setLargeIcon(largeIcon)
.setContentIntent(intent)
.setCategory(Notification.CATEGORY_SERVICE)
.setVisibility(Notification.VISIBILITY_SECRET)
.setLights(
ContextCompat.getColor(context, R.color.notification_led_color),
context.getResources().getInteger(R.integer.notification_ms_on),
context.getResources()
.getInteger(R.integer.notification_ms_off))
.setWhen(System.currentTimeMillis())
.setPriority(priority)
.setShowWhen(true)
.build();
} else {
notif =
new Notification.Builder(context)
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(icon, level)
.setContentIntent(intent)
.setCategory(Notification.CATEGORY_SERVICE)
.setVisibility(Notification.VISIBILITY_SECRET)
.setLights(
ContextCompat.getColor(context, R.color.notification_led_color),
context.getResources().getInteger(R.integer.notification_ms_on),
context.getResources()
.getInteger(R.integer.notification_ms_off))
.setPriority(priority)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.build();
}
return notif;
}
public static Notification createMissedCallNotification(
Context context, String title, String text, PendingIntent intent) {
return new Notification.Builder(context)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(R.drawable.call_status_missed)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
.setCategory(Notification.CATEGORY_EVENT)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setLights(
ContextCompat.getColor(context, R.color.notification_led_color),
context.getResources().getInteger(R.integer.notification_ms_on),
context.getResources().getInteger(R.integer.notification_ms_off))
.setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.build();
}
public static Notification createSimpleNotification(
Context context, String title, String text, PendingIntent intent) {
return new Notification.Builder(context)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(R.drawable.linphone_logo)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
.setCategory(Notification.CATEGORY_MESSAGE)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setLights(
ContextCompat.getColor(context, R.color.notification_led_color),
context.getResources().getInteger(R.integer.notification_ms_on),
context.getResources().getInteger(R.integer.notification_ms_off))
.setWhen(System.currentTimeMillis())
.setPriority(Notification.PRIORITY_HIGH)
.setShowWhen(true)
.build();
}
public static void closeContentProviderClient(ContentProviderClient client) {
client.release();
}
public static void setShowWhenLocked(Activity activity, boolean enable) {
if (enable) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
}
public static void setTurnScreenOn(Activity activity, boolean enable) {
if (enable) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}
}
}

View file

@ -0,0 +1,263 @@
package org.linphone.compatibility;
/*
ApiTwentySixPlus.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static org.linphone.compatibility.Compatibility.CHAT_NOTIFICATIONS_GROUP;
import android.annotation.TargetApi;
import android.app.FragmentTransaction;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.provider.Settings;
import org.linphone.R;
import org.linphone.notifications.Notifiable;
import org.linphone.notifications.NotifiableMessage;
@TargetApi(26)
class ApiTwentySixPlus {
public static String getDeviceName(Context context) {
String name =
Settings.Global.getString(
context.getContentResolver(), Settings.Global.DEVICE_NAME);
if (name == null) {
name = BluetoothAdapter.getDefaultAdapter().getName();
}
if (name == null) {
name = Settings.Secure.getString(context.getContentResolver(), "bluetooth_name");
}
if (name == null) {
name = Build.MANUFACTURER + " " + Build.MODEL;
}
return name;
}
public static Notification createRepliedNotification(Context context, String reply) {
return new Notification.Builder(
context, context.getString(R.string.notification_channel_id))
.setSmallIcon(R.drawable.topbar_chat_notification)
.setContentText(
context.getString(R.string.notification_replied_label).replace("%s", reply))
.build();
}
public static void createServiceChannel(Context context) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Create service/call notification channel
String id = context.getString(R.string.notification_service_channel_id);
CharSequence name = context.getString(R.string.content_title_notification_service);
String description = context.getString(R.string.content_title_notification_service);
NotificationChannel channel =
new NotificationChannel(id, name, NotificationManager.IMPORTANCE_NONE);
channel.setDescription(description);
channel.enableVibration(false);
channel.enableLights(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
public static void createMessageChannel(Context context) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Create message notification channel
String id = context.getString(R.string.notification_channel_id);
String name = context.getString(R.string.content_title_notification);
String description = context.getString(R.string.content_title_notification);
NotificationChannel channel =
new NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(description);
channel.setLightColor(context.getColor(R.color.notification_led_color));
channel.enableLights(true);
channel.enableVibration(true);
channel.setShowBadge(true);
notificationManager.createNotificationChannel(channel);
}
public static Notification createMessageNotification(
Context context, Notifiable notif, Bitmap contactIcon, PendingIntent intent) {
Notification.MessagingStyle style = new Notification.MessagingStyle(notif.getMyself());
for (NotifiableMessage message : notif.getMessages()) {
Notification.MessagingStyle.Message msg =
new Notification.MessagingStyle.Message(
message.getMessage(), message.getTime(), message.getSender());
if (message.getFilePath() != null)
msg.setData(message.getFileMime(), message.getFilePath());
style.addMessage(msg);
}
if (notif.isGroup()) {
style.setConversationTitle(notif.getGroupTitle());
}
return new Notification.Builder(
context, context.getString(R.string.notification_channel_id))
.setSmallIcon(R.drawable.topbar_chat_notification)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(
Notification.DEFAULT_SOUND
| Notification.DEFAULT_VIBRATE
| Notification.DEFAULT_LIGHTS)
.setLargeIcon(contactIcon)
.setCategory(Notification.CATEGORY_MESSAGE)
.setGroup(CHAT_NOTIFICATIONS_GROUP)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setNumber(notif.getMessages().size())
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.setStyle(style)
.addAction(ApiTwentyFourPlus.getReplyMessageAction(context, notif))
.addAction(ApiTwentyFourPlus.getMarkMessageAsReadAction(context, notif))
.build();
}
public static Notification createInCallNotification(
Context context,
int callId,
boolean showAnswerAction,
String msg,
int iconID,
Bitmap contactIcon,
String contactName,
PendingIntent intent) {
Notification.Builder builder =
new Notification.Builder(
context,
context.getString(R.string.notification_service_channel_id))
.setContentTitle(contactName)
.setContentText(msg)
.setSmallIcon(iconID)
.setAutoCancel(false)
.setContentIntent(intent)
.setLargeIcon(contactIcon)
.setCategory(Notification.CATEGORY_CALL)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.addAction(ApiTwentyFourPlus.getCallDeclineAction(context, callId));
if (showAnswerAction) {
builder.addAction(ApiTwentyFourPlus.getCallAnswerAction(context, callId));
}
return builder.build();
}
public static Notification createNotification(
Context context,
String title,
String message,
int icon,
int level,
Bitmap largeIcon,
PendingIntent intent,
int priority) {
if (largeIcon != null) {
return new Notification.Builder(
context, context.getString(R.string.notification_service_channel_id))
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(icon, level)
.setLargeIcon(largeIcon)
.setContentIntent(intent)
.setCategory(Notification.CATEGORY_SERVICE)
.setVisibility(Notification.VISIBILITY_SECRET)
.setPriority(priority)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.build();
} else {
return new Notification.Builder(
context, context.getString(R.string.notification_service_channel_id))
.setContentTitle(title)
.setContentText(message)
.setSmallIcon(icon, level)
.setContentIntent(intent)
.setCategory(Notification.CATEGORY_SERVICE)
.setVisibility(Notification.VISIBILITY_SECRET)
.setPriority(priority)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.build();
}
}
public static Notification createMissedCallNotification(
Context context, String title, String text, PendingIntent intent) {
return new Notification.Builder(
context, context.getString(R.string.notification_channel_id))
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(R.drawable.call_status_missed)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
.setCategory(Notification.CATEGORY_EVENT)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColor(context.getColor(R.color.notification_led_color))
.build();
}
public static Notification createSimpleNotification(
Context context, String title, String text, PendingIntent intent) {
return new Notification.Builder(
context, context.getString(R.string.notification_channel_id))
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(R.drawable.linphone_logo)
.setAutoCancel(true)
.setContentIntent(intent)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
.setCategory(Notification.CATEGORY_MESSAGE)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setWhen(System.currentTimeMillis())
.setShowWhen(true)
.setColorized(true)
.setColor(context.getColor(R.color.notification_led_color))
.build();
}
public static void startService(Context context, Intent intent) {
context.startForegroundService(intent);
}
public static void setFragmentTransactionReorderingAllowed(
FragmentTransaction transaction, boolean allowed) {
transaction.setReorderingAllowed(allowed);
}
}

View file

@ -1,8 +1,5 @@
package org.linphone.compatibility; package org.linphone.compatibility;
import android.annotation.TargetApi;
import android.widget.TextView;
/* /*
ApiTwentyThreePlus.java ApiTwentyThreePlus.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France Copyright (C) 2017 Belledonne Communications, Grenoble, France
@ -22,9 +19,18 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import android.annotation.TargetApi;
import android.content.Context;
import android.os.PowerManager;
import android.widget.TextView;
@TargetApi(23) @TargetApi(23)
public class ApiTwentyThreePlus { class ApiTwentyThreePlus {
public static void setTextAppearance(TextView textview, int style) { public static void setTextAppearance(TextView textview, int style) {
textview.setTextAppearance(style); textview.setTextAppearance(style);
} }
public static boolean isAppIdleMode(Context context) {
return ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).isDeviceIdleMode();
}
} }

View file

@ -0,0 +1,240 @@
package org.linphone.compatibility;
/*
Compatibility.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Activity;
import android.app.FragmentTransaction;
import android.app.Notification;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.provider.Settings;
import android.widget.TextView;
import org.linphone.mediastream.Version;
import org.linphone.notifications.Notifiable;
public class Compatibility {
public static final String CHAT_NOTIFICATIONS_GROUP = "CHAT_NOTIF_GROUP";
public static final String KEY_TEXT_REPLY = "key_text_reply";
public static final String INTENT_NOTIF_ID = "NOTIFICATION_ID";
public static final String INTENT_REPLY_NOTIF_ACTION = "org.linphone.REPLY_ACTION";
public static final String INTENT_HANGUP_CALL_NOTIF_ACTION = "org.linphone.HANGUP_CALL_ACTION";
public static final String INTENT_ANSWER_CALL_NOTIF_ACTION = "org.linphone.ANSWER_CALL_ACTION";
public static final String INTENT_LOCAL_IDENTITY = "LOCAL_IDENTITY";
public static final String INTENT_MARK_AS_READ_ACTION = "org.linphone.MARK_AS_READ_ACTION";
public static String getDeviceName(Context context) {
if (Version.sdkAboveOrEqual(25)) {
return ApiTwentySixPlus.getDeviceName(context);
}
String name = BluetoothAdapter.getDefaultAdapter().getName();
if (name == null) {
name = Settings.Secure.getString(context.getContentResolver(), "bluetooth_name");
}
if (name == null) {
name = Build.MANUFACTURER + " " + Build.MODEL;
}
return name;
}
public static void createNotificationChannels(Context context) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
ApiTwentySixPlus.createServiceChannel(context);
ApiTwentySixPlus.createMessageChannel(context);
}
}
public static Notification createSimpleNotification(
Context context, String title, String text, PendingIntent intent) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
return ApiTwentySixPlus.createSimpleNotification(context, title, text, intent);
}
return ApiTwentyOnePlus.createSimpleNotification(context, title, text, intent);
}
public static Notification createMissedCallNotification(
Context context, String title, String text, PendingIntent intent) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
return ApiTwentySixPlus.createMissedCallNotification(context, title, text, intent);
}
return ApiTwentyOnePlus.createMissedCallNotification(context, title, text, intent);
}
public static Notification createMessageNotification(
Context context,
Notifiable notif,
String msgSender,
String msg,
Bitmap contactIcon,
PendingIntent intent) {
if (Version.sdkAboveOrEqual(28)) {
return ApiTwentyEightPlus.createMessageNotification(
context, notif, contactIcon, intent);
} else if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
return ApiTwentySixPlus.createMessageNotification(context, notif, contactIcon, intent);
} else if (Version.sdkAboveOrEqual(Version.API24_NOUGAT_70)) {
return ApiTwentyFourPlus.createMessageNotification(context, notif, contactIcon, intent);
}
return ApiTwentyOnePlus.createMessageNotification(
context, notif.getMessages().size(), msgSender, msg, contactIcon, intent);
}
public static Notification createRepliedNotification(Context context, String reply) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
return ApiTwentySixPlus.createRepliedNotification(context, reply);
} else if (Version.sdkAboveOrEqual(Version.API24_NOUGAT_70)) {
return ApiTwentyFourPlus.createRepliedNotification(context, reply);
}
return null;
}
public static Notification createInCallNotification(
Context context,
int callId,
boolean showAnswerAction,
String msg,
int iconID,
Bitmap contactIcon,
String contactName,
PendingIntent intent) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
return ApiTwentySixPlus.createInCallNotification(
context,
callId,
showAnswerAction,
msg,
iconID,
contactIcon,
contactName,
intent);
} else if (Version.sdkAboveOrEqual(Version.API24_NOUGAT_70)) {
return ApiTwentyFourPlus.createInCallNotification(
context,
callId,
showAnswerAction,
msg,
iconID,
contactIcon,
contactName,
intent);
}
return ApiTwentyOnePlus.createInCallNotification(
context, msg, iconID, contactIcon, contactName, intent);
}
public static Notification createNotification(
Context context,
String title,
String message,
int icon,
int iconLevel,
Bitmap largeIcon,
PendingIntent intent,
int priority) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
return ApiTwentySixPlus.createNotification(
context, title, message, icon, iconLevel, largeIcon, intent, priority);
}
return ApiTwentyOnePlus.createNotification(
context, title, message, icon, iconLevel, largeIcon, intent, priority);
}
public static boolean canDrawOverlays(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(context);
}
return true;
}
public static void setTextAppearance(TextView textview, Context context, int style) {
if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
ApiTwentyThreePlus.setTextAppearance(textview, style);
} else {
textview.setTextAppearance(context, style);
}
}
public static void startService(Context context, Intent intent) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
ApiTwentySixPlus.startService(context, intent);
} else {
context.startService(intent);
}
}
public static void setFragmentTransactionReorderingAllowed(
FragmentTransaction transaction, boolean allowed) {
if (Version.sdkAboveOrEqual(Version.API26_O_80)) {
ApiTwentySixPlus.setFragmentTransactionReorderingAllowed(transaction, allowed);
}
}
public static void closeContentProviderClient(ContentProviderClient client) {
if (Version.sdkAboveOrEqual(Version.API24_NOUGAT_70)) {
ApiTwentyFourPlus.closeContentProviderClient(client);
} else {
ApiTwentyOnePlus.closeContentProviderClient(client);
}
}
public static boolean isAppUserRestricted(Context context) {
if (Version.sdkAboveOrEqual(Version.API28_PIE_90)) {
return ApiTwentyEightPlus.isAppUserRestricted(context);
}
return false;
}
public static boolean isAppIdleMode(Context context) {
if (Version.sdkAboveOrEqual(Version.API23_MARSHMALLOW_60)) {
return ApiTwentyThreePlus.isAppIdleMode(context);
}
return false;
}
public static int getAppStandbyBucket(Context context) {
if (Version.sdkAboveOrEqual(Version.API28_PIE_90)) {
return ApiTwentyEightPlus.getAppStandbyBucket(context);
}
return 0;
}
public static String getAppStandbyBucketNameFromValue(int bucket) {
if (Version.sdkAboveOrEqual(Version.API28_PIE_90)) {
return ApiTwentyEightPlus.getAppStandbyBucketNameFromValue(bucket);
}
return null;
}
public static void setShowWhenLocked(Activity activity, boolean enable) {
if (Version.sdkStrictlyBelow(Version.API27_OREO_81)) {
ApiTwentyOnePlus.setShowWhenLocked(activity, enable);
}
}
public static void setTurnScreenOn(Activity activity, boolean enable) {
if (Version.sdkStrictlyBelow(Version.API27_OREO_81)) {
ApiTwentyOnePlus.setTurnScreenOn(activity, enable);
}
}
}

View file

@ -19,13 +19,12 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
@TargetApi(8) public class CompatibilityScaleGestureDetector
public class CompatibilityScaleGestureDetector extends ScaleGestureDetector.SimpleOnScaleGestureListener { extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private ScaleGestureDetector detector; private ScaleGestureDetector detector;
private CompatibilityScaleGestureListener listener; private CompatibilityScaleGestureListener listener;
@ -58,4 +57,4 @@ public class CompatibilityScaleGestureDetector extends ScaleGestureDetector.Simp
listener = null; listener = null;
detector = null; detector = null;
} }
} }

View file

@ -20,5 +20,5 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
public interface CompatibilityScaleGestureListener { public interface CompatibilityScaleGestureListener {
public boolean onScale(CompatibilityScaleGestureDetector detector); boolean onScale(CompatibilityScaleGestureDetector detector);
} }

View file

@ -0,0 +1,659 @@
package org.linphone.contacts;
/*
AndroidContact.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import java.io.Serializable;
import java.util.ArrayList;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.core.tools.Log;
class AndroidContact implements Serializable {
protected String mAndroidId, mAndroidRawId, mAndroidLookupKey;
protected boolean isAndroidRawIdLinphone;
private transient ArrayList<ContentProviderOperation> mChangesToCommit;
protected AndroidContact() {
mChangesToCommit = new ArrayList<>();
isAndroidRawIdLinphone = false;
}
protected String getAndroidId() {
return mAndroidId;
}
protected void setAndroidId(String id) {
mAndroidId = id;
}
protected String getAndroidLookupKey() {
return mAndroidLookupKey;
}
protected void setAndroidLookupKey(String lookupKey) {
mAndroidLookupKey = lookupKey;
}
protected Uri getAndroidLookupUri() {
return ContactsContract.Contacts.getLookupUri(
Long.parseLong(mAndroidId), getAndroidLookupKey());
}
protected boolean isAndroidContact() {
return mAndroidId != null;
}
protected void addChangesToCommit(ContentProviderOperation operation) {
Log.i("[Contact] Added operation " + operation);
mChangesToCommit.add(operation);
}
protected void saveChangesCommited() {
if (ContactsManager.getInstance().hasReadContactsAccess() && mChangesToCommit.size() > 0) {
try {
ContentResolver contentResolver = LinphoneService.instance().getContentResolver();
ContentProviderResult[] results =
contentResolver.applyBatch(ContactsContract.AUTHORITY, mChangesToCommit);
if (results != null
&& results.length > 0
&& results[0] != null
&& results[0].uri != null) {
String rawId = String.valueOf(ContentUris.parseId(results[0].uri));
if (mAndroidId == null) {
Log.i("[Contact] Contact created with RAW ID " + rawId);
final String[] projection =
new String[] {ContactsContract.RawContacts.CONTACT_ID};
final Cursor cursor =
contentResolver.query(results[0].uri, projection, null, null, null);
if (cursor != null) {
cursor.moveToNext();
long contactId = cursor.getLong(0);
mAndroidId = String.valueOf(contactId);
cursor.close();
Log.i("[Contact] Contact created with ID " + mAndroidId);
}
} else {
if (mAndroidRawId == null || !isAndroidRawIdLinphone) {
Log.i(
"[Contact] Linphone RAW ID "
+ rawId
+ " created from existing RAW ID "
+ mAndroidRawId);
mAndroidRawId = rawId;
isAndroidRawIdLinphone = true;
}
}
}
} catch (Exception e) {
Log.e("[Contact] Exception while saving changes: " + e);
} finally {
mChangesToCommit.clear();
}
}
}
protected void createAndroidContact() {
if (LinphoneManager.getInstance()
.getContext()
.getResources()
.getBoolean(R.bool.use_linphone_tag)) {
Log.i("[Contact] Creating contact using linphone account type");
addChangesToCommit(
ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(
RawContacts.ACCOUNT_TYPE,
ContactsManager.getInstance()
.getString(R.string.sync_account_type))
.withValue(
RawContacts.ACCOUNT_NAME,
ContactsManager.getInstance()
.getString(R.string.sync_account_name))
.withValue(
RawContacts.AGGREGATION_MODE,
RawContacts.AGGREGATION_MODE_DEFAULT)
.build());
isAndroidRawIdLinphone = true;
} else {
Log.i("[Contact] Creating contact using default account type");
addChangesToCommit(
ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_TYPE, null)
.withValue(RawContacts.ACCOUNT_NAME, null)
.withValue(
RawContacts.AGGREGATION_MODE,
RawContacts.AGGREGATION_MODE_DEFAULT)
.build());
}
}
protected void deleteAndroidContact() {
ContactsManager.getInstance().delete(mAndroidId);
}
protected Uri getContactThumbnailPictureUri() {
Uri person = ContentUris.withAppendedId(Contacts.CONTENT_URI, Long.parseLong(mAndroidId));
return Uri.withAppendedPath(person, Contacts.Photo.CONTENT_DIRECTORY);
}
protected Uri getContactPictureUri() {
Uri person = ContentUris.withAppendedId(Contacts.CONTENT_URI, Long.parseLong(mAndroidId));
return Uri.withAppendedPath(person, Contacts.Photo.DISPLAY_PHOTO);
}
protected void setName(String fn, String ln) {
if ((fn == null || fn.isEmpty()) && (ln == null || ln.isEmpty())) {
Log.e("[Contact] Can't set both first and last name to null or empty");
return;
}
if (mAndroidId == null) {
Log.i("[Contact] Setting given & family name " + fn + " " + ln + " to new contact.");
addChangesToCommit(
ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(
Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.StructuredName.GIVEN_NAME, fn)
.withValue(CommonDataKinds.StructuredName.FAMILY_NAME, ln)
.build());
} else {
Log.i(
"[Contact] Setting given & family name "
+ fn
+ " "
+ ln
+ " to existing contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
String select =
ContactsContract.Data.CONTACT_ID
+ "=? AND "
+ ContactsContract.Data.MIMETYPE
+ "=?";
String[] args =
new String[] {
getAndroidId(),
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
};
addChangesToCommit(
ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(select, args)
.withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName
.CONTENT_ITEM_TYPE)
.withValue(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, fn)
.withValue(
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, ln)
.build());
}
}
protected void addNumberOrAddress(String value, String oldValueToReplace, boolean isSIP) {
if (value == null || value.isEmpty()) {
Log.e("[Contact] Can't add null or empty number or address");
return;
}
if (oldValueToReplace != null) {
if (mAndroidId == null) {
Log.e("[Contact] Can't update a number or address in non existing contact");
return;
}
Log.i(
"[Contact] Updating "
+ oldValueToReplace
+ " by "
+ value
+ " in contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
if (isSIP) {
String select =
ContactsContract.Data.CONTACT_ID
+ "=? AND ("
+ ContactsContract.Data.MIMETYPE
+ "=? OR "
+ ContactsContract.Data.MIMETYPE
+ "=? OR "
+ ContactsContract.Data.MIMETYPE
+ "=?) AND data1=?";
String[] args =
new String[] {
mAndroidId,
"vnd.android.cursor.item/org.linphone.profile", // Old value
ContactsManager.getInstance()
.getString(R.string.linphone_address_mime_type),
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,
oldValueToReplace
};
addChangesToCommit(
ContentProviderOperation.newUpdate(Data.CONTENT_URI)
.withSelection(select, args)
.withValue(
Data.MIMETYPE,
ContactsManager.getInstance()
.getString(R.string.linphone_address_mime_type))
.withValue("data1", value) // Value
.withValue(
"data2",
ContactsManager.getInstance()
.getString(R.string.app_name)) // Summary
.withValue("data3", value) // Detail
.build());
} else {
String select =
ContactsContract.Data.CONTACT_ID
+ "=? AND "
+ ContactsContract.Data.MIMETYPE
+ "=? AND data1=?";
String[] args =
new String[] {
mAndroidId,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
oldValueToReplace
};
addChangesToCommit(
ContentProviderOperation.newUpdate(Data.CONTENT_URI)
.withSelection(select, args)
.withValue(Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, value)
.withValue(
ContactsContract.CommonDataKinds.Phone.TYPE,
CommonDataKinds.Phone.TYPE_MOBILE)
.build());
}
} else {
if (mAndroidId == null) {
Log.i("[Contact] Adding number or address " + value + " to new contact.");
if (isSIP) {
addChangesToCommit(
ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(
Data.MIMETYPE,
ContactsManager.getInstance()
.getString(R.string.linphone_address_mime_type))
.withValue("data1", value) // Value
.withValue(
"data2",
ContactsManager.getInstance()
.getString(R.string.app_name)) // Summary
.withValue("data3", value) // Detail
.build());
} else {
addChangesToCommit(
ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(
Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, value)
.withValue(
ContactsContract.CommonDataKinds.Phone.TYPE,
CommonDataKinds.Phone.TYPE_MOBILE)
.build());
}
} else {
Log.i(
"[Contact] Adding number or address "
+ value
+ " to existing contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
if (isSIP) {
addChangesToCommit(
ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValue(ContactsContract.Data.RAW_CONTACT_ID, mAndroidRawId)
.withValue(
Data.MIMETYPE,
ContactsManager.getInstance()
.getString(R.string.linphone_address_mime_type))
.withValue("data1", value) // Value
.withValue(
"data2",
ContactsManager.getInstance()
.getString(R.string.app_name)) // Summary
.withValue("data3", value) // Detail
.build());
} else {
addChangesToCommit(
ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValue(ContactsContract.Data.RAW_CONTACT_ID, mAndroidRawId)
.withValue(
Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, value)
.withValue(
ContactsContract.CommonDataKinds.Phone.TYPE,
CommonDataKinds.Phone.TYPE_MOBILE)
.build());
}
}
}
}
protected void removeNumberOrAddress(String noa, boolean isSIP) {
if (noa == null || noa.isEmpty()) {
Log.e("[Contact] Can't remove null or empty number or address.");
return;
}
if (mAndroidId == null) {
Log.e("[Contact] Can't remove a number or address from non existing contact");
return;
} else {
Log.i(
"[Contact] Removing number or address "
+ noa
+ " from existing contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
if (isSIP) {
String select =
ContactsContract.Data.CONTACT_ID
+ "=? AND ("
+ ContactsContract.Data.MIMETYPE
+ "=? OR "
+ ContactsContract.Data.MIMETYPE
+ "=?) AND data1=?";
String[] args =
new String[] {
mAndroidId,
ContactsManager.getInstance()
.getString(R.string.linphone_address_mime_type),
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,
noa
};
addChangesToCommit(
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(select, args)
.build());
} else {
String select =
ContactsContract.Data.CONTACT_ID
+ "=? AND "
+ ContactsContract.Data.MIMETYPE
+ "=? AND data1=?";
String[] args =
new String[] {
mAndroidId,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
noa
};
addChangesToCommit(
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI)
.withSelection(select, args)
.build());
}
}
}
protected void setOrganization(String org, String previousValue) {
if (org == null || org.isEmpty()) {
if (mAndroidId == null) {
Log.e("[Contact] Can't set organization to null or empty for new contact");
return;
}
}
if (mAndroidId == null) {
Log.i("[Contact] Setting organization " + org + " to new contact.");
addChangesToCommit(
ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
.withValue(
Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
.withValue(CommonDataKinds.Organization.COMPANY, org)
.build());
} else {
if (previousValue != null) {
String select =
ContactsContract.Data.CONTACT_ID
+ "=? AND "
+ ContactsContract.Data.MIMETYPE
+ "=? AND "
+ ContactsContract.CommonDataKinds.Organization.COMPANY
+ "=?";
String[] args =
new String[] {
getAndroidId(),
ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE,
previousValue
};
Log.i(
"[Contact] Updating organization "
+ org
+ " to existing contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
addChangesToCommit(
ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(select, args)
.withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Organization
.CONTENT_ITEM_TYPE)
.withValue(
ContactsContract.CommonDataKinds.Organization.COMPANY, org)
.build());
} else {
Log.i(
"[Contact] Setting organization "
+ org
+ " to existing contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
addChangesToCommit(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValue(ContactsContract.Data.RAW_CONTACT_ID, mAndroidRawId)
.withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Organization
.CONTENT_ITEM_TYPE)
.withValue(
ContactsContract.CommonDataKinds.Organization.COMPANY, org)
.build());
}
}
}
protected void setPhoto(byte[] photo) {
if (photo == null) {
Log.e("[Contact] Can't set null picture.");
return;
}
if (mAndroidId == null) {
Log.i("[Contact] Setting picture to new contact.");
addChangesToCommit(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, photo)
.build());
} else {
Log.i(
"[Contact] Setting picture to existing contact "
+ mAndroidId
+ " ("
+ mAndroidRawId
+ ")");
addChangesToCommit(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValue(ContactsContract.Data.RAW_CONTACT_ID, mAndroidRawId)
.withValue(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, photo)
.withValue(ContactsContract.Data.IS_PRIMARY, 1)
.withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1)
.build());
}
}
protected String findRawContactID() {
ContentResolver resolver = LinphoneService.instance().getContentResolver();
String result = null;
String[] projection = {ContactsContract.RawContacts._ID};
String selection = ContactsContract.RawContacts.CONTACT_ID + "=?";
Cursor c =
resolver.query(
ContactsContract.RawContacts.CONTENT_URI,
projection,
selection,
new String[] {mAndroidId},
null);
if (c != null) {
if (c.moveToFirst()) {
result = c.getString(c.getColumnIndex(ContactsContract.RawContacts._ID));
}
c.close();
}
return result;
}
protected void createRawLinphoneContactFromExistingAndroidContactIfNeeded(String fullName) {
if (LinphoneManager.getInstance()
.getContext()
.getResources()
.getBoolean(R.bool.use_linphone_tag)) {
if (mAndroidId != null && (mAndroidRawId == null || !isAndroidRawIdLinphone)) {
if (mAndroidRawId == null) {
Log.i("[Contact] RAW ID not found for contact " + mAndroidId);
mAndroidRawId = findRawContactID();
}
Log.i("[Contact] Found RAW ID for contact " + mAndroidId + " : " + mAndroidRawId);
String linphoneRawId = findLinphoneRawContactId();
if (linphoneRawId == null) {
Log.i("[Contact] Linphone RAW ID not found for contact " + mAndroidId);
createRawLinphoneContactFromExistingAndroidContact();
} else {
Log.i(
"[Contact] Linphone RAW ID found for contact "
+ mAndroidId
+ " : "
+ linphoneRawId);
mAndroidRawId = linphoneRawId;
}
isAndroidRawIdLinphone = true;
}
}
}
private void createRawLinphoneContactFromExistingAndroidContact() {
addChangesToCommit(
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(
ContactsContract.RawContacts.ACCOUNT_TYPE,
ContactsManager.getInstance().getString(R.string.sync_account_type))
.withValue(
ContactsContract.RawContacts.ACCOUNT_NAME,
ContactsManager.getInstance().getString(R.string.sync_account_name))
.withValue(
ContactsContract.RawContacts.AGGREGATION_MODE,
ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
.build());
addChangesToCommit(
ContentProviderOperation.newUpdate(
ContactsContract.AggregationExceptions.CONTENT_URI)
.withValue(
ContactsContract.AggregationExceptions.TYPE,
ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER)
.withValue(
ContactsContract.AggregationExceptions.RAW_CONTACT_ID1,
mAndroidRawId)
.withValueBackReference(
ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, 0)
.build());
Log.i(
"[Contact] Creating linphone RAW contact for contact "
+ mAndroidId
+ " linked with existing RAW contact "
+ mAndroidRawId);
saveChangesCommited();
}
private String findLinphoneRawContactId() {
ContentResolver resolver = LinphoneService.instance().getContentResolver();
String result = null;
String[] projection = {ContactsContract.RawContacts._ID};
String selection =
ContactsContract.RawContacts.CONTACT_ID
+ "=? AND "
+ ContactsContract.RawContacts.ACCOUNT_TYPE
+ "=?";
Cursor c =
resolver.query(
ContactsContract.RawContacts.CONTENT_URI,
projection,
selection,
new String[] {
mAndroidId,
ContactsManager.getInstance().getString(R.string.sync_account_type)
},
null);
if (c != null) {
if (c.moveToFirst()) {
result = c.getString(c.getColumnIndex(ContactsContract.RawContacts._ID));
}
c.close();
}
return result;
}
}

View file

@ -0,0 +1,244 @@
package org.linphone.contacts;
/*
AsyncContactsLoader.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.provider.ContactsContract;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.core.Core;
import org.linphone.core.Friend;
import org.linphone.core.FriendList;
import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
class AsyncContactsLoader extends AsyncTask<Void, Void, AsyncContactsLoader.AsyncContactsData> {
@SuppressLint("InlinedApi")
public static final String[] PROJECTION = {
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.LOOKUP_KEY,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
ContactsContract.Data.MIMETYPE,
"data1", // Company, Phone or SIP Address
"data2", // ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
"data3", // ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
"data4", // Normalized phone number
};
private Context mContext;
public AsyncContactsLoader(Context context) {
mContext = context;
}
@Override
protected void onPreExecute() {
Log.i("[Contacts Manager] Synchronization started");
if (mContext == null) {
mContext = LinphoneService.instance().getApplicationContext();
}
if (LinphonePreferences.instance().isFriendlistsubscriptionEnabled()) {
String rls = mContext.getString(R.string.rls_uri);
for (FriendList list : LinphoneManager.getLc().getFriendsLists()) {
if (rls != null
&& (list.getRlsAddress() == null
|| !list.getRlsAddress().asStringUriOnly().equals(rls))) {
list.setRlsUri(rls);
}
list.addListener(ContactsManager.getInstance());
}
}
}
@Override
protected AsyncContactsData doInBackground(Void... params) {
Log.i("[Contacts Manager] Background synchronization started");
Cursor c =
mContext.getContentResolver()
.query(
ContactsContract.Data.CONTENT_URI,
PROJECTION,
ContactsContract.Data.IN_VISIBLE_GROUP + " == 1",
null,
null);
HashMap<String, LinphoneContact> androidContactsCache = new HashMap<>();
AsyncContactsData data = new AsyncContactsData();
List<String> nativeIds = new ArrayList<>();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
for (FriendList list : lc.getFriendsLists()) {
for (Friend friend : list.getFriends()) {
if (isCancelled()) return data;
LinphoneContact contact = (LinphoneContact) friend.getUserData();
if (contact != null) {
contact.clearAddresses();
if (contact.getAndroidId() != null) {
androidContactsCache.put(contact.getAndroidId(), contact);
nativeIds.add(contact.getAndroidId());
}
} else {
if (friend.getRefKey() != null) {
// Friend has a refkey but no LinphoneContact => represents a
// native contact stored in db from a previous version of Linphone,
// remove it
list.removeFriend(friend);
} else {
// No refkey so it's a standalone contact
contact = new LinphoneContact();
contact.setFriend(friend);
contact.syncValuesFromFriend();
data.contacts.add(contact);
}
}
}
}
}
if (c != null) {
while (c.moveToNext()) {
if (isCancelled()) return data;
String id = c.getString(c.getColumnIndex(ContactsContract.Data.CONTACT_ID));
String lookupKey =
c.getString(c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
LinphoneContact contact = androidContactsCache.get(id);
if (contact == null) {
nativeIds.add(id);
contact = new LinphoneContact();
contact.setAndroidId(id);
contact.setAndroidLookupKey(lookupKey);
androidContactsCache.put(id, contact);
}
contact.syncValuesFromAndroidCusor(c);
}
c.close();
for (FriendList list : lc.getFriendsLists()) {
for (Friend friend : list.getFriends()) {
if (isCancelled()) return data;
LinphoneContact contact = (LinphoneContact) friend.getUserData();
if (contact != null && contact.isAndroidContact()) {
String id = contact.getAndroidId();
if (id != null && !nativeIds.contains(id)) {
// Has been removed since last fetch
androidContactsCache.remove(id);
}
}
}
}
nativeIds.clear();
}
for (LinphoneContact contact : androidContactsCache.values()) {
if (isCancelled()) return data;
if (contact.getFullName() == null) {
for (LinphoneNumberOrAddress noa : contact.getNumbersOrAddresses()) {
if (noa.isSIPAddress()) {
contact.setFullName(LinphoneUtils.getAddressDisplayName(noa.getValue()));
Log.w(
"[Contacts Manager] Couldn't find a display name for contact "
+ contact.getFullName()
+ ", used SIP address display name / username instead...");
break;
}
}
}
if (contact.getFriend() != null) {
for (LinphoneNumberOrAddress noa : contact.getNumbersOrAddresses()) {
PresenceModel pm =
contact.getFriend().getPresenceModelForUriOrTel(noa.getValue());
if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) {
data.sipContacts.add(contact);
break;
}
}
}
if (!mContext.getResources().getBoolean(R.bool.hide_sip_contacts_without_presence)) {
if (contact.hasAddress() && !data.sipContacts.contains(contact)) {
data.sipContacts.add(contact);
}
}
data.contacts.add(contact);
}
androidContactsCache.clear();
Collections.sort(data.contacts);
Collections.sort(data.sipContacts);
Log.i("[Contacts Manager] Background synchronization finished");
return data;
}
@Override
protected void onPostExecute(AsyncContactsData data) {
for (LinphoneContact contact : data.contacts) {
contact.createOrUpdateFriendFromNativeContact();
}
// Now that contact fetching is asynchronous, this is required to ensure
// presence subscription event will be sent with all friends
if (LinphonePreferences.instance().isFriendlistsubscriptionEnabled()) {
for (FriendList list : LinphoneManager.getLc().getFriendsLists()) {
list.updateSubscriptions();
}
}
ContactsManager.getInstance().setContacts(data.contacts);
ContactsManager.getInstance().setSipContacts(data.sipContacts);
for (ContactsUpdatedListener listener :
ContactsManager.getInstance().getContactsListeners()) {
listener.onContactsUpdated();
}
Log.i("[Contacts Manager] Synchronization finished");
}
class AsyncContactsData {
final List<LinphoneContact> contacts;
final List<LinphoneContact> sipContacts;
AsyncContactsData() {
contacts = new ArrayList<>();
sipContacts = new ArrayList<>();
}
}
}

View file

@ -20,64 +20,73 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import android.view.View; import android.view.View;
import java.io.Serializable;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Factory; import org.linphone.core.Factory;
import org.linphone.core.SearchResult; import org.linphone.core.FriendCapability;
import java.io.Serializable;
public class ContactAddress implements Serializable { public class ContactAddress implements Serializable {
private LinphoneContact contact; private LinphoneContact mContact;
private SearchResult result; private String mAddress;
private String address; private String mPhoneNumber;
private String phoneNumber; private boolean mIsLinphoneContact;
private boolean isLinphoneContact; private boolean mIsSelect = false;
private boolean isSelect = false; private boolean mIsAdmin = false;
private boolean isAdmin = false; private transient View mView;
private transient View view;
public ContactAddress(LinphoneContact c, String a, String pn, boolean isLC) {
init(c, a, pn, isLC);
}
public ContactAddress(LinphoneContact c, String a, String pn, boolean isLC, boolean isAdmin) {
init(c, a, pn, isLC);
mIsAdmin = isAdmin;
}
public boolean isAdmin() { public boolean isAdmin() {
return isAdmin; return mIsAdmin;
} }
public void setAdmin(boolean admin) { public void setAdmin(boolean admin) {
isAdmin = admin; mIsAdmin = admin;
} }
public boolean isSelect() { public boolean isSelect() {
return isSelect; return mIsSelect;
} }
public void setView(View v) { public void setSelect(boolean select) {
view = v; mIsSelect = select;
} }
public View getView() { public View getView() {
return view; return mView;
}
public void setView(View v) {
mView = v;
} }
public LinphoneContact getContact() { public LinphoneContact getContact() {
return contact; return mContact;
}
public SearchResult getResult() {
return result;
}
public void setResult(SearchResult result) {
this.result = result;
} }
public String getAddressAsDisplayableString() { public String getAddressAsDisplayableString() {
Address addr = getAddress(); Address addr = getAddress();
if (addr != null && addr.getUsername() != null) return addr.asStringUriOnly(); if (addr != null && addr.getUsername() != null) return addr.asStringUriOnly();
return address; return mAddress;
} }
public Address getAddress() { public Address getAddress() {
String presence = contact.getPresenceModelForUriOrTel((phoneNumber != null && !phoneNumber.isEmpty()) ? phoneNumber : address); String presence = null;
Address addr = Factory.instance().createAddress(presence != null ? presence : address); if (mContact != null) {
presence =
mContact.getContactFromPresenceModelForUriOrTel(
(mPhoneNumber != null && !mPhoneNumber.isEmpty())
? mPhoneNumber
: mAddress);
}
Address addr = Factory.instance().createAddress(presence != null ? presence : mAddress);
// Remove the user=phone URI param if existing, it will break everything otherwise // Remove the user=phone URI param if existing, it will break everything otherwise
if (addr.hasUriParam("user")) { if (addr.hasUriParam("user")) {
addr.removeUriParam("user"); addr.removeUriParam("user");
@ -86,8 +95,8 @@ public class ContactAddress implements Serializable {
} }
public String getDisplayName() { public String getDisplayName() {
if (address != null) { if (mAddress != null) {
Address addr = Factory.instance().createAddress(address); Address addr = Factory.instance().createAddress(mAddress);
if (addr != null) { if (addr != null) {
return addr.getDisplayName(); return addr.getDisplayName();
} }
@ -96,8 +105,8 @@ public class ContactAddress implements Serializable {
} }
public String getUsername() { public String getUsername() {
if (address != null) { if (mAddress != null) {
Address addr = Factory.instance().createAddress(address); Address addr = Factory.instance().createAddress(mAddress);
if (addr != null) { if (addr != null) {
return addr.getUsername(); return addr.getUsername();
} }
@ -106,31 +115,18 @@ public class ContactAddress implements Serializable {
} }
public String getPhoneNumber() { public String getPhoneNumber() {
return phoneNumber; return mPhoneNumber;
} }
public void setSelect(boolean select) { public boolean hasCapability(FriendCapability capability) {
isSelect = select; return mContact != null && mContact.hasFriendCapability(capability);
}
public boolean isLinphoneContact() {
return isLinphoneContact;
} }
private void init(LinphoneContact c, String a, String pn, boolean isLC) { private void init(LinphoneContact c, String a, String pn, boolean isLC) {
contact = c; mContact = c;
address = a; mAddress = a;
phoneNumber = pn; mPhoneNumber = pn;
isLinphoneContact = isLC; mIsLinphoneContact = isLC;
}
public ContactAddress(LinphoneContact c, String a, String pn, boolean isLC) {
init(c, a, pn, isLC);
}
public ContactAddress(LinphoneContact c, String a, String pn, boolean isLC, boolean isAdmin) {
init(c, a, pn, isLC);
this.isAdmin = isAdmin;
} }
@Override @Override
@ -138,8 +134,8 @@ public class ContactAddress implements Serializable {
if (other == null) return false; if (other == null) return false;
if (other == this) return true; if (other == this) return true;
if (!(other instanceof ContactAddress)) return false; if (!(other instanceof ContactAddress)) return false;
if (((ContactAddress) other).getAddressAsDisplayableString() == this.getAddressAsDisplayableString()) return ((ContactAddress) other)
return true; .getAddressAsDisplayableString()
return false; .equals(getAddressAsDisplayableString());
} }
} }

View file

@ -0,0 +1,398 @@
package org.linphone.contacts;
/*
ContactDetailsFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TableLayout;
import android.widget.TextView;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.Address;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.ChatRoomParams;
import org.linphone.core.Core;
import org.linphone.core.Factory;
import org.linphone.core.FriendCapability;
import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ContactDetailsFragment extends Fragment
implements OnClickListener, ContactsUpdatedListener {
private LinphoneContact mContact;
private ImageView mEditContact, mDeleteContact, mBack;
private TextView mOrganization;
private RelativeLayout mWaitLayout;
private LayoutInflater mInflater;
private View mView;
private boolean mDisplayChatAddressOnly = false;
private ChatRoom mChatRoom;
private ChatRoomListenerStub mChatRoomCreationListener;
private final OnClickListener mDialListener =
new OnClickListener() {
@Override
public void onClick(View v) {
if (LinphoneActivity.isInstanciated()) {
String tag = (String) v.getTag();
LinphoneActivity.instance()
.setAddresGoToDialerAndCall(tag, mContact.getFullName());
}
}
};
private final OnClickListener mChatListener =
new OnClickListener() {
@Override
public void onClick(View v) {
if (LinphoneActivity.isInstanciated()) {
String tag = (String) v.getTag();
Core lc = LinphoneManager.getLc();
Address participant = Factory.instance().createAddress(tag);
ProxyConfig defaultProxyConfig = lc.getDefaultProxyConfig();
boolean isSecured = v.getId() == R.id.contact_chat_secured;
if (defaultProxyConfig != null) {
ChatRoom room =
lc.findOneToOneChatRoom(
defaultProxyConfig.getContact(),
participant,
isSecured);
if (room != null) {
LinphoneActivity.instance()
.goToChat(
room.getLocalAddress().asStringUriOnly(),
room.getPeerAddress().asStringUriOnly(),
null);
} else {
if (defaultProxyConfig.getConferenceFactoryUri() != null
&& (isSecured
|| !LinphonePreferences.instance()
.useBasicChatRoomFor1To1())) {
mWaitLayout.setVisibility(View.VISIBLE);
ChatRoomParams params = lc.createDefaultChatRoomParams();
params.enableEncryption(isSecured);
params.enableGroup(false);
// We don't want a basic chat room,
// so if isSecured is false we have to set this manually
params.setBackend(ChatRoomBackend.FlexisipChat);
Address participants[] = new Address[1];
participants[0] = participant;
mChatRoom =
lc.createChatRoom(
params,
getString(R.string.dummy_group_chat_subject),
participants);
if (mChatRoom != null) {
mChatRoom.addListener(mChatRoomCreationListener);
} else {
Log.w(
"[Contact Details Fragment] createChatRoom returned null...");
mWaitLayout.setVisibility(View.GONE);
}
} else {
room = lc.getChatRoom(participant);
LinphoneActivity.instance()
.goToChat(
room.getLocalAddress().asStringUriOnly(),
room.getPeerAddress().asStringUriOnly(),
null);
}
}
}
}
}
};
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mContact = (LinphoneContact) getArguments().getSerializable("Contact");
this.mInflater = inflater;
mView = inflater.inflate(R.layout.contact, container, false);
if (getArguments() != null) {
mDisplayChatAddressOnly = getArguments().getBoolean("ChatAddressOnly");
}
mWaitLayout = mView.findViewById(R.id.waitScreen);
mWaitLayout.setVisibility(View.GONE);
mEditContact = mView.findViewById(R.id.editContact);
mEditContact.setOnClickListener(this);
mDeleteContact = mView.findViewById(R.id.deleteContact);
mDeleteContact.setOnClickListener(this);
mOrganization = mView.findViewById(R.id.contactOrganization);
boolean isOrgVisible = getResources().getBoolean(R.bool.display_contact_organization);
String org = mContact.getOrganization();
if (org != null && !org.isEmpty() && isOrgVisible) {
mOrganization.setText(org);
} else {
mOrganization.setVisibility(View.GONE);
}
mBack = mView.findViewById(R.id.back);
if (getResources().getBoolean(R.bool.isTablet)) {
mBack.setVisibility(View.INVISIBLE);
} else {
mBack.setOnClickListener(this);
}
mChatRoomCreationListener =
new ChatRoomListenerStub() {
@Override
public void onStateChanged(ChatRoom cr, ChatRoom.State newState) {
if (newState == ChatRoom.State.Created) {
mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance()
.goToChat(
cr.getLocalAddress().asStringUriOnly(),
cr.getPeerAddress().asStringUriOnly(),
null);
} else if (newState == ChatRoom.State.CreationFailed) {
mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance().displayChatRoomError();
Log.e(
"Group chat room for address "
+ cr.getPeerAddress()
+ " has failed !");
}
}
};
return mView;
}
public void changeDisplayedContact(LinphoneContact newContact) {
mContact = newContact;
displayContact(mInflater, mView);
}
@SuppressLint("InflateParams")
private void displayContact(LayoutInflater inflater, View view) {
ContactAvatar.displayAvatar(mContact, view.findViewById(R.id.avatar_layout));
TextView contactName = view.findViewById(R.id.contact_name);
contactName.setText(mContact.getFullName());
mOrganization.setText(
(mContact.getOrganization() != null) ? mContact.getOrganization() : "");
TableLayout controls = view.findViewById(R.id.controls);
controls.removeAllViews();
for (LinphoneNumberOrAddress noa : mContact.getNumbersOrAddresses()) {
boolean skip = false;
View v = inflater.inflate(R.layout.contact_control_row, null);
String value = noa.getValue();
String displayednumberOrAddress =
LinphoneUtils.getDisplayableUsernameFromAddress(value);
TextView label = v.findViewById(R.id.address_label);
if (noa.isSIPAddress()) {
label.setText(R.string.sip_address);
skip |= getResources().getBoolean(R.bool.hide_contact_sip_addresses);
} else {
label.setText(R.string.phone_number);
skip |= getResources().getBoolean(R.bool.hide_contact_phone_numbers);
}
TextView tv = v.findViewById(R.id.numeroOrAddress);
tv.setText(displayednumberOrAddress);
tv.setSelected(true);
ProxyConfig lpc = LinphoneManager.getLc().getDefaultProxyConfig();
if (lpc != null) {
String username = lpc.normalizePhoneNumber(displayednumberOrAddress);
if (username != null) {
value = LinphoneUtils.getFullAddressFromUsername(username);
}
}
v.findViewById(R.id.friendLinphone).setVisibility(View.GONE);
if (mContact.getFriend() != null) {
PresenceModel pm = mContact.getFriend().getPresenceModelForUriOrTel(noa.getValue());
if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) {
v.findViewById(R.id.friendLinphone).setVisibility(View.VISIBLE);
} else {
if (getResources()
.getBoolean(R.bool.hide_numbers_and_addresses_without_presence)) {
skip = true;
}
}
}
v.findViewById(R.id.inviteFriend).setVisibility(View.GONE);
if (!noa.isSIPAddress()
&& v.findViewById(R.id.friendLinphone).getVisibility() == View.GONE
&& !getResources().getBoolean(R.bool.hide_invite_contact)) {
v.findViewById(R.id.inviteFriend).setVisibility(View.VISIBLE);
v.findViewById(R.id.inviteFriend).setTag(noa.getNormalizedPhone());
v.findViewById(R.id.inviteFriend)
.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
String number = (String) v.getTag();
Intent smsIntent = new Intent(Intent.ACTION_SENDTO);
smsIntent.putExtra("address", number);
smsIntent.setData(Uri.parse("smsto:" + number));
String text =
getString(R.string.invite_friend_text)
.replace(
"%s",
getString(R.string.download_link));
smsIntent.putExtra("sms_body", text);
startActivity(smsIntent);
}
});
}
String contactAddress = mContact.getContactFromPresenceModelForUriOrTel(noa.getValue());
if (!mDisplayChatAddressOnly) {
v.findViewById(R.id.contact_call).setOnClickListener(mDialListener);
if (contactAddress != null) {
v.findViewById(R.id.contact_call).setTag(contactAddress);
} else {
v.findViewById(R.id.contact_call).setTag(value);
}
} else {
v.findViewById(R.id.contact_call).setVisibility(View.GONE);
}
v.findViewById(R.id.contact_chat).setOnClickListener(mChatListener);
v.findViewById(R.id.contact_chat_secured).setOnClickListener(mChatListener);
if (contactAddress != null) {
v.findViewById(R.id.contact_chat).setTag(contactAddress);
v.findViewById(R.id.contact_chat_secured).setTag(contactAddress);
} else {
v.findViewById(R.id.contact_chat).setTag(value);
v.findViewById(R.id.contact_chat_secured).setTag(value);
}
if (v.findViewById(R.id.friendLinphone).getVisibility() == View.VISIBLE
&& mContact.hasPresenceModelForUriOrTelCapability(
noa.getValue(), FriendCapability.LimeX3Dh)) {
v.findViewById(R.id.contact_chat_secured).setVisibility(View.VISIBLE);
} else {
v.findViewById(R.id.contact_chat_secured).setVisibility(View.GONE);
}
if (getResources().getBoolean(R.bool.disable_chat)) {
v.findViewById(R.id.contact_chat).setVisibility(View.GONE);
v.findViewById(R.id.contact_chat_secured).setVisibility(View.GONE);
}
if (!skip) {
controls.addView(v);
}
}
}
@Override
public void onContactsUpdated() {
LinphoneContact contact =
ContactsManager.getInstance().findContactFromAndroidId(mContact.getAndroidId());
if (contact != null) {
changeDisplayedContact(contact);
}
}
@Override
public void onResume() {
super.onResume();
ContactsManager.getInstance().addContactsListener(this);
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.CONTACT_DETAIL);
}
displayContact(mInflater, mView);
}
@Override
public void onPause() {
if (mChatRoom != null) {
mChatRoom.removeListener(mChatRoomCreationListener);
}
ContactsManager.getInstance().removeContactsListener(this);
super.onPause();
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.editContact) {
ContactsManager.getInstance().editContact(getActivity(), mContact, null);
} else if (id == R.id.deleteContact) {
final Dialog dialog =
LinphoneActivity.instance().displayDialog(getString(R.string.delete_text));
Button delete = dialog.findViewById(R.id.dialog_delete_button);
Button cancel = dialog.findViewById(R.id.dialog_cancel_button);
delete.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
mContact.delete();
// To ensure removed contact won't appear in the contacts list anymore
ContactsManager.getInstance().fetchContactsAsync();
LinphoneActivity.instance().displayContacts(false);
dialog.dismiss();
}
});
cancel.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
}
});
dialog.show();
} else if (id == R.id.back) {
getFragmentManager().popBackStackImmediate();
}
}
}

View file

@ -0,0 +1,676 @@
package org.linphone.contacts;
/*
ContactEditorFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.ContactsContract.DisplayPhoto;
import android.provider.MediaStore;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.tools.Log;
import org.linphone.mediastream.Version;
import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class ContactEditorFragment extends Fragment {
private static final int ADD_PHOTO = 1337;
private static final int PHOTO_SIZE = 128;
private View mView;
private ImageView mCancel, mDeleteContact, mOk;
private ImageView mAddNumber, mAddSipAddress, mContactPicture;
private LinearLayout mPhoneNumbersSection, mSipAddressesSection;
private EditText mFirstName, mLastName, mOrganization;
private LayoutInflater mInflater;
private boolean mIsNewContact;
private LinphoneContact mContact;
private List<LinphoneNumberOrAddress> mNumbersAndAddresses;
private int mFirstSipAddressIndex = -1;
private LinearLayout mSipAddresses, mNumbers;
private String mNewSipOrNumberToAdd, mNewDisplayName;
private Uri mPickedPhotoForContactUri;
private byte[] mPhotoToAdd;
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mInflater = inflater;
mContact = null;
mIsNewContact = true;
if (getArguments() != null) {
Serializable obj = getArguments().getSerializable("Contact");
if (obj != null) {
mContact = (LinphoneContact) obj;
mContact.createRawLinphoneContactFromExistingAndroidContactIfNeeded(
mContact.getFullName());
mIsNewContact = false;
if (getArguments().getString("NewSipAdress") != null) {
mNewSipOrNumberToAdd = getArguments().getString("NewSipAdress");
}
if (getArguments().getString("NewDisplayName") != null) {
mNewDisplayName = getArguments().getString("NewDisplayName");
}
} else if (getArguments().getString("NewSipAdress") != null) {
mNewSipOrNumberToAdd = getArguments().getString("NewSipAdress");
if (getArguments().getString("NewDisplayName") != null) {
mNewDisplayName = getArguments().getString("NewDisplayName");
}
}
}
mView = inflater.inflate(R.layout.contact_edit, container, false);
mPhoneNumbersSection = mView.findViewById(R.id.phone_numbers);
if (getResources().getBoolean(R.bool.hide_phone_numbers_in_editor)
|| !ContactsManager.getInstance().hasReadContactsAccess()) {
// Currently linphone friends don't support phone mNumbers, so hide them
mPhoneNumbersSection.setVisibility(View.GONE);
}
mSipAddressesSection = mView.findViewById(R.id.sip_addresses);
if (getResources().getBoolean(R.bool.hide_sip_addresses_in_editor)) {
mSipAddressesSection.setVisibility(View.GONE);
}
mDeleteContact = mView.findViewById(R.id.delete_contact);
mCancel = mView.findViewById(R.id.cancel);
mCancel.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
getFragmentManager().popBackStackImmediate();
}
});
mOk = mView.findViewById(R.id.ok);
mOk.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
if (mIsNewContact) {
boolean areAllFielsEmpty = true;
for (LinphoneNumberOrAddress nounoa : mNumbersAndAddresses) {
if (nounoa.getValue() != null && !nounoa.getValue().equals("")) {
areAllFielsEmpty = false;
break;
}
}
if (areAllFielsEmpty) {
getFragmentManager().popBackStackImmediate();
return;
}
mContact = LinphoneContact.createContact();
}
mContact.setFirstNameAndLastName(
mFirstName.getText().toString(),
mLastName.getText().toString(),
true);
if (mPhotoToAdd != null) {
mContact.setPhoto(mPhotoToAdd);
}
for (LinphoneNumberOrAddress noa : mNumbersAndAddresses) {
if (noa.getValue() == null || noa.getValue().isEmpty()) {
if (noa.getOldValue() != null && !noa.getOldValue().isEmpty()) {
Log.i("[Contact Editor] Removing number " + noa.getOldValue());
mContact.removeNumberOrAddress(noa);
}
} else {
if (noa.getOldValue() != null
&& noa.getOldValue().equals(noa.getValue())) {
Log.i(
"[Contact Editor] Keeping existing number "
+ noa.getValue());
continue;
}
if (noa.isSIPAddress()) {
noa.setValue(
LinphoneUtils.getFullAddressFromUsername(
noa.getValue()));
}
Log.i("[Contact Editor] Adding new number " + noa.getValue());
mContact.addOrUpdateNumberOrAddress(noa);
}
}
if (!mOrganization.getText().toString().isEmpty() || !mIsNewContact) {
mContact.setOrganization(mOrganization.getText().toString(), true);
}
mContact.save();
if (mIsNewContact) {
// Ensure fetch will be done so the new contact appears in the contacts
// list: contacts content observer may not be notified if contacts sync
// is disabled at system level
ContactsManager.getInstance().fetchContactsAsync();
}
getFragmentManager().popBackStackImmediate();
if (mIsNewContact || LinphoneActivity.instance().isTablet()) {
LinphoneActivity.instance().displayContact(mContact, false);
}
}
});
mLastName = mView.findViewById(R.id.contactLastName);
// Hack to display keyboard when touching focused edittext on Nexus One
if (Version.sdkStrictlyBelow(Version.API11_HONEYCOMB_30)) {
mLastName.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
InputMethodManager imm =
(InputMethodManager)
LinphoneActivity.instance()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
});
}
mLastName.addTextChangedListener(
new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mOk.setEnabled(
mLastName.getText().length() > 0
|| mFirstName.getText().length() > 0);
}
@Override
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {}
});
mFirstName = mView.findViewById(R.id.contactFirstName);
mFirstName.addTextChangedListener(
new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mOk.setEnabled(
mFirstName.getText().length() > 0
|| mLastName.getText().length() > 0);
}
@Override
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {}
});
mOrganization = mView.findViewById(R.id.contactOrganization);
boolean isOrgVisible = getResources().getBoolean(R.bool.display_contact_organization);
if (!isOrgVisible) {
mOrganization.setVisibility(View.GONE);
mView.findViewById(R.id.contactOrganizationTitle).setVisibility(View.GONE);
} else {
if (!mIsNewContact) {
mOrganization.setText(mContact.getOrganization());
}
}
if (!mIsNewContact) {
String fn = mContact.getFirstName();
String ln = mContact.getLastName();
if (fn != null || ln != null) {
mFirstName.setText(fn);
mLastName.setText(ln);
} else {
mLastName.setText(mContact.getFullName());
mFirstName.setText("");
}
mDeleteContact.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
final Dialog dialog =
LinphoneActivity.instance()
.displayDialog(getString(R.string.delete_text));
Button delete = dialog.findViewById(R.id.dialog_delete_button);
Button cancel = dialog.findViewById(R.id.dialog_cancel_button);
delete.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
mContact.delete();
LinphoneActivity.instance().displayContacts(false);
dialog.dismiss();
}
});
cancel.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
}
});
dialog.show();
}
});
} else {
mDeleteContact.setVisibility(View.INVISIBLE);
}
mContactPicture = mView.findViewById(R.id.contact_picture);
if (mContact != null) {
ContactAvatar.displayAvatar(mContact, mView.findViewById(R.id.avatar_layout));
} else {
ContactAvatar.displayAvatar("", mView.findViewById(R.id.avatar_layout));
}
mContactPicture.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
pickImage();
LinphoneActivity.instance().checkAndRequestCameraPermission();
}
});
mNumbersAndAddresses = new ArrayList<>();
mSipAddresses = initSipAddressFields(mContact);
mNumbers = initNumbersFields(mContact);
mAddSipAddress = mView.findViewById(R.id.add_address_field);
if (getResources().getBoolean(R.bool.allow_only_one_sip_address)) {
mAddSipAddress.setVisibility(View.GONE);
}
mAddSipAddress.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
addEmptyRowToAllowNewNumberOrAddress(mSipAddresses, true);
}
});
mAddNumber = mView.findViewById(R.id.add_number_field);
if (getResources().getBoolean(R.bool.allow_only_one_phone_number)) {
mAddNumber.setVisibility(View.GONE);
}
mAddNumber.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
addEmptyRowToAllowNewNumberOrAddress(mNumbers, false);
}
});
mLastName.requestFocus();
return mView;
}
@Override
public void onResume() {
super.onResume();
// Force hide keyboard
getActivity()
.getWindow()
.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
}
@Override
public void onPause() {
// Force hide keyboard
InputMethodManager imm =
(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
View view = getActivity().getCurrentFocus();
if (imm != null && view != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
super.onPause();
}
private void pickImage() {
mPickedPhotoForContactUri = null;
final List<Intent> cameraIntents = new ArrayList<>();
final Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file =
new File(
FileUtils.getStorageDirectory(LinphoneActivity.instance()),
getString(R.string.temp_photo_name));
mPickedPhotoForContactUri = Uri.fromFile(file);
captureIntent.putExtra("outputX", PHOTO_SIZE);
captureIntent.putExtra("outputY", PHOTO_SIZE);
captureIntent.putExtra("aspectX", 0);
captureIntent.putExtra("aspectY", 0);
captureIntent.putExtra("scale", true);
captureIntent.putExtra("return-data", false);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mPickedPhotoForContactUri);
cameraIntents.add(captureIntent);
final Intent galleryIntent = new Intent();
galleryIntent.setType("image/*");
galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
final Intent chooserIntent =
Intent.createChooser(galleryIntent, getString(R.string.image_picker_title));
chooserIntent.putExtra(
Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[] {}));
startActivityForResult(chooserIntent, ADD_PHOTO);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ADD_PHOTO && resultCode == Activity.RESULT_OK) {
if (data != null && data.getExtras() != null && data.getExtras().get("data") != null) {
Bitmap bm = (Bitmap) data.getExtras().get("data");
editContactPicture(null, bm);
} else if (data != null && data.getData() != null) {
Uri selectedImageUri = data.getData();
try {
Bitmap selectedImage =
MediaStore.Images.Media.getBitmap(
LinphoneManager.getInstance().getContext().getContentResolver(),
selectedImageUri);
selectedImage =
Bitmap.createScaledBitmap(selectedImage, PHOTO_SIZE, PHOTO_SIZE, false);
editContactPicture(null, selectedImage);
} catch (IOException e) {
Log.e(e);
}
} else if (mPickedPhotoForContactUri != null) {
String filePath = mPickedPhotoForContactUri.getPath();
editContactPicture(filePath, null);
} else {
File file =
new File(
FileUtils.getStorageDirectory(LinphoneActivity.instance()),
getString(R.string.temp_photo_name));
if (file.exists()) {
mPickedPhotoForContactUri = Uri.fromFile(file);
String filePath = mPickedPhotoForContactUri.getPath();
editContactPicture(filePath, null);
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void editContactPicture(String filePath, Bitmap image) {
if (image == null) {
image = BitmapFactory.decodeFile(filePath);
}
Bitmap scaledPhoto;
int size = getThumbnailSize();
if (size > 0) {
scaledPhoto = Bitmap.createScaledBitmap(image, size, size, false);
} else {
scaledPhoto = Bitmap.createBitmap(image);
}
image.recycle();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
scaledPhoto.compress(Bitmap.CompressFormat.PNG, 0, stream);
mContactPicture.setImageBitmap(scaledPhoto);
mPhotoToAdd = stream.toByteArray();
}
private int getThumbnailSize() {
int value = -1;
Cursor c =
LinphoneActivity.instance()
.getContentResolver()
.query(
DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
new String[] {DisplayPhoto.THUMBNAIL_MAX_DIM},
null,
null,
null);
try {
c.moveToFirst();
value = c.getInt(0);
} catch (Exception e) {
Log.e(e);
}
c.close();
return value;
}
private LinearLayout initNumbersFields(final LinphoneContact contact) {
LinearLayout controls = mView.findViewById(R.id.controls_numbers);
controls.removeAllViews();
if (contact != null) {
for (LinphoneNumberOrAddress numberOrAddress : contact.getNumbersOrAddresses()) {
if (!numberOrAddress.isSIPAddress()) {
View view = displayNumberOrAddress(controls, numberOrAddress.getValue(), false);
if (view != null) controls.addView(view);
}
}
}
if (mNewSipOrNumberToAdd != null) {
boolean isSip =
LinphoneUtils.isStrictSipAddress(mNewSipOrNumberToAdd)
|| !LinphoneUtils.isNumberAddress(mNewSipOrNumberToAdd);
if (!isSip) {
View view = displayNumberOrAddress(controls, mNewSipOrNumberToAdd, false);
if (view != null) controls.addView(view);
}
}
if (mNewDisplayName != null) {
EditText lastNameEditText = mView.findViewById(R.id.contactLastName);
if (mView != null) lastNameEditText.setText(mNewDisplayName);
}
if (controls.getChildCount() == 0) {
addEmptyRowToAllowNewNumberOrAddress(controls, false);
}
return controls;
}
private LinearLayout initSipAddressFields(final LinphoneContact contact) {
LinearLayout controls = mView.findViewById(R.id.controls_sip_address);
controls.removeAllViews();
if (contact != null) {
for (LinphoneNumberOrAddress numberOrAddress : contact.getNumbersOrAddresses()) {
if (numberOrAddress.isSIPAddress()) {
View view = displayNumberOrAddress(controls, numberOrAddress.getValue(), true);
if (view != null) controls.addView(view);
}
}
}
if (mNewSipOrNumberToAdd != null) {
boolean isSip =
LinphoneUtils.isStrictSipAddress(mNewSipOrNumberToAdd)
|| !LinphoneUtils.isNumberAddress(mNewSipOrNumberToAdd);
if (isSip) {
View view = displayNumberOrAddress(controls, mNewSipOrNumberToAdd, true);
if (view != null) controls.addView(view);
}
}
if (controls.getChildCount() == 0) {
addEmptyRowToAllowNewNumberOrAddress(controls, true);
}
return controls;
}
private View displayNumberOrAddress(
final LinearLayout controls, String numberOrAddress, boolean isSIP) {
String displayNumberOrAddress = numberOrAddress;
if (isSIP) {
if (mFirstSipAddressIndex == -1) {
mFirstSipAddressIndex = controls.getChildCount();
}
displayNumberOrAddress =
LinphoneUtils.getDisplayableUsernameFromAddress(numberOrAddress);
}
if ((getResources().getBoolean(R.bool.hide_phone_numbers_in_editor) && !isSIP)
|| (getResources().getBoolean(R.bool.hide_sip_addresses_in_editor) && isSIP)) {
return null;
}
LinphoneNumberOrAddress tempNounoa;
if (mIsNewContact || mNewSipOrNumberToAdd != null) {
tempNounoa = new LinphoneNumberOrAddress(numberOrAddress, isSIP);
} else {
tempNounoa = new LinphoneNumberOrAddress(numberOrAddress, isSIP, numberOrAddress);
}
final LinphoneNumberOrAddress nounoa = tempNounoa;
mNumbersAndAddresses.add(nounoa);
final View view = mInflater.inflate(R.layout.contact_edit_row, null);
final EditText noa = view.findViewById(R.id.numoraddr);
if (!isSIP) {
noa.setInputType(InputType.TYPE_CLASS_PHONE);
}
noa.setText(displayNumberOrAddress);
noa.addTextChangedListener(
new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
nounoa.setValue(noa.getText().toString());
}
@Override
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {}
});
ImageView delete = view.findViewById(R.id.delete_field);
if ((getResources().getBoolean(R.bool.allow_only_one_phone_number) && !isSIP)
|| (getResources().getBoolean(R.bool.allow_only_one_sip_address) && isSIP)) {
delete.setVisibility(View.GONE);
}
delete.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
if (mContact != null) {
mContact.removeNumberOrAddress(nounoa);
}
mNumbersAndAddresses.remove(nounoa);
view.setVisibility(View.GONE);
}
});
return view;
}
@SuppressLint("InflateParams")
private void addEmptyRowToAllowNewNumberOrAddress(
final LinearLayout controls, final boolean isSip) {
final View view = mInflater.inflate(R.layout.contact_edit_row, null);
final LinphoneNumberOrAddress nounoa = new LinphoneNumberOrAddress(null, isSip);
final EditText noa = view.findViewById(R.id.numoraddr);
mNumbersAndAddresses.add(nounoa);
noa.setHint(isSip ? getString(R.string.sip_address) : getString(R.string.phone_number));
if (!isSip) {
noa.setInputType(InputType.TYPE_CLASS_PHONE);
noa.setHint(R.string.phone_number);
} else {
noa.setHint(R.string.sip_address);
}
noa.requestFocus();
noa.addTextChangedListener(
new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
nounoa.setValue(noa.getText().toString());
}
@Override
public void beforeTextChanged(
CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {}
});
final ImageView delete = view.findViewById(R.id.delete_field);
if ((getResources().getBoolean(R.bool.allow_only_one_phone_number) && !isSip)
|| (getResources().getBoolean(R.bool.allow_only_one_sip_address) && isSip)) {
delete.setVisibility(View.GONE);
}
delete.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
mNumbersAndAddresses.remove(nounoa);
view.setVisibility(View.GONE);
}
});
controls.addView(view);
}
}

View file

@ -0,0 +1,77 @@
package org.linphone.contacts;
/*
ContactViewHolder.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.linphone.R;
public class ContactViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnLongClickListener {
public final CheckBox delete;
public final ImageView linphoneFriend;
public final TextView name;
public final LinearLayout separator;
public final TextView separatorText;
public final RelativeLayout avatarLayout;
public final TextView organization;
private final ClickListener mListener;
public ContactViewHolder(View view, ClickListener listener) {
super(view);
delete = view.findViewById(R.id.delete);
linphoneFriend = view.findViewById(R.id.friendLinphone);
name = view.findViewById(R.id.name);
separator = view.findViewById(R.id.separator);
separatorText = view.findViewById(R.id.separator_text);
avatarLayout = view.findViewById(R.id.avatar_layout);
organization = view.findViewById(R.id.contactOrganization);
// friendStatus = view.findViewById(R.id.friendStatus);
mListener = listener;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
@Override
public void onClick(View view) {
if (mListener != null) {
mListener.onItemClicked(getAdapterPosition());
}
}
public boolean onLongClick(View v) {
if (mListener != null) {
return mListener.onItemLongClicked(getAdapterPosition());
}
return false;
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}

View file

@ -0,0 +1,173 @@
package org.linphone.contacts;
/*
ContactsAdapter.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SectionIndexer;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.linphone.R;
import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper;
import org.linphone.views.ContactAvatar;
public class ContactsAdapter extends SelectableAdapter<ContactViewHolder>
implements SectionIndexer {
private List<LinphoneContact> mContacts;
private String[] mSections;
private ArrayList<String> mSectionsList;
private Map<String, Integer> mMap = new LinkedHashMap<>();
private final ContactViewHolder.ClickListener mClickListener;
private final Context mContext;
private boolean mIsSearchMode;
ContactsAdapter(
Context context,
List<LinphoneContact> contactsList,
ContactViewHolder.ClickListener clickListener,
SelectableHelper helper) {
super(helper);
mContext = context;
updateDataSet(contactsList);
mClickListener = clickListener;
}
@NonNull
@Override
public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.contact_cell, parent, false);
return new ContactViewHolder(v, mClickListener);
}
@Override
public void onBindViewHolder(@NonNull ContactViewHolder holder, final int position) {
LinphoneContact contact = (LinphoneContact) getItem(position);
holder.name.setText(contact.getFullName());
if (!mIsSearchMode) {
String fullName = contact.getFullName();
if (fullName != null && !fullName.isEmpty()) {
holder.separatorText.setText(String.valueOf(fullName.charAt(0)));
}
}
holder.separator.setVisibility(
mIsSearchMode
|| (!mIsSearchMode
&& getPositionForSection(getSectionForPosition(position))
!= position)
? View.GONE
: View.VISIBLE);
holder.linphoneFriend.setVisibility(contact.isInFriendList() ? View.VISIBLE : View.GONE);
ContactAvatar.displayAvatar(contact, holder.avatarLayout);
boolean isOrgVisible =
mContext.getResources().getBoolean(R.bool.display_contact_organization);
String org = contact.getOrganization();
if (org != null && !org.isEmpty() && isOrgVisible) {
holder.organization.setText(org);
holder.organization.setVisibility(View.VISIBLE);
} else {
holder.organization.setVisibility(View.GONE);
}
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.INVISIBLE);
holder.delete.setChecked(isSelected(position));
}
@Override
public int getItemCount() {
return mContacts.size();
}
public Object getItem(int position) {
if (position >= getItemCount()) return null;
return mContacts.get(position);
}
public void setIsSearchMode(boolean set) {
mIsSearchMode = set;
}
public long getItemId(int position) {
return position;
}
public void updateDataSet(List<LinphoneContact> contactsList) {
mContacts = contactsList;
mMap = new LinkedHashMap<>();
String prevLetter = null;
for (int i = 0; i < mContacts.size(); i++) {
LinphoneContact contact = mContacts.get(i);
String fullName = contact.getFullName();
if (fullName == null || fullName.isEmpty()) {
continue;
}
String firstLetter = fullName.substring(0, 1).toUpperCase(Locale.getDefault());
if (!firstLetter.equals(prevLetter)) {
prevLetter = firstLetter;
mMap.put(firstLetter, i);
}
}
mSectionsList = new ArrayList<>(mMap.keySet());
mSections = new String[mSectionsList.size()];
mSectionsList.toArray(mSections);
notifyDataSetChanged();
}
@Override
public Object[] getSections() {
return mSections;
}
@Override
public int getPositionForSection(int sectionIndex) {
if (sectionIndex >= mSections.length || sectionIndex < 0) {
return 0;
}
return mMap.get(mSections[sectionIndex]);
}
@Override
public int getSectionForPosition(int position) {
if (position >= mContacts.size() || position < 0) {
return 0;
}
LinphoneContact contact = mContacts.get(position);
String fullName = contact.getFullName();
if (fullName == null || fullName.isEmpty()) {
return 0;
}
String letter = fullName.substring(0, 1).toUpperCase(Locale.getDefault());
return mSectionsList.indexOf(letter);
}
}

View file

@ -0,0 +1,418 @@
package org.linphone.contacts;
/*
ContactsFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.utils.SelectableHelper;
public class ContactsFragment extends Fragment
implements OnItemClickListener,
ContactsUpdatedListener,
ContactViewHolder.ClickListener,
SelectableHelper.DeleteListener {
private RecyclerView mContactsList;
private TextView mNoSipContact, mNoContact;
private ImageView mAllContacts, mLinphoneContacts, mNewContact;
private boolean mOnlyDisplayLinphoneContacts;
private View mAllContactsSelected, mLinphoneContactsSelected;
private int mLastKnownPosition;
private boolean mEditOnClick = false, mEditConsumed = false, mOnlyDisplayChatAddress = false;
private String mSipAddressToAdd, mDisplayName = null;
private SearchView mSearchView;
private ProgressBar mContactsFetchInProgress;
private LinearLayoutManager mLayoutManager;
private Context mContext;
private SelectableHelper mSelectionHelper;
private ContactsAdapter mContactAdapter;
private SwipeRefreshLayout mContactsRefresher;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.contacts_list, container, false);
mContext = getActivity().getApplicationContext();
mSelectionHelper = new SelectableHelper(view, this);
mSelectionHelper.setDialogMessage(R.string.delete_contacts_text);
if (getArguments() != null) {
mEditOnClick = getArguments().getBoolean("EditOnClick");
mSipAddressToAdd = getArguments().getString("SipAddress");
if (getArguments().getString("DisplayName") != null) {
mDisplayName = getArguments().getString("DisplayName");
}
mOnlyDisplayChatAddress = getArguments().getBoolean("ChatAddressOnly");
if (getArguments().getBoolean("EditOnClick")) {
Toast.makeText(
LinphoneActivity.instance(),
R.string.toast_choose_contact_for_edition,
Toast.LENGTH_LONG)
.show();
}
getArguments().clear();
}
mNoSipContact = view.findViewById(R.id.noSipContact);
mNoContact = view.findViewById(R.id.noContact);
mContactsList = view.findViewById(R.id.contactsList);
mAllContacts = view.findViewById(R.id.all_contacts);
mLinphoneContacts = view.findViewById(R.id.linphone_contacts);
mAllContactsSelected = view.findViewById(R.id.all_contacts_select);
mLinphoneContactsSelected = view.findViewById(R.id.linphone_contacts_select);
mContactsFetchInProgress = view.findViewById(R.id.contactsFetchInProgress);
mNewContact = view.findViewById(R.id.newContact);
mContactsRefresher = view.findViewById(R.id.contactsListRefresher);
mContactsRefresher.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
ContactsManager.getInstance().fetchContactsAsync();
}
});
mAllContacts.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnlyDisplayLinphoneContacts = false;
mAllContactsSelected.setVisibility(View.VISIBLE);
mAllContacts.setEnabled(false);
mLinphoneContacts.setEnabled(true);
mLinphoneContactsSelected.setVisibility(View.INVISIBLE);
changeContactsAdapter();
}
});
mLinphoneContacts.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
mAllContactsSelected.setVisibility(View.INVISIBLE);
mLinphoneContactsSelected.setVisibility(View.VISIBLE);
mLinphoneContacts.setEnabled(false);
mAllContacts.setEnabled(true);
mOnlyDisplayLinphoneContacts = true;
changeContactsAdapter();
}
});
mNewContact.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
mEditConsumed = true;
ContactsManager.getInstance()
.createContact(getActivity(), mDisplayName, mSipAddressToAdd);
}
});
if (getResources().getBoolean(R.bool.hide_non_linphone_contacts)) {
mAllContacts.setEnabled(false);
mLinphoneContacts.setEnabled(false);
mOnlyDisplayLinphoneContacts = true;
mAllContacts.setOnClickListener(null);
mLinphoneContacts.setOnClickListener(null);
mLinphoneContacts.setVisibility(View.INVISIBLE);
mLinphoneContactsSelected.setVisibility(View.INVISIBLE);
} else {
mAllContacts.setEnabled(mOnlyDisplayLinphoneContacts);
mLinphoneContacts.setEnabled(!mAllContacts.isEnabled());
}
mNewContact.setEnabled(LinphoneManager.getLc().getCallsNb() == 0);
if (!ContactsManager.getInstance().contactsFetchedOnce()) {
if (ContactsManager.getInstance().hasReadContactsAccess()) {
mContactsFetchInProgress.setVisibility(View.VISIBLE);
} else {
LinphoneActivity.instance().checkAndRequestReadContactsPermission();
}
} else {
if (!mOnlyDisplayLinphoneContacts
&& ContactsManager.getInstance().getContacts().size() == 0) {
mNoContact.setVisibility(View.VISIBLE);
} else if (mOnlyDisplayLinphoneContacts
&& ContactsManager.getInstance().getSIPContacts().size() == 0) {
mNoSipContact.setVisibility(View.VISIBLE);
}
}
mSearchView = view.findViewById(R.id.searchField);
mSearchView.setOnQueryTextListener(
new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
searchContacts(newText);
return true;
}
});
mLayoutManager = new LinearLayoutManager(mContext);
mContactsList.setLayoutManager(mLayoutManager);
DividerItemDecoration dividerItemDecoration =
new DividerItemDecoration(
mContactsList.getContext(), mLayoutManager.getOrientation());
dividerItemDecoration.setDrawable(
getActivity().getResources().getDrawable(R.drawable.divider));
mContactsList.addItemDecoration(dividerItemDecoration);
return view;
}
public void displayFirstContact() {
if (mContactsList != null
&& mContactsList.getAdapter() != null
&& mContactsList.getAdapter().getItemCount() > 0) {
ContactsAdapter mAdapt = (ContactsAdapter) mContactsList.getAdapter();
LinphoneActivity.instance().displayContact((LinphoneContact) mAdapt.getItem(0), false);
} else {
LinphoneActivity.instance().displayEmptyFragment();
}
}
private void searchContacts(String search) {
boolean isEditionEnabled = false;
if (search == null || search.length() == 0) {
changeContactsAdapter();
return;
}
changeContactsToggle();
List<LinphoneContact> listContact;
if (mOnlyDisplayLinphoneContacts) {
listContact = ContactsManager.getInstance().getSIPContacts(search);
} else {
listContact = ContactsManager.getInstance().getContacts(search);
}
if (mContactAdapter != null && mContactAdapter.isEditionEnabled()) {
isEditionEnabled = true;
}
mContactAdapter = new ContactsAdapter(mContext, listContact, this, mSelectionHelper);
mContactAdapter.setIsSearchMode(true);
// mContactsList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
mSelectionHelper.setAdapter(mContactAdapter);
if (isEditionEnabled) {
mSelectionHelper.enterEditionMode();
}
mContactsList.setAdapter(mContactAdapter);
}
private void changeContactsAdapter() {
changeContactsToggle();
List<LinphoneContact> listContact;
mNoSipContact.setVisibility(View.GONE);
mNoContact.setVisibility(View.GONE);
mContactsList.setVisibility(View.VISIBLE);
boolean isEditionEnabled = false;
String query = mSearchView.getQuery().toString();
if (query.equals("")) {
if (mOnlyDisplayLinphoneContacts) {
listContact = ContactsManager.getInstance().getSIPContacts();
} else {
listContact = ContactsManager.getInstance().getContacts();
}
} else {
if (mOnlyDisplayLinphoneContacts) {
listContact = ContactsManager.getInstance().getSIPContacts(query);
} else {
listContact = ContactsManager.getInstance().getContacts(query);
}
}
if (mContactAdapter != null && mContactAdapter.isEditionEnabled()) {
isEditionEnabled = true;
}
mContactAdapter = new ContactsAdapter(mContext, listContact, this, mSelectionHelper);
mSelectionHelper.setAdapter(mContactAdapter);
if (isEditionEnabled) {
mSelectionHelper.enterEditionMode();
}
mContactsList.setAdapter(mContactAdapter);
mContactAdapter.notifyDataSetChanged();
if (!mOnlyDisplayLinphoneContacts && mContactAdapter.getItemCount() == 0) {
mNoContact.setVisibility(View.VISIBLE);
} else if (mOnlyDisplayLinphoneContacts && mContactAdapter.getItemCount() == 0) {
mNoSipContact.setVisibility(View.VISIBLE);
}
}
private void changeContactsToggle() {
if (mOnlyDisplayLinphoneContacts
&& !getResources().getBoolean(R.bool.hide_non_linphone_contacts)) {
mAllContacts.setEnabled(true);
mAllContactsSelected.setVisibility(View.INVISIBLE);
mLinphoneContacts.setEnabled(false);
mLinphoneContactsSelected.setVisibility(View.VISIBLE);
} else {
mAllContacts.setEnabled(false);
mAllContactsSelected.setVisibility(View.VISIBLE);
mLinphoneContacts.setEnabled(true);
mLinphoneContactsSelected.setVisibility(View.INVISIBLE);
}
}
@Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
LinphoneContact contact = (LinphoneContact) adapter.getItemAtPosition(position);
if (mEditOnClick) {
mEditConsumed = true;
ContactsManager.getInstance().editContact(getActivity(), contact, mSipAddressToAdd);
} else {
mLastKnownPosition = mLayoutManager.findFirstVisibleItemPosition();
LinphoneActivity.instance().displayContact(contact, mOnlyDisplayChatAddress);
}
}
@Override
public void onItemClicked(int position) {
LinphoneContact contact = (LinphoneContact) mContactAdapter.getItem(position);
if (mContactAdapter.isEditionEnabled()) {
mContactAdapter.toggleSelection(position);
} else if (mEditOnClick) {
mEditConsumed = true;
ContactsManager.getInstance().editContact(getActivity(), contact, mSipAddressToAdd);
} else {
mLastKnownPosition = mLayoutManager.findFirstVisibleItemPosition();
LinphoneActivity.instance().displayContact(contact, mOnlyDisplayChatAddress);
}
}
@Override
public boolean onItemLongClicked(int position) {
if (!mContactAdapter.isEditionEnabled()) {
mSelectionHelper.enterEditionMode();
}
mContactAdapter.toggleSelection(position);
return true;
}
@Override
public void onResume() {
super.onResume();
ContactsManager.getInstance().addContactsListener(this);
if (mEditConsumed) {
mEditOnClick = false;
mSipAddressToAdd = null;
}
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.CONTACTS_LIST);
mOnlyDisplayLinphoneContacts =
ContactsManager.getInstance().isLinphoneContactsPrefered()
|| getResources().getBoolean(R.bool.hide_non_linphone_contacts);
}
changeContactsToggle();
invalidate();
}
@Override
public void onPause() {
ContactsManager.getInstance().removeContactsListener(this);
super.onPause();
}
@Override
public void onContactsUpdated() {
if (!LinphoneActivity.isInstanciated()
|| (LinphoneActivity.instance().getCurrentFragment()
!= FragmentsAvailable.CONTACTS_LIST
&& !LinphoneActivity.instance().isTablet())) return;
if (mContactAdapter != null) {
mContactAdapter.updateDataSet(
mOnlyDisplayLinphoneContacts
? ContactsManager.getInstance().getSIPContacts()
: ContactsManager.getInstance().getContacts());
mContactAdapter.notifyDataSetChanged();
if (mContactAdapter.getItemCount() > 0) {
mNoContact.setVisibility(View.GONE);
mNoSipContact.setVisibility(View.GONE);
}
}
mContactsFetchInProgress.setVisibility(View.GONE);
mContactsRefresher.setRefreshing(false);
}
private void invalidate() {
if (mSearchView != null && mSearchView.getQuery().toString().length() > 0) {
searchContacts(mSearchView.getQuery().toString());
} else {
changeContactsAdapter();
}
mContactsList.scrollToPosition(mLastKnownPosition);
}
@Override
public void onDeleteSelection(Object[] objectsToDelete) {
ArrayList<String> ids = new ArrayList<>();
int size = mContactAdapter.getSelectedItemCount();
for (int i = size - 1; i >= 0; i--) {
LinphoneContact contact = (LinphoneContact) objectsToDelete[i];
if (contact.isAndroidContact()) {
contact.deleteFriend();
ids.add(contact.getAndroidId());
} else {
contact.delete();
}
}
ContactsManager.getInstance().deleteMultipleContactsAtOnce(ids);
}
}

View file

@ -0,0 +1,517 @@
package org.linphone.contacts;
/*
ContactsManager.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.compatibility.Compatibility;
import org.linphone.core.Address;
import org.linphone.core.Core;
import org.linphone.core.Friend;
import org.linphone.core.FriendList;
import org.linphone.core.FriendListListener;
import org.linphone.core.MagicSearch;
import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
public class ContactsManager extends ContentObserver implements FriendListListener {
private static ContactsManager sInstance;
private List<LinphoneContact> mContacts, mSipContacts;
private ArrayList<ContactsUpdatedListener> mContactsUpdatedListeners;
private MagicSearch mMagicSearch;
private boolean mContactsFetchedOnce = false;
private Context mContext;
private AsyncContactsLoader mLoadContactTask;
private boolean mInitialized = false;
public static ContactsManager getInstance() {
if (sInstance == null) sInstance = new ContactsManager();
return sInstance;
}
private ContactsManager() {
super(LinphoneService.instance().handler);
mContactsUpdatedListeners = new ArrayList<>();
mContacts = new ArrayList<>();
mSipContacts = new ArrayList<>();
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null) {
mMagicSearch = LinphoneManager.getLcIfManagerNotDestroyedOrNull().createMagicSearch();
mMagicSearch.setLimitedSearch(false); // Do not limit the number of results
}
}
public void addContactsListener(ContactsUpdatedListener listener) {
mContactsUpdatedListeners.add(listener);
}
public void removeContactsListener(ContactsUpdatedListener listener) {
mContactsUpdatedListeners.remove(listener);
}
public ArrayList<ContactsUpdatedListener> getContactsListeners() {
return mContactsUpdatedListeners;
}
@Override
public void onChange(boolean selfChange) {
onChange(selfChange, null);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
fetchContactsAsync();
}
public synchronized List<LinphoneContact> getContacts() {
return mContacts;
}
synchronized void setContacts(List<LinphoneContact> c) {
mContacts = c;
}
public synchronized List<LinphoneContact> getSIPContacts() {
return mSipContacts;
}
synchronized void setSipContacts(List<LinphoneContact> c) {
mSipContacts = c;
}
public void destroy() {
if (mLoadContactTask != null) {
mLoadContactTask.cancel(true);
}
// LinphoneContact has a Friend field and Friend can have a LinphoneContact has userData
// Friend also keeps a ref on the Core, so we have to clean them
for (LinphoneContact c : mContacts) {
c.setFriend(null);
}
mContacts.clear();
for (LinphoneContact c : mSipContacts) {
c.setFriend(null);
}
mSipContacts.clear();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
for (FriendList list : lc.getFriendsLists()) {
list.removeListener(this);
}
}
sInstance = null;
}
public void fetchContactsAsync() {
if (mLoadContactTask != null) {
mLoadContactTask.cancel(true);
}
if (!hasReadContactsAccess()) {
Log.w("[Contacts Manager] Can't fetch contact without READ permission");
return;
}
mLoadContactTask = new AsyncContactsLoader(mContext);
mContactsFetchedOnce = true;
mLoadContactTask.executeOnExecutor(THREAD_POOL_EXECUTOR);
}
public void editContact(Context context, LinphoneContact contact, String valueToAdd) {
if (context.getResources().getBoolean(R.bool.use_native_contact_editor)) {
Intent intent = new Intent(Intent.ACTION_EDIT);
Uri contactUri = contact.getAndroidLookupUri();
intent.setDataAndType(contactUri, ContactsContract.Contacts.CONTENT_ITEM_TYPE);
intent.putExtra(
"finishActivityOnSaveCompleted", true); // So after save will go back here
if (valueToAdd != null) {
intent.putExtra(ContactsContract.Intents.Insert.IM_HANDLE, valueToAdd);
}
context.startActivity(intent);
} else {
LinphoneActivity.instance().editContact(contact, valueToAdd);
}
}
public void createContact(Context context, String name, String valueToAdd) {
if (context.getResources().getBoolean(R.bool.use_native_contact_editor)) {
Intent intent = new Intent(ContactsContract.Intents.Insert.ACTION);
intent.setType(ContactsContract.RawContacts.CONTENT_TYPE);
intent.putExtra(
"finishActivityOnSaveCompleted", true); // So after save will go back here
if (name != null) {
intent.putExtra(ContactsContract.Intents.Insert.NAME, name);
}
if (valueToAdd != null) {
intent.putExtra(ContactsContract.Intents.Insert.IM_HANDLE, valueToAdd);
}
context.startActivity(intent);
} else {
LinphoneActivity.instance().addContact(name, valueToAdd);
}
}
public MagicSearch getMagicSearch() {
return mMagicSearch;
}
public boolean contactsFetchedOnce() {
return mContactsFetchedOnce;
}
public List<LinphoneContact> getContacts(String search) {
search = search.toLowerCase(Locale.getDefault());
List<LinphoneContact> searchContactsBegin = new ArrayList<>();
List<LinphoneContact> searchContactsContain = new ArrayList<>();
for (LinphoneContact contact : getContacts()) {
if (contact.getFullName() != null) {
if (contact.getFullName().toLowerCase(Locale.getDefault()).startsWith(search)) {
searchContactsBegin.add(contact);
} else if (contact.getFullName()
.toLowerCase(Locale.getDefault())
.contains(search)) {
searchContactsContain.add(contact);
}
}
}
searchContactsBegin.addAll(searchContactsContain);
return searchContactsBegin;
}
public List<LinphoneContact> getSIPContacts(String search) {
search = search.toLowerCase(Locale.getDefault());
List<LinphoneContact> searchContactsBegin = new ArrayList<>();
List<LinphoneContact> searchContactsContain = new ArrayList<>();
for (LinphoneContact contact : getSIPContacts()) {
if (contact.getFullName() != null) {
if (contact.getFullName().toLowerCase(Locale.getDefault()).startsWith(search)) {
searchContactsBegin.add(contact);
} else if (contact.getFullName()
.toLowerCase(Locale.getDefault())
.contains(search)) {
searchContactsContain.add(contact);
}
}
}
searchContactsBegin.addAll(searchContactsContain);
return searchContactsBegin;
}
public void enableContactsAccess() {
LinphonePreferences.instance().disableFriendsStorage();
}
public boolean hasReadContactsAccess() {
if (mContext == null) {
return false;
}
boolean contactsR =
(PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager()
.checkPermission(
android.Manifest.permission.READ_CONTACTS,
mContext.getPackageName()));
return contactsR
&& !mContext.getResources().getBoolean(R.bool.force_use_of_linphone_friends);
}
public boolean hasWriteContactsAccess() {
if (mContext == null) {
return false;
}
return (PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager()
.checkPermission(
Manifest.permission.WRITE_CONTACTS, mContext.getPackageName()));
}
public boolean hasWriteSyncPermission() {
if (mContext == null) {
return false;
}
return (PackageManager.PERMISSION_GRANTED
== mContext.getPackageManager()
.checkPermission(
Manifest.permission.WRITE_SYNC_SETTINGS,
mContext.getPackageName()));
}
public boolean isLinphoneContactsPrefered() {
ProxyConfig lpc = LinphoneManager.getLc().getDefaultProxyConfig();
return lpc != null
&& lpc.getIdentityAddress()
.getDomain()
.equals(mContext.getString(R.string.default_domain));
}
public void initializeContactManager(Context context) {
mContext = context;
if (!mInitialized) {
if (mContext.getResources().getBoolean(R.bool.use_linphone_tag)) {
if (hasReadContactsAccess()
&& hasWriteContactsAccess()
&& hasWriteSyncPermission()) {
if (LinphoneService.isReady()) {
ContactsManager.getInstance().initializeSyncAccount();
mInitialized = true;
}
}
}
}
if (mContext != null && getContacts().size() == 0 && hasReadContactsAccess()) {
fetchContactsAsync();
}
}
private void makeContactAccountVisible() {
ContentProviderClient client =
mContext.getContentResolver()
.acquireContentProviderClient(ContactsContract.AUTHORITY_URI);
ContentValues values = new ContentValues();
values.put(
ContactsContract.Settings.ACCOUNT_NAME,
mContext.getString(R.string.sync_account_name));
values.put(
ContactsContract.Settings.ACCOUNT_TYPE,
mContext.getString(R.string.sync_account_type));
values.put(ContactsContract.Settings.UNGROUPED_VISIBLE, true);
try {
client.insert(
ContactsContract.Settings.CONTENT_URI
.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build(),
values);
Log.i("[Contacts Manager] Contacts account made visible");
} catch (RemoteException e) {
Log.e("[Contacts Manager] Couldn't make contacts account visible: " + e);
}
Compatibility.closeContentProviderClient(client);
}
private void initializeSyncAccount() {
AccountManager accountManager =
(AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
Account[] accounts =
accountManager.getAccountsByType(mContext.getString(R.string.sync_account_type));
if (accounts != null && accounts.length == 0) {
Account newAccount =
new Account(
mContext.getString(R.string.sync_account_name),
mContext.getString(R.string.sync_account_type));
try {
accountManager.addAccountExplicitly(newAccount, null, null);
Log.i("[Contacts Manager] Contact account added");
makeContactAccountVisible();
} catch (Exception e) {
Log.e("[Contacts Manager] Couldn't initialize sync account: " + e);
}
} else if (accounts != null) {
for (int i = 0; i < accounts.length; i++) {
Log.i(
"[Contacts Manager] Found account with name \""
+ accounts[i].name
+ "\" and type \""
+ accounts[i].type
+ "\"");
makeContactAccountVisible();
}
}
}
public synchronized LinphoneContact findContactFromAndroidId(String androidId) {
if (androidId == null) return null;
for (LinphoneContact c : getContacts()) {
if (c.getAndroidId() != null && c.getAndroidId().equals(androidId)) {
return c;
}
}
return null;
}
public synchronized LinphoneContact findContactFromAddress(Address address) {
if (address == null) return null;
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
Friend lf = lc.findFriend(address);
if (lf != null) {
return (LinphoneContact) lf.getUserData();
}
return findContactFromPhoneNumber(address.getUsername());
}
public synchronized LinphoneContact findContactFromPhoneNumber(String phoneNumber) {
if (phoneNumber == null) return null;
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
ProxyConfig lpc = null;
if (lc != null) {
lpc = lc.getDefaultProxyConfig();
}
if (lpc == null) return null;
String normalized = lpc.normalizePhoneNumber(phoneNumber);
if (normalized == null) normalized = phoneNumber;
Address addr = lpc.normalizeSipUri(normalized);
if (addr == null) {
return null;
}
addr.setUriParam("user", "phone");
Friend lf =
lc.findFriend(
addr); // Without this, the hashmap inside liblinphone won't find it...
if (lf != null) {
return (LinphoneContact) lf.getUserData();
}
return null;
}
public String getAddressOrNumberForAndroidContact(ContentResolver resolver, Uri contactUri) {
// Phone Numbers
String[] projection = new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER};
Cursor c = resolver.query(contactUri, projection, null, null, null);
if (c != null) {
if (c.moveToNext()) {
int numberIndex = c.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
String number = c.getString(numberIndex);
c.close();
return number;
}
}
c.close();
// SIP addresses
projection = new String[] {ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS};
c = resolver.query(contactUri, projection, null, null, null);
if (c != null) {
if (c.moveToNext()) {
int numberIndex =
c.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS);
String address = c.getString(numberIndex);
c.close();
return address;
}
}
c.close();
return null;
}
private synchronized boolean refreshSipContact(Friend lf) {
LinphoneContact contact = (LinphoneContact) lf.getUserData();
if (contact != null) {
if (!mSipContacts.contains(contact)) {
mSipContacts.add(contact);
return true;
}
}
return false;
}
public void delete(String id) {
ArrayList<String> ids = new ArrayList<>();
ids.add(id);
deleteMultipleContactsAtOnce(ids);
}
public void deleteMultipleContactsAtOnce(List<String> ids) {
String select = ContactsContract.Data.CONTACT_ID + " = ?";
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (String id : ids) {
String[] args = new String[] {id};
ops.add(
ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)
.withSelection(select, args)
.build());
}
try {
mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
Log.e("[Contacts Manager] " + e);
}
// To ensure removed contacts won't appear in the contacts list anymore
fetchContactsAsync();
}
public String getString(int resourceID) {
if (mContext == null) return null;
return mContext.getString(resourceID);
}
@Override
public void onContactCreated(FriendList list, Friend lf) {}
@Override
public void onContactDeleted(FriendList list, Friend lf) {}
@Override
public void onContactUpdated(FriendList list, Friend newFriend, Friend oldFriend) {}
@Override
public void onSyncStatusChanged(FriendList list, FriendList.SyncStatus status, String msg) {}
@Override
public void onPresenceReceived(FriendList list, Friend[] friends) {
boolean updated = false;
for (Friend lf : friends) {
boolean newContact = ContactsManager.getInstance().refreshSipContact(lf);
if (newContact) {
updated = true;
}
}
if (updated) {
Collections.sort(mSipContacts);
}
for (ContactsUpdatedListener listener : mContactsUpdatedListeners) {
listener.onContactsUpdated();
}
}
}

View file

@ -21,4 +21,4 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
public interface ContactsUpdatedListener { public interface ContactsUpdatedListener {
void onContactsUpdated(); void onContactsUpdated();
} }

View file

@ -0,0 +1,557 @@
package org.linphone.contacts;
/*
LinphoneContact.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.Address;
import org.linphone.core.Core;
import org.linphone.core.Friend;
import org.linphone.core.FriendCapability;
import org.linphone.core.FriendList;
import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel;
import org.linphone.core.SubscribePolicy;
public class LinphoneContact extends AndroidContact
implements Serializable, Comparable<LinphoneContact> {
private static final long serialVersionUID = 9015568163905205244L;
private transient Friend mFriend;
private String mFullName, mFirstName, mLastName, mOrganization;
private transient Uri mPhotoUri, mThumbnailUri;
private List<LinphoneNumberOrAddress> mAddresses;
private boolean mHasSipAddress;
public LinphoneContact() {
super();
mAddresses = new ArrayList<>();
mAndroidId = null;
mAndroidLookupKey = null;
mThumbnailUri = null;
mPhotoUri = null;
mHasSipAddress = false;
}
public static LinphoneContact createContact() {
LinphoneContact contact = new LinphoneContact();
if (ContactsManager.getInstance().hasReadContactsAccess()) {
contact.createAndroidContact();
} else {
contact.createFriend();
}
return contact;
}
@Override
public int compareTo(LinphoneContact contact) {
String fullName =
getFullName() != null ? getFullName().toUpperCase(Locale.getDefault()) : "";
String contactFullName =
contact.getFullName() != null
? contact.getFullName().toUpperCase(Locale.getDefault())
: "";
if (fullName.equals(contactFullName)) {
if (getAndroidId() != null) {
if (contact.getAndroidId() != null) {
int idComp = getAndroidId().compareTo(contact.getAndroidId());
if (idComp == 0) return 0;
List<LinphoneNumberOrAddress> noas1 = getNumbersOrAddresses();
List<LinphoneNumberOrAddress> noas2 = contact.getNumbersOrAddresses();
if (noas1.size() == noas2.size()) {
if (noas1.containsAll(noas2) && noas2.containsAll(noas1)) {
return 0;
}
return -1;
}
return Integer.compare(noas1.size(), noas2.size());
}
return -1;
}
if (contact.getAndroidId() != null) return 1;
return 0;
}
return fullName.compareTo(contactFullName);
}
@Override
public boolean equals(Object obj) {
if (obj.getClass() != LinphoneContact.class) return false;
LinphoneContact contact = (LinphoneContact) obj;
return (this.compareTo(contact) == 0);
}
/*
Name related
*/
public String getFullName() {
return mFullName;
}
public void setFullName(String name) {
mFullName = name;
}
public void setFirstNameAndLastName(String fn, String ln, boolean commitChanges) {
if (fn != null && fn.length() == 0 && ln != null && ln.length() == 0) return;
if (fn != null && fn.equals(mFirstName) && ln != null && ln.equals(mLastName)) return;
if (commitChanges) {
setName(fn, ln);
}
mFirstName = fn;
mLastName = ln;
if (mFullName == null) {
if (mFirstName != null
&& mLastName != null
&& mFirstName.length() > 0
&& mLastName.length() > 0) {
mFullName = mFirstName + " " + mLastName;
} else if (mFirstName != null && mFirstName.length() > 0) {
mFullName = mFirstName;
} else if (mLastName != null && mLastName.length() > 0) {
mFullName = mLastName;
}
}
}
public String getFirstName() {
return mFirstName;
}
public String getLastName() {
return mLastName;
}
/*
Organization related
*/
public String getOrganization() {
return mOrganization;
}
public void setOrganization(String org, boolean commitChanges) {
if ((org == null || org.isEmpty()) && (mOrganization == null || mOrganization.isEmpty()))
return;
if (org != null && org.equals(mOrganization)) return;
if (commitChanges) {
setOrganization(org, mOrganization);
}
mOrganization = org;
}
/*
Picture related
*/
public boolean hasPhoto() {
return mPhotoUri != null;
}
public Uri getPhotoUri() {
return mPhotoUri;
}
private void setPhotoUri(Uri uri) {
if (uri.equals(mPhotoUri)) return;
mPhotoUri = uri;
}
public Uri getThumbnailUri() {
return mThumbnailUri;
}
private void setThumbnailUri(Uri uri) {
if (uri.equals(mThumbnailUri)) return;
mThumbnailUri = uri;
}
/*
Number or address related
*/
public void addNumberOrAddress(LinphoneNumberOrAddress noa) {
if (noa == null) return;
if (noa.isSIPAddress()) {
mHasSipAddress = true;
mAddresses.add(noa);
} else {
boolean found = false;
// Check for duplicated phone numbers but with different formats
for (LinphoneNumberOrAddress number : mAddresses) {
if (!number.isSIPAddress()
&& noa.getNormalizedPhone().equals(number.getNormalizedPhone())) {
found = true;
break;
}
}
if (!found) {
mAddresses.add(noa);
}
}
}
public List<LinphoneNumberOrAddress> getNumbersOrAddresses() {
return mAddresses;
}
public boolean hasAddress(String address) {
for (LinphoneNumberOrAddress noa : getNumbersOrAddresses()) {
if (noa.isSIPAddress()) {
String value = noa.getValue();
if (address.startsWith(value) || value.equals("sip:" + address)) {
// Startswith is to workaround the fact that the
// address may have a ;gruu= at the end...
return true;
}
}
}
return false;
}
public boolean hasAddress() {
return mHasSipAddress;
}
public void removeNumberOrAddress(LinphoneNumberOrAddress noa) {
if (noa != null && noa.getOldValue() != null) {
removeNumberOrAddress(noa.getOldValue(), noa.isSIPAddress());
if (isFriend()) {
if (noa.isSIPAddress()) {
if (!noa.getOldValue().startsWith("sip:")) {
noa.setOldValue("sip:" + noa.getOldValue());
}
}
LinphoneNumberOrAddress toRemove = null;
for (LinphoneNumberOrAddress address : mAddresses) {
if (noa.getOldValue().equals(address.getValue())
&& noa.isSIPAddress() == address.isSIPAddress()) {
toRemove = address;
break;
}
}
if (toRemove != null) {
mAddresses.remove(toRemove);
}
}
}
}
public void addOrUpdateNumberOrAddress(LinphoneNumberOrAddress noa) {
if (noa != null && noa.getValue() != null) {
addNumberOrAddress(noa.getValue(), noa.getOldValue(), noa.isSIPAddress());
if (isFriend()) {
if (noa.isSIPAddress()) {
if (!noa.getValue().startsWith("sip:")) {
noa.setValue("sip:" + noa.getValue());
}
}
if (noa.getOldValue() != null) {
if (noa.isSIPAddress()) {
if (!noa.getOldValue().startsWith("sip:")) {
noa.setOldValue("sip:" + noa.getOldValue());
}
}
for (LinphoneNumberOrAddress address : mAddresses) {
if (noa.getOldValue().equals(address.getValue())
&& noa.isSIPAddress() == address.isSIPAddress()) {
address.setValue(noa.getValue());
break;
}
}
} else {
mAddresses.add(noa);
}
}
}
}
public void clearAddresses() {
mAddresses.clear();
}
/*
Friend related
*/
public Friend getFriend() {
return mFriend;
}
private void createOrUpdateFriend() {
boolean created = false;
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc == null) return;
if (!isFriend()) {
mFriend = lc.createFriend();
mFriend.enableSubscribes(false);
mFriend.setIncSubscribePolicy(SubscribePolicy.SPDeny);
if (isAndroidContact()) {
mFriend.setRefKey(getAndroidId());
}
mFriend.setUserData(this);
created = true;
}
if (isFriend()) {
mFriend.edit();
mFriend.setName(mFullName);
if (mFriend.getVcard() != null) {
mFriend.getVcard().setFamilyName(mLastName);
mFriend.getVcard().setGivenName(mFirstName);
}
if (mOrganization != null) {
mFriend.getVcard().setOrganization(mOrganization);
}
if (!created) {
for (Address address : mFriend.getAddresses()) {
mFriend.removeAddress(address);
}
for (String phone : mFriend.getPhoneNumbers()) {
mFriend.removePhoneNumber(phone);
}
}
for (LinphoneNumberOrAddress noa : mAddresses) {
if (noa.isSIPAddress()) {
Address addr = lc.interpretUrl(noa.getValue());
if (addr != null) {
mFriend.addAddress(addr);
}
} else {
mFriend.addPhoneNumber(noa.getValue());
}
}
mFriend.done();
}
if (created) {
lc.addFriend(mFriend);
}
if (!ContactsManager.getInstance().hasReadContactsAccess()) {
// This refresh is only needed if app has no contacts permission to refresh the list of
// Friends.
// Otherwise contacts will be refreshed due to changes in native contact and the handler
// in ContactsManager
ContactsManager.getInstance().fetchContactsAsync();
}
}
public void deleteFriend() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (mFriend != null && lc != null) {
for (FriendList list : lc.getFriendsLists()) {
list.removeFriend(mFriend);
}
}
}
public void createOrUpdateFriendFromNativeContact() {
if (isAndroidContact()) {
createOrUpdateFriend();
}
}
public boolean isFriend() {
return mFriend != null;
}
public void setFriend(Friend f) {
if (mFriend != null && (f == null || f != mFriend)) {
mFriend.setUserData(null);
}
mFriend = f;
if (mFriend != null) {
mFriend.setUserData(this);
}
}
public boolean isInFriendList() {
if (mFriend == null) return false;
for (LinphoneNumberOrAddress noa : mAddresses) {
PresenceModel pm = mFriend.getPresenceModelForUriOrTel(noa.getValue());
if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) {
return true;
}
}
return false;
}
public String getContactFromPresenceModelForUriOrTel(String uri) {
if (mFriend != null && mFriend.getPresenceModelForUriOrTel(uri) != null) {
return mFriend.getPresenceModelForUriOrTel(uri).getContact();
}
return null;
}
public PresenceBasicStatus getBasicStatusFromPresenceModelForUriOrTel(String uri) {
if (mFriend != null && mFriend.getPresenceModelForUriOrTel(uri) != null) {
return mFriend.getPresenceModelForUriOrTel(uri).getBasicStatus();
}
return PresenceBasicStatus.Closed;
}
public boolean hasPresenceModelForUriOrTelCapability(String uri, FriendCapability capability) {
if (mFriend != null && mFriend.getPresenceModelForUriOrTel(uri) != null) {
return mFriend.getPresenceModelForUriOrTel(uri).hasCapability(capability);
}
return false;
}
private void createFriend() {
LinphoneContact contact = new LinphoneContact();
Friend friend = LinphoneManager.getLc().createFriend();
// Disable subscribes for now
friend.enableSubscribes(false);
friend.setIncSubscribePolicy(SubscribePolicy.SPDeny);
contact.mFriend = friend;
friend.setUserData(contact);
}
/*
Contact related
*/
protected void setAndroidId(String id) {
super.setAndroidId(id);
setThumbnailUri(getContactThumbnailPictureUri());
setPhotoUri(getContactPictureUri());
}
public void syncValuesFromFriend() {
if (isFriend()) {
mAddresses = new ArrayList<>();
mFullName = mFriend.getName();
mLastName = mFriend.getVcard().getFamilyName();
mFirstName = mFriend.getVcard().getGivenName();
mThumbnailUri = null;
mPhotoUri = null;
mHasSipAddress = mFriend.getAddress() != null;
mOrganization = mFriend.getVcard().getOrganization();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null && lc.vcardSupported()) {
for (Address addr : mFriend.getAddresses()) {
if (addr != null) {
addNumberOrAddress(
new LinphoneNumberOrAddress(addr.asStringUriOnly(), true));
}
}
for (String tel : mFriend.getPhoneNumbers()) {
if (tel != null) {
addNumberOrAddress(new LinphoneNumberOrAddress(tel, false));
}
}
} else {
Address addr = mFriend.getAddress();
addNumberOrAddress(new LinphoneNumberOrAddress(addr.asStringUriOnly(), true));
}
}
}
public void syncValuesFromAndroidContact(Context context) {
Cursor c =
context.getContentResolver()
.query(
ContactsContract.Data.CONTENT_URI,
AsyncContactsLoader.PROJECTION,
ContactsContract.Data.IN_VISIBLE_GROUP
+ " == 1 AND "
+ ContactsContract.Data.CONTACT_ID
+ " == "
+ mAndroidId,
null,
null);
if (c != null) {
mAddresses = new ArrayList<>();
while (c.moveToNext()) {
syncValuesFromAndroidCusor(c);
}
c.close();
}
}
public void syncValuesFromAndroidCusor(Cursor c) {
String displayName =
c.getString(c.getColumnIndex(ContactsContract.Data.DISPLAY_NAME_PRIMARY));
String mime = c.getString(c.getColumnIndex(ContactsContract.Data.MIMETYPE));
String data1 = c.getString(c.getColumnIndex("data1"));
String data2 = c.getString(c.getColumnIndex("data2"));
String data3 = c.getString(c.getColumnIndex("data3"));
String data4 = c.getString(c.getColumnIndex("data4"));
String lookupKey = c.getString(c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
setAndroidLookupKey(lookupKey);
setFullName(displayName);
if (ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mime)) {
addNumberOrAddress(new LinphoneNumberOrAddress(data1, data4));
} else if (ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE.equals(mime)
|| LinphoneManager.getInstance()
.getContext()
.getString(R.string.linphone_address_mime_type)
.equals(mime)) {
addNumberOrAddress(new LinphoneNumberOrAddress(data1, true));
} else if (ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE.equals(mime)) {
setOrganization(data1, false);
} else if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(mime)) {
setFirstNameAndLastName(data2, data3, false);
}
}
public void save() {
saveChangesCommited();
syncValuesFromAndroidContact(LinphoneActivity.instance());
createOrUpdateFriend();
}
public void delete() {
deleteAndroidContact();
if (isFriend()) {
deleteFriend();
}
}
public boolean hasFriendCapability(FriendCapability capability) {
if (!isFriend()) return false;
return getFriend().hasCapability(capability);
}
}

View file

@ -24,34 +24,34 @@ import java.io.Serializable;
public class LinphoneNumberOrAddress implements Serializable, Comparable<LinphoneNumberOrAddress> { public class LinphoneNumberOrAddress implements Serializable, Comparable<LinphoneNumberOrAddress> {
private static final long serialVersionUID = -2301689469730072896L; private static final long serialVersionUID = -2301689469730072896L;
private boolean isSIPAddress; private final boolean mIsSIPAddress;
private String value, oldValueForUpdatePurpose; private String mValue, mOldValueForUpdatePurpose;
private String normalizedPhone; private final String mNormalizedPhone;
public LinphoneNumberOrAddress(String v, boolean isSIP) { public LinphoneNumberOrAddress(String v, boolean isSIP) {
value = v; mValue = v;
isSIPAddress = isSIP; mIsSIPAddress = isSIP;
oldValueForUpdatePurpose = null; mOldValueForUpdatePurpose = null;
normalizedPhone = null; mNormalizedPhone = null;
} }
public LinphoneNumberOrAddress(String v, String normalizedV) { public LinphoneNumberOrAddress(String v, String normalizedV) {
value = v; mValue = v;
normalizedPhone = normalizedV != null ? normalizedV : v; mNormalizedPhone = normalizedV != null ? normalizedV : v;
isSIPAddress = false; mIsSIPAddress = false;
oldValueForUpdatePurpose = null; mOldValueForUpdatePurpose = null;
} }
public LinphoneNumberOrAddress(String v, boolean isSip, String old) { public LinphoneNumberOrAddress(String v, boolean isSip, String old) {
this(v, isSip); this(v, isSip);
oldValueForUpdatePurpose = old; mOldValueForUpdatePurpose = old;
} }
@Override @Override
public int compareTo(LinphoneNumberOrAddress noa) { public int compareTo(LinphoneNumberOrAddress noa) {
if (value != null) { if (mValue != null) {
if (noa.isSIPAddress() && isSIPAddress()) { if (noa.isSIPAddress() && isSIPAddress()) {
return value.compareTo(noa.getValue()); return mValue.compareTo(noa.getValue());
} else if (!noa.isSIPAddress() && !isSIPAddress()) { } else if (!noa.isSIPAddress() && !isSIPAddress()) {
return getNormalizedPhone().compareTo(noa.getNormalizedPhone()); return getNormalizedPhone().compareTo(noa.getNormalizedPhone());
} }
@ -67,24 +67,26 @@ public class LinphoneNumberOrAddress implements Serializable, Comparable<Linphon
} }
public boolean isSIPAddress() { public boolean isSIPAddress() {
return isSIPAddress; return mIsSIPAddress;
} }
public String getOldValue() { public String getOldValue() {
return oldValueForUpdatePurpose; return mOldValueForUpdatePurpose;
} }
public void setOldValue(String v) { public void setOldValue(String v) {
oldValueForUpdatePurpose = v; mOldValueForUpdatePurpose = v;
} }
public String getValue() { public String getValue() {
return value; return mValue;
} }
public void setValue(String v) { public void setValue(String v) {
value = v; mValue = v;
} }
public String getNormalizedPhone() { return normalizedPhone != null ? normalizedPhone : value; } public String getNormalizedPhone() {
return mNormalizedPhone != null ? mNormalizedPhone : mValue;
}
} }

View file

@ -0,0 +1,64 @@
package org.linphone.contacts;
/*
SearchContactViewHolder.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.linphone.R;
public class SearchContactViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
public final TextView name;
public final TextView address;
public final ImageView linphoneContact;
public final ImageView isSelect;
public final RelativeLayout avatarLayout;
public final View disabled;
private final ClickListener mListener;
public SearchContactViewHolder(View view, ClickListener listener) {
super(view);
name = view.findViewById(R.id.contact_name);
address = view.findViewById(R.id.contact_address);
linphoneContact = view.findViewById(R.id.contact_linphone);
isSelect = view.findViewById(R.id.contact_is_select);
avatarLayout = view.findViewById(R.id.avatar_layout);
disabled = view.findViewById(R.id.disabled);
mListener = listener;
view.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (mListener != null) {
mListener.onItemClicked(getAdapterPosition());
}
}
public interface ClickListener {
void onItemClicked(int position);
}
}

View file

@ -0,0 +1,275 @@
package org.linphone.contacts;
/*
SearchContactsAdapter.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.Address;
import org.linphone.core.FriendCapability;
import org.linphone.core.PresenceBasicStatus;
import org.linphone.core.PresenceModel;
import org.linphone.core.ProxyConfig;
import org.linphone.core.SearchResult;
import org.linphone.views.ContactAvatar;
public class SearchContactsAdapter extends RecyclerView.Adapter<SearchContactViewHolder> {
private List<SearchResult> mContacts;
private ArrayList<ContactAddress> mContactsSelected;
private boolean mOnlySipContact = false;
private SearchContactViewHolder.ClickListener mListener;
private final boolean mIsOnlyOnePersonSelection;
private String mPreviousSearch;
private boolean mSecurityEnabled;
public SearchContactsAdapter(
SearchContactViewHolder.ClickListener clickListener,
boolean hideSelectionMark,
boolean isSecurityEnabled) {
mIsOnlyOnePersonSelection = hideSelectionMark;
mListener = clickListener;
setContactsSelectedList(null);
mPreviousSearch = null;
mSecurityEnabled = isSecurityEnabled;
mContacts = new ArrayList<>();
}
public List<SearchResult> getContacts() {
return mContacts;
}
public void setOnlySipContact(boolean enable) {
mOnlySipContact = enable;
}
public void setSecurityEnabled(boolean enable) {
mSecurityEnabled = enable;
notifyDataSetChanged();
}
@NonNull
@Override
public SearchContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.search_contact_cell, parent, false);
return new SearchContactViewHolder(v, mListener);
}
@Override
public void onBindViewHolder(@NonNull SearchContactViewHolder holder, int position) {
SearchResult searchResult = getItem(position);
LinphoneContact contact;
if (searchResult.getFriend() != null && searchResult.getFriend().getUserData() != null) {
contact = (LinphoneContact) searchResult.getFriend().getUserData();
} else {
if (searchResult.getAddress() == null) {
contact =
ContactsManager.getInstance()
.findContactFromPhoneNumber(searchResult.getPhoneNumber());
} else {
contact =
ContactsManager.getInstance()
.findContactFromAddress(searchResult.getAddress());
}
}
final String numberOrAddress =
(searchResult.getPhoneNumber() != null)
? searchResult.getPhoneNumber()
: searchResult.getAddress().asStringUriOnly();
holder.name.setVisibility(View.GONE);
if (contact != null && contact.getFullName() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(contact.getFullName());
} else if (searchResult.getAddress() != null) {
if (searchResult.getAddress().getUsername() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(searchResult.getAddress().getUsername());
} else if (searchResult.getAddress().getDisplayName() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(searchResult.getAddress().getDisplayName());
}
} else if (searchResult.getAddress() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(
(searchResult.getAddress().getDisplayName() != null)
? searchResult.getAddress().getDisplayName()
: searchResult.getAddress().getUsername());
}
holder.disabled.setVisibility(View.GONE);
if (contact != null) {
if (contact.getFullName() == null
&& contact.getFirstName() == null
&& contact.getLastName() == null) {
contact.setFullName(holder.name.getText().toString());
}
ContactAvatar.displayAvatar(
contact,
contact.hasFriendCapability(FriendCapability.LimeX3Dh),
holder.avatarLayout);
if ((!mIsOnlyOnePersonSelection
&& !searchResult.hasCapability(FriendCapability.GroupChat))
|| (mSecurityEnabled
&& !searchResult.hasCapability(FriendCapability.LimeX3Dh))) {
// Disable row, contact doesn't have the required capabilities
holder.disabled.setVisibility(View.VISIBLE);
} else if (mSecurityEnabled || !mIsOnlyOnePersonSelection) {
ProxyConfig lpc =
LinphoneManager.getLcIfManagerNotDestroyedOrNull().getDefaultProxyConfig();
if (lpc != null
&& searchResult.getAddress() != null
&& lpc.getIdentityAddress().weakEqual(searchResult.getAddress())) {
// Disable row, we can't use our own address in a group chat room
holder.disabled.setVisibility(View.VISIBLE);
}
}
} else {
ContactAvatar.displayAvatar(holder.name.getText().toString(), holder.avatarLayout);
}
holder.address.setText(numberOrAddress);
if (holder.linphoneContact != null) {
holder.linphoneContact.setVisibility(View.GONE);
if (searchResult.getFriend() != null
&& contact != null
&& contact.getBasicStatusFromPresenceModelForUriOrTel(numberOrAddress)
== PresenceBasicStatus.Open) {
holder.linphoneContact.setVisibility(View.VISIBLE);
}
}
if (holder.isSelect != null) {
if (isContactSelected(searchResult)) {
holder.isSelect.setVisibility(View.VISIBLE);
} else {
holder.isSelect.setVisibility(View.INVISIBLE);
}
if (mIsOnlyOnePersonSelection) {
holder.isSelect.setVisibility(View.GONE);
}
}
}
public long getItemId(int position) {
return position;
}
public synchronized boolean isContactSelected(SearchResult sr) {
for (ContactAddress c : mContactsSelected) {
Address addr = c.getAddress();
if (addr != null && sr.getAddress() != null) {
if (addr.weakEqual(sr.getAddress())) {
return true;
}
} else {
if (c.getPhoneNumber() != null && sr.getPhoneNumber() != null) {
if (c.getPhoneNumber().compareTo(sr.getPhoneNumber()) == 0) return true;
}
}
}
return false;
}
public synchronized ArrayList<ContactAddress> getContactsSelectedList() {
return mContactsSelected;
}
public synchronized void setContactsSelectedList(ArrayList<ContactAddress> contactsList) {
if (contactsList == null) {
mContactsSelected = new ArrayList<>();
} else {
mContactsSelected = contactsList;
}
}
public synchronized boolean toggleContactSelection(ContactAddress ca) {
if (mContactsSelected.contains(ca)) {
mContactsSelected.remove(ca);
return false;
} else {
mContactsSelected.add(ca);
return true;
}
}
private SearchResult getItem(int position) {
return mContacts.get(position);
}
@Override
public int getItemCount() {
return mContacts.size();
}
public void searchContacts(String search) {
List<SearchResult> result = new ArrayList<>();
if (mPreviousSearch != null) {
if (mPreviousSearch.length() > search.length()) {
ContactsManager.getInstance().getMagicSearch().resetSearchCache();
}
}
mPreviousSearch = search;
String domain = "";
ProxyConfig prx = LinphoneManager.getLc().getDefaultProxyConfig();
if (prx != null) domain = prx.getDomain();
SearchResult[] searchResults =
ContactsManager.getInstance()
.getMagicSearch()
.getContactListFromFilter(search, mOnlySipContact ? domain : "");
for (SearchResult sr : searchResults) {
if (LinphoneActivity.instance()
.getResources()
.getBoolean(R.bool.hide_sip_contacts_without_presence)) {
if (sr.getFriend() != null) {
PresenceModel pm =
sr.getFriend()
.getPresenceModelForUriOrTel(sr.getAddress().asStringUriOnly());
if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) {
result.add(sr);
} else {
pm = sr.getFriend().getPresenceModelForUriOrTel(sr.getPhoneNumber());
if (pm != null && pm.getBasicStatus().equals(PresenceBasicStatus.Open)) {
result.add(sr);
}
}
}
} else {
result.add(sr);
}
}
mContacts = result;
notifyDataSetChanged();
}
}

View file

@ -2,7 +2,7 @@ package org.linphone.firebase;
/* /*
FirebaseMessaging.java FirebaseMessaging.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France Copyright (C) 2017-2019 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License
@ -19,20 +19,29 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import android.content.Intent;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.LinphoneUtils;
import org.linphone.mediastream.Log;
import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.ACTION_MAIN;
import android.content.Intent;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.linphone.LinphoneService;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
public class FirebaseMessaging extends FirebaseMessagingService { public class FirebaseMessaging extends FirebaseMessagingService {
public FirebaseMessaging() { public FirebaseMessaging() {}
@Override
public void onNewToken(final String token) {
android.util.Log.i("FirebaseIdService", "[Push Notification] Refreshed token: " + token);
LinphoneUtils.dispatchOnUIThread(
new Runnable() {
@Override
public void run() {
LinphonePreferences.instance().setPushNotificationRegistrationID(token);
}
});
} }
@Override @Override
@ -45,17 +54,6 @@ public class FirebaseMessaging extends FirebaseMessagingService {
intent.setClass(this, LinphoneService.class); intent.setClass(this, LinphoneService.class);
intent.putExtra("PushNotification", true); intent.putExtra("PushNotification", true);
startService(intent); startService(intent);
} else if (LinphoneManager.isInstanciated() && LinphoneManager.getLc().getCallsNb() == 0) {
LinphoneUtils.dispatchOnUIThread(new Runnable() {
@Override
public void run() {
Log.i("[Push Notification] Push notification received with LinphoneManager still alive");
if (LinphoneManager.isInstanciated() && LinphoneManager.getLc().getCallsNb() == 0) {
LinphoneManager.getLc().setNetworkReachable(false);
LinphoneManager.getLc().setNetworkReachable(true);
}
}
});
} }
} }
} }

View file

@ -0,0 +1,73 @@
package org.linphone.firebase;
/*
FirebasePushHelper.java
Copyright (C) 2019 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.Context;
import androidx.annotation.NonNull;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import org.linphone.R;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.PushNotificationUtils;
public class FirebasePushHelper implements PushNotificationUtils.PushHelperInterface {
public FirebasePushHelper() {}
@Override
public void init(Context context) {
Log.i(
"[Push Notification] firebase push sender id "
+ context.getString(R.string.gcm_defaultSenderId));
try {
FirebaseInstanceId.getInstance()
.getInstanceId()
.addOnCompleteListener(
new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
Log.e(
"[Push Notification] firebase getInstanceId failed: "
+ task.getException());
return;
}
String token = task.getResult().getToken();
Log.i("[Push Notification] firebase token is: " + token);
LinphonePreferences.instance()
.setPushNotificationRegistrationID(token);
}
});
} catch (Exception e) {
Log.e("[Push Notification] firebase not available.");
}
}
@Override
public boolean isAvailable(Context context) {
GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
int resultCode = googleApiAvailability.isGooglePlayServicesAvailable(context);
return resultCode == ConnectionResult.SUCCESS;
}
}

View file

@ -0,0 +1,188 @@
package org.linphone.fragments;
/*
AboutFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import org.linphone.BuildConfig;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.core.Core;
import org.linphone.core.Core.LogCollectionUploadState;
import org.linphone.core.CoreListenerStub;
import org.linphone.settings.LinphonePreferences;
public class AboutFragment extends Fragment implements OnClickListener {
private View mSendLogButton = null;
private View mResetLogButton = null;
private CoreListenerStub mListener;
private ProgressDialog mProgress;
private boolean mUploadInProgress;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.about, container, false);
TextView aboutVersion = view.findViewById(R.id.about_android_version);
TextView aboutLiblinphoneVersion = view.findViewById(R.id.about_liblinphone_sdk_version);
aboutLiblinphoneVersion.setText(
String.format(
getString(R.string.about_liblinphone_sdk_version),
getString(R.string.linphone_sdk_version)
+ " ("
+ getString(R.string.linphone_sdk_branch)
+ ")"));
// We can't access a library's BuildConfig, so we have to set it as a resource
aboutVersion.setText(
String.format(
getString(R.string.about_version),
BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")"));
TextView privacyPolicy = view.findViewById(R.id.privacy_policy_link);
privacyPolicy.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
Intent browserIntent =
new Intent(
Intent.ACTION_VIEW,
Uri.parse(getString(R.string.about_privacy_policy_link)));
startActivity(browserIntent);
}
});
TextView license = view.findViewById(R.id.about_text);
license.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
Intent browserIntent =
new Intent(
Intent.ACTION_VIEW,
Uri.parse(getString(R.string.about_license_link)));
startActivity(browserIntent);
}
});
mSendLogButton = view.findViewById(R.id.send_log);
mSendLogButton.setOnClickListener(this);
mSendLogButton.setVisibility(
LinphonePreferences.instance().isDebugEnabled() ? View.VISIBLE : View.GONE);
mResetLogButton = view.findViewById(R.id.reset_log);
mResetLogButton.setOnClickListener(this);
mResetLogButton.setVisibility(
LinphonePreferences.instance().isDebugEnabled() ? View.VISIBLE : View.GONE);
mListener =
new CoreListenerStub() {
@Override
public void onLogCollectionUploadProgressIndication(
Core lc, int offset, int total) {}
@Override
public void onLogCollectionUploadStateChanged(
Core lc, LogCollectionUploadState state, String info) {
if (state == LogCollectionUploadState.InProgress) {
displayUploadLogsInProgress();
} else if (state == LogCollectionUploadState.Delivered
|| state == LogCollectionUploadState.NotDelivered) {
mUploadInProgress = false;
if (mProgress != null) mProgress.dismiss();
}
}
};
return view;
}
private void displayUploadLogsInProgress() {
if (mUploadInProgress) {
return;
}
mUploadInProgress = true;
mProgress = ProgressDialog.show(LinphoneActivity.instance(), null, null);
Drawable d =
new ColorDrawable(ContextCompat.getColor(getActivity(), R.color.light_grey_color));
d.setAlpha(200);
mProgress
.getWindow()
.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
mProgress.getWindow().setBackgroundDrawable(d);
mProgress.setContentView(R.layout.wait_layout);
mProgress.show();
}
@Override
public void onPause() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
super.onPause();
}
@Override
public void onResume() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
}
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.ABOUT);
}
super.onResume();
}
@Override
public void onClick(View v) {
if (LinphoneActivity.isInstanciated()) {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (v == mSendLogButton) {
if (lc != null) {
lc.uploadLogCollection();
}
} else if (v == mResetLogButton) {
if (lc != null) {
lc.resetLogCollection();
}
}
}
}
}

View file

@ -30,33 +30,36 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.LinphoneService; import org.linphone.LinphoneService;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.core.Core; import org.linphone.core.Core;
import org.linphone.mediastream.Log; import org.linphone.core.tools.Log;
import org.linphone.ui.AddressAware; import org.linphone.views.AddressAware;
import org.linphone.ui.AddressText; import org.linphone.views.AddressText;
import org.linphone.ui.CallButton; import org.linphone.views.CallButton;
import org.linphone.ui.EraseButton; import org.linphone.views.EraseButton;
public class DialerFragment extends Fragment { public class DialerFragment extends Fragment {
private static DialerFragment instance; private static DialerFragment sInstance;
private static boolean isCallTransferOngoing = false; private static boolean sIsCallTransferOngoing = false;
private AddressAware numpad; private AddressAware mNumpad;
private AddressText mAddress; private AddressText mAddress;
private CallButton mCall; private CallButton mCall;
private ImageView mAddContact; private ImageView mAddContact;
private OnClickListener addContactListener, cancelListener, transferListener; private OnClickListener mAddContactListener, mCancelListener, mTransferListener;
private boolean shouldEmptyAddressField = true;
/** @return null if not ready yet */
public static DialerFragment instance() {
return sInstance;
}
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialer, container, false); View view = inflater.inflate(R.layout.dialer, container, false);
mAddress = view.findViewById(R.id.address); mAddress = view.findViewById(R.id.address);
@ -67,141 +70,143 @@ public class DialerFragment extends Fragment {
mCall = view.findViewById(R.id.call); mCall = view.findViewById(R.id.call);
mCall.setAddressWidget(mAddress); mCall.setAddressWidget(mAddress);
if (LinphoneActivity.isInstanciated() && LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null && LinphoneManager.getLcIfManagerNotDestroyedOrNull().getCallsNb() > 0) { if (LinphoneActivity.isInstanciated()
if (isCallTransferOngoing) { && LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null
&& LinphoneManager.getLcIfManagerNotDestroyedOrNull().getCallsNb() > 0) {
if (sIsCallTransferOngoing) {
mCall.setImageResource(R.drawable.call_transfer); mCall.setImageResource(R.drawable.call_transfer);
} else { } else {
mCall.setImageResource(R.drawable.call_add); mCall.setImageResource(R.drawable.call_add);
} }
} else { } else {
if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null && LinphoneManager.getLcIfManagerNotDestroyedOrNull().getVideoActivationPolicy().getAutomaticallyInitiate()) { if (LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null
&& LinphoneManager.getLcIfManagerNotDestroyedOrNull()
.getVideoActivationPolicy()
.getAutomaticallyInitiate()) {
mCall.setImageResource(R.drawable.call_video_start); mCall.setImageResource(R.drawable.call_video_start);
} else { } else {
mCall.setImageResource(R.drawable.call_audio_start); mCall.setImageResource(R.drawable.call_audio_start);
} }
} }
numpad = view.findViewById(R.id.numpad); mNumpad = view.findViewById(R.id.numpad);
if (numpad != null) { if (mNumpad != null) {
numpad.setAddressWidget(mAddress); mNumpad.setAddressWidget(mAddress);
} }
mAddContact = view.findViewById(R.id.add_contact); mAddContact = view.findViewById(R.id.add_contact);
mAddContact.setEnabled(!(LinphoneActivity.isInstanciated() && LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null && LinphoneManager.getLc().getCallsNb() > 0)); mAddContact.setEnabled(
!(LinphoneActivity.isInstanciated()
&& LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null
&& LinphoneManager.getLc().getCallsNb() > 0));
addContactListener = new OnClickListener() { mAddContactListener =
@Override new OnClickListener() {
public void onClick(View v) { @Override
LinphoneActivity.instance().displayContactsForEdition(mAddress.getText().toString()); public void onClick(View v) {
} LinphoneActivity.instance()
}; .displayContactsForEdition(mAddress.getText().toString());
cancelListener = new OnClickListener() { }
@Override };
public void onClick(View v) { mCancelListener =
LinphoneActivity.instance().resetClassicMenuLayoutAndGoBackToCallIfStillRunning(); new OnClickListener() {
} @Override
}; public void onClick(View v) {
transferListener = new OnClickListener() { LinphoneActivity.instance()
@Override .resetClassicMenuLayoutAndGoBackToCallIfStillRunning();
public void onClick(View v) { }
Core lc = LinphoneManager.getLc(); };
if (lc.getCurrentCall() == null) { mTransferListener =
return; new OnClickListener() {
} @Override
lc.transferCall(lc.getCurrentCall(), mAddress.getText().toString()); public void onClick(View v) {
isCallTransferOngoing = false; Core lc = LinphoneManager.getLc();
LinphoneActivity.instance().resetClassicMenuLayoutAndGoBackToCallIfStillRunning(); if (lc.getCurrentCall() == null) {
} return;
}; }
lc.transferCall(lc.getCurrentCall(), mAddress.getText().toString());
sIsCallTransferOngoing = false;
LinphoneActivity.instance()
.resetClassicMenuLayoutAndGoBackToCallIfStillRunning();
}
};
resetLayout(isCallTransferOngoing); resetLayout();
if (getArguments() != null) { if (getArguments() != null) {
shouldEmptyAddressField = false;
String number = getArguments().getString("SipUri"); String number = getArguments().getString("SipUri");
String displayName = getArguments().getString("DisplayName"); String displayName = getArguments().getString("DisplayName");
String photo = getArguments().getString("PhotoUri");
mAddress.setText(number); mAddress.setText(number);
if (displayName != null) { if (displayName != null) {
mAddress.setDisplayedName(displayName); mAddress.setDisplayedName(displayName);
} }
} }
instance = this; sInstance = this;
return view; return view;
} }
/**
* @return null if not ready yet
*/
public static DialerFragment instance() {
return instance;
}
@Override @Override
public void onPause() { public void onPause() {
instance = null; sInstance = null;
super.onPause(); super.onPause();
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
instance = this; sInstance = this;
if (LinphoneActivity.isInstanciated()) { if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.DIALER); LinphoneActivity.instance().selectMenu(FragmentsAvailable.DIALER);
LinphoneActivity.instance().updateDialerFragment(this); LinphoneActivity.instance().updateDialerFragment();
LinphoneActivity.instance().showStatusBar(); LinphoneActivity.instance().showStatusBar();
LinphoneActivity.instance().hideTabBar(false);
} }
boolean isOrientationLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; boolean isOrientationLandscape =
getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
if (isOrientationLandscape && !getResources().getBoolean(R.bool.isTablet)) { if (isOrientationLandscape && !getResources().getBoolean(R.bool.isTablet)) {
((LinearLayout) numpad).setVisibility(View.GONE); ((LinearLayout) mNumpad).setVisibility(View.GONE);
} else { } else {
((LinearLayout) numpad).setVisibility(View.VISIBLE); ((LinearLayout) mNumpad).setVisibility(View.VISIBLE);
} }
if (shouldEmptyAddressField) { resetLayout();
mAddress.setText("");
} else {
shouldEmptyAddressField = true;
}
resetLayout(isCallTransferOngoing);
String addressWaitingToBeCalled = LinphoneActivity.instance().mAddressWaitingToBeCalled; String addressWaitingToBeCalled = LinphoneActivity.instance().addressWaitingToBeCalled;
if (addressWaitingToBeCalled != null) { if (addressWaitingToBeCalled != null) {
mAddress.setText(addressWaitingToBeCalled); mAddress.setText(addressWaitingToBeCalled);
if (getResources().getBoolean(R.bool.automatically_start_intercepted_outgoing_gsm_call)) { if (getResources()
.getBoolean(R.bool.automatically_start_intercepted_outgoing_gsm_call)) {
newOutgoingCall(addressWaitingToBeCalled); newOutgoingCall(addressWaitingToBeCalled);
} }
LinphoneActivity.instance().mAddressWaitingToBeCalled = null; LinphoneActivity.instance().addressWaitingToBeCalled = null;
} }
} }
public void resetLayout(boolean callTransfer) { public void resetLayout() {
if (!LinphoneActivity.isInstanciated()) { if (!LinphoneActivity.isInstanciated()) {
return; return;
} }
isCallTransferOngoing = LinphoneActivity.instance().isCallTransfer(); sIsCallTransferOngoing = LinphoneActivity.instance().isCallTransfer();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc == null) { if (lc == null) {
return; return;
} }
if (lc.getCallsNb() > 0) { if (lc.getCallsNb() > 0) {
if (isCallTransferOngoing) { if (sIsCallTransferOngoing) {
mCall.setImageResource(R.drawable.call_transfer); mCall.setImageResource(R.drawable.call_transfer);
mCall.setExternalClickListener(transferListener); mCall.setExternalClickListener(mTransferListener);
} else { } else {
mCall.setImageResource(R.drawable.call_add); mCall.setImageResource(R.drawable.call_add);
mCall.resetClickListener(); mCall.resetClickListener();
} }
mAddContact.setEnabled(true); mAddContact.setEnabled(true);
mAddContact.setImageResource(R.drawable.call_alt_back); mAddContact.setImageResource(R.drawable.call_back);
mAddContact.setOnClickListener(cancelListener); mAddContact.setOnClickListener(mCancelListener);
} else { } else {
if (LinphoneManager.getLc().getVideoActivationPolicy().getAutomaticallyInitiate()) { if (LinphoneManager.getLc().getVideoActivationPolicy().getAutomaticallyInitiate()) {
mCall.setImageResource(R.drawable.call_video_start); mCall.setImageResource(R.drawable.call_video_start);
@ -209,18 +214,20 @@ public class DialerFragment extends Fragment {
mCall.setImageResource(R.drawable.call_audio_start); mCall.setImageResource(R.drawable.call_audio_start);
} }
mAddContact.setEnabled(false); mAddContact.setEnabled(false);
mAddContact.setImageResource(R.drawable.contact_add_button); mAddContact.setImageResource(R.drawable.contact_add);
mAddContact.setOnClickListener(addContactListener); mAddContact.setOnClickListener(mAddContactListener);
enableDisableAddContact(); enableDisableAddContact();
} }
} }
public void enableDisableAddContact() { public void enableDisableAddContact() {
mAddContact.setEnabled(LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null && LinphoneManager.getLc().getCallsNb() > 0 || !mAddress.getText().toString().equals("")); mAddContact.setEnabled(
LinphoneManager.getLcIfManagerNotDestroyedOrNull() != null
&& LinphoneManager.getLc().getCallsNb() > 0
|| !mAddress.getText().toString().equals(""));
} }
public void displayTextInAddressBar(String numberOrSipAddress) { public void displayTextInAddressBar(String numberOrSipAddress) {
shouldEmptyAddressField = false;
mAddress.setText(numberOrSipAddress); mAddress.setText(numberOrSipAddress);
} }
@ -238,7 +245,11 @@ public class DialerFragment extends Fragment {
mAddress.setText(intent.getData().getSchemeSpecificPart()); mAddress.setText(intent.getData().getSchemeSpecificPart());
} else { } else {
Uri contactUri = intent.getData(); Uri contactUri = intent.getData();
String address = ContactsManager.getAddressOrNumberForAndroidContact(LinphoneService.instance().getContentResolver(), contactUri); String address =
ContactsManager.getInstance()
.getAddressOrNumberForAndroidContact(
LinphoneService.instance().getContentResolver(),
contactUri);
if (address != null) { if (address != null) {
mAddress.setText(address); mAddress.setText(address);
} else { } else {

View file

@ -24,17 +24,14 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.linphone.R; import org.linphone.R;
public class EmptyFragment extends Fragment { public class EmptyFragment extends Fragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.empty_fragment, container, false); return inflater.inflate(R.layout.empty_fragment, container, false);
return view;
} }
} }

View file

@ -31,12 +31,15 @@ public enum FragmentsAvailable {
ABOUT, ABOUT,
ACCOUNT_SETTINGS, ACCOUNT_SETTINGS,
SETTINGS, SETTINGS,
SETTINGS_SUBLEVEL,
CHAT_LIST, CHAT_LIST,
CHAT, CHAT,
CREATE_CHAT, CREATE_CHAT,
INFO_GROUP_CHAT, INFO_GROUP_CHAT,
GROUP_CHAT, GROUP_CHAT,
MESSAGE_IMDN; MESSAGE_IMDN,
CONTACT_DEVICES,
RECORDING_LIST;
public boolean shouldAddItselfToTheRightOf(FragmentsAvailable fragment) { public boolean shouldAddItselfToTheRightOf(FragmentsAvailable fragment) {
switch (this) { switch (this) {
@ -44,20 +47,33 @@ public enum FragmentsAvailable {
return fragment == HISTORY_LIST || fragment == HISTORY_DETAIL; return fragment == HISTORY_LIST || fragment == HISTORY_DETAIL;
case CONTACT_DETAIL: case CONTACT_DETAIL:
return fragment == CONTACTS_LIST || fragment == CONTACT_EDITOR || fragment == CONTACT_DETAIL; return fragment == CONTACTS_LIST
|| fragment == CONTACT_EDITOR
|| fragment == CONTACT_DETAIL;
case CONTACT_EDITOR: case CONTACT_EDITOR:
return fragment == CONTACTS_LIST || fragment == CONTACT_DETAIL || fragment == CONTACT_EDITOR; return fragment == CONTACTS_LIST
|| fragment == CONTACT_DETAIL
|| fragment == CONTACT_EDITOR;
case CHAT: case CHAT:
return fragment == CHAT_LIST || fragment == CHAT; return fragment == CHAT_LIST || fragment == CHAT;
case GROUP_CHAT: case GROUP_CHAT:
return fragment == CHAT_LIST || fragment == GROUP_CHAT; return fragment == CHAT_LIST
|| fragment == GROUP_CHAT
|| fragment == INFO_GROUP_CHAT
|| fragment == CREATE_CHAT;
case MESSAGE_IMDN: case MESSAGE_IMDN:
return fragment == GROUP_CHAT || fragment == MESSAGE_IMDN; return fragment == GROUP_CHAT || fragment == MESSAGE_IMDN;
case SETTINGS_SUBLEVEL:
return fragment == SETTINGS || fragment == SETTINGS_SUBLEVEL;
case CONTACT_DEVICES:
return fragment == GROUP_CHAT || fragment == CONTACT_DEVICES;
default: default:
return false; return false;
} }

View file

@ -0,0 +1,509 @@
package org.linphone.fragments;
/*
StatusFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Activity;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.assistant.AssistantActivity;
import org.linphone.call.CallActivity;
import org.linphone.call.CallIncomingActivity;
import org.linphone.call.CallOutgoingActivity;
import org.linphone.core.Call;
import org.linphone.core.Content;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Event;
import org.linphone.core.MediaEncryption;
import org.linphone.core.ProxyConfig;
import org.linphone.core.RegistrationState;
import org.linphone.core.tools.Log;
import org.linphone.settings.LinphonePreferences;
public class StatusFragment extends Fragment {
private final Handler mRefreshHandler = new Handler();
private TextView mStatusText, mVoicemailCount;
private ImageView mStatusLed, mCallQuality, mEncryption, mMenu, mVoicemail;
private Runnable mCallQualityUpdater;
private boolean mIsInCall, mIsAttached = false;
private CoreListenerStub mListener;
private Dialog mZrtpDialog = null;
private int mDisplayedQuality = -1;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.status, container, false);
mStatusText = view.findViewById(R.id.status_text);
mStatusLed = view.findViewById(R.id.status_led);
mCallQuality = view.findViewById(R.id.call_quality);
mEncryption = view.findViewById(R.id.encryption);
mMenu = view.findViewById(R.id.side_menu_button);
mVoicemail = view.findViewById(R.id.voicemail);
mVoicemailCount = view.findViewById(R.id.voicemail_count);
// We create it once to not delay the first display
populateSliderContent();
mListener =
new CoreListenerStub() {
@Override
public void onRegistrationStateChanged(
final Core lc,
final ProxyConfig proxy,
final RegistrationState state,
String smessage) {
if (!mIsAttached || !LinphoneService.isReady()) {
return;
}
if (lc.getProxyConfigList() == null) {
mStatusLed.setImageResource(R.drawable.led_disconnected);
mStatusText.setText(getString(R.string.no_account));
} else {
mStatusLed.setVisibility(View.VISIBLE);
}
if (lc.getDefaultProxyConfig() != null
&& lc.getDefaultProxyConfig().equals(proxy)) {
mStatusLed.setImageResource(getStatusIconResource(state));
mStatusText.setText(getStatusIconText(state));
} else if (lc.getDefaultProxyConfig() == null) {
mStatusLed.setImageResource(getStatusIconResource(state));
mStatusText.setText(getStatusIconText(state));
}
try {
mStatusText.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
Core core =
LinphoneManager
.getLcIfManagerNotDestroyedOrNull();
if (core != null) {
core.refreshRegisters();
}
}
});
} catch (IllegalStateException ise) {
Log.e(ise);
}
}
@Override
public void onNotifyReceived(
Core lc, Event ev, String eventName, Content content) {
if (!content.getType().equals("application")) return;
if (!content.getSubtype().equals("simple-message-summary")) return;
if (content.getSize() == 0) return;
int unreadCount = 0;
String data = content.getStringBuffer().toLowerCase();
String[] voiceMail = data.split("voice-message: ");
if (voiceMail.length >= 2) {
final String[] intToParse = voiceMail[1].split("/", 0);
try {
unreadCount = Integer.parseInt(intToParse[0]);
} catch (NumberFormatException nfe) {
}
if (unreadCount > 0) {
mVoicemailCount.setText(String.valueOf(unreadCount));
mVoicemail.setVisibility(View.VISIBLE);
mVoicemailCount.setVisibility(View.VISIBLE);
} else {
mVoicemail.setVisibility(View.GONE);
mVoicemailCount.setVisibility(View.GONE);
}
}
}
};
mIsAttached = true;
Activity activity = getActivity();
if (activity instanceof LinphoneActivity) {
((LinphoneActivity) activity).updateStatusFragment(this);
} else if (activity instanceof CallActivity) {
((CallActivity) activity).updateStatusFragment(this);
} else if (activity instanceof AssistantActivity) {
((AssistantActivity) activity).updateStatusFragment(this);
}
mIsInCall =
activity instanceof CallActivity
|| activity instanceof CallIncomingActivity
|| activity instanceof CallOutgoingActivity;
return view;
}
public void setCoreListener() {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
ProxyConfig lpc = lc.getDefaultProxyConfig();
if (lpc != null) {
mListener.onRegistrationStateChanged(lc, lpc, lpc.getState(), null);
}
}
}
@Override
public void onDetach() {
super.onDetach();
mIsAttached = false;
}
// NORMAL STATUS BAR
private void populateSliderContent() {
if (LinphoneManager.isInstanciated() && LinphoneManager.getLc() != null) {
mVoicemailCount.setVisibility(View.GONE);
if (mIsInCall && mIsAttached) {
// Call call = LinphoneManager.getLc().getCurrentCall();
// initCallStatsRefresher(call, callStats);
} else if (!mIsInCall) {
mVoicemailCount.setVisibility(View.VISIBLE);
}
if (LinphoneManager.getLc().getProxyConfigList().length == 0) {
mStatusLed.setImageResource(R.drawable.led_disconnected);
mStatusText.setText(getString(R.string.no_account));
}
}
}
public void resetAccountStatus() {
if (LinphoneManager.getLc().getProxyConfigList().length == 0) {
mStatusLed.setImageResource(R.drawable.led_disconnected);
mStatusText.setText(getString(R.string.no_account));
}
}
public void enableSideMenu(boolean enabled) {
mMenu.setEnabled(enabled);
}
private int getStatusIconResource(RegistrationState state) {
try {
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
boolean defaultAccountConnected =
(lc != null
&& lc.getDefaultProxyConfig() != null
&& lc.getDefaultProxyConfig().getState() == RegistrationState.Ok);
if (state == RegistrationState.Ok && defaultAccountConnected) {
return R.drawable.led_connected;
} else if (state == RegistrationState.Progress) {
return R.drawable.led_inprogress;
} else if (state == RegistrationState.Failed) {
return R.drawable.led_error;
} else {
return R.drawable.led_disconnected;
}
} catch (Exception e) {
Log.e(e);
}
return R.drawable.led_disconnected;
}
private String getStatusIconText(RegistrationState state) {
Context context = getActivity();
if (!mIsAttached && LinphoneActivity.isInstanciated())
context = LinphoneActivity.instance();
else if (!mIsAttached && LinphoneService.isReady()) context = LinphoneService.instance();
try {
if (state == RegistrationState.Ok
&& LinphoneManager.getLcIfManagerNotDestroyedOrNull()
.getDefaultProxyConfig()
.getState()
== RegistrationState.Ok) {
return context.getString(R.string.status_connected);
} else if (state == RegistrationState.Progress) {
return context.getString(R.string.status_in_progress);
} else if (state == RegistrationState.Failed) {
return context.getString(R.string.status_error);
} else {
return context.getString(R.string.status_not_connected);
}
} catch (Exception e) {
Log.e(e);
}
return context.getString(R.string.status_not_connected);
}
// INCALL STATUS BAR
private void startCallQuality() {
mCallQuality.setVisibility(View.VISIBLE);
mRefreshHandler.postDelayed(
mCallQualityUpdater =
new Runnable() {
final Call mCurrentCall = LinphoneManager.getLc().getCurrentCall();
public void run() {
if (mCurrentCall == null) {
mCallQualityUpdater = null;
return;
}
float newQuality = mCurrentCall.getCurrentQuality();
updateQualityOfSignalIcon(newQuality);
if (mIsInCall) {
mRefreshHandler.postDelayed(this, 1000);
} else mCallQualityUpdater = null;
}
},
1000);
}
private void updateQualityOfSignalIcon(float quality) {
int iQuality = (int) quality;
if (iQuality == mDisplayedQuality) return;
if (quality >= 4) // Good Quality
{
mCallQuality.setImageResource(R.drawable.call_quality_indicator_4);
} else if (quality >= 3) // Average quality
{
mCallQuality.setImageResource(R.drawable.call_quality_indicator_3);
} else if (quality >= 2) // Low quality
{
mCallQuality.setImageResource(R.drawable.call_quality_indicator_2);
} else if (quality >= 1) // Very low quality
{
mCallQuality.setImageResource(R.drawable.call_quality_indicator_1);
} else // Worst quality
{
mCallQuality.setImageResource(R.drawable.call_quality_indicator_0);
}
mDisplayedQuality = iQuality;
}
@Override
public void onResume() {
super.onResume();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.addListener(mListener);
ProxyConfig lpc = lc.getDefaultProxyConfig();
if (lpc != null) {
mListener.onRegistrationStateChanged(lc, lpc, lpc.getState(), null);
}
Call call = lc.getCurrentCall();
if (mIsInCall && (call != null || lc.getConferenceSize() > 1 || lc.getCallsNb() > 0)) {
if (call != null) {
startCallQuality();
refreshStatusItems(call);
}
mMenu.setVisibility(View.INVISIBLE);
mCallQuality.setVisibility(View.VISIBLE);
// We are obviously connected
if (lc.getDefaultProxyConfig() == null) {
mStatusLed.setImageResource(R.drawable.led_disconnected);
mStatusText.setText(getString(R.string.no_account));
} else {
mStatusLed.setImageResource(
getStatusIconResource(lc.getDefaultProxyConfig().getState()));
mStatusText.setText(getStatusIconText(lc.getDefaultProxyConfig().getState()));
}
}
} else {
mStatusText.setVisibility(View.VISIBLE);
mEncryption.setVisibility(View.GONE);
}
}
@Override
public void onPause() {
super.onPause();
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc != null) {
lc.removeListener(mListener);
}
if (mCallQualityUpdater != null) {
mRefreshHandler.removeCallbacks(mCallQualityUpdater);
mCallQualityUpdater = null;
}
}
public void refreshStatusItems(final Call call) {
if (call != null) {
mVoicemailCount.setVisibility(View.GONE);
MediaEncryption mediaEncryption = call.getCurrentParams().getMediaEncryption();
mEncryption.setVisibility(View.VISIBLE);
if (mediaEncryption == MediaEncryption.SRTP
|| (mediaEncryption == MediaEncryption.ZRTP
&& call.getAuthenticationTokenVerified())
|| mediaEncryption == MediaEncryption.DTLS) {
mEncryption.setImageResource(R.drawable.security_ok);
} else if (mediaEncryption == MediaEncryption.ZRTP
&& !call.getAuthenticationTokenVerified()) {
mEncryption.setImageResource(R.drawable.security_pending);
} else {
mEncryption.setImageResource(R.drawable.security_ko);
// Do not show the unsecure icon if user doesn't want to do call mEncryption
if (LinphonePreferences.instance().getMediaEncryption() == MediaEncryption.None) {
mEncryption.setVisibility(View.GONE);
}
}
if (mediaEncryption == MediaEncryption.ZRTP) {
mEncryption.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
showZRTPDialog(call);
}
});
} else {
mEncryption.setOnClickListener(null);
}
}
}
public void showZRTPDialog(final Call call) {
if (getActivity() == null) {
Log.w("Can't display ZRTP popup, no Activity");
return;
}
if (mZrtpDialog == null || !mZrtpDialog.isShowing()) {
String token = call.getAuthenticationToken();
if (token == null) {
Log.w("Can't display ZRTP popup, no token !");
return;
}
if (token.length() < 4) {
Log.w("Can't display ZRTP popup, token is invalid (" + token + ")");
return;
}
mZrtpDialog = new Dialog(getActivity());
mZrtpDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
mZrtpDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mZrtpDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
mZrtpDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Drawable d =
new ColorDrawable(
ContextCompat.getColor(getActivity(), R.color.dark_grey_color));
d.setAlpha(200);
mZrtpDialog.setContentView(R.layout.dialog);
mZrtpDialog
.getWindow()
.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
mZrtpDialog.getWindow().setBackgroundDrawable(d);
String zrtpToRead, zrtpToListen;
if (call.getDir().equals(Call.Dir.Incoming)) {
zrtpToRead = token.substring(0, 2);
zrtpToListen = token.substring(2);
} else {
zrtpToListen = token.substring(0, 2);
zrtpToRead = token.substring(2);
}
TextView localSas = mZrtpDialog.findViewById(R.id.zrtp_sas_local);
localSas.setText(zrtpToRead.toUpperCase());
TextView remoteSas = mZrtpDialog.findViewById(R.id.zrtp_sas_remote);
remoteSas.setText(zrtpToListen.toUpperCase());
TextView message = mZrtpDialog.findViewById(R.id.dialog_message);
message.setVisibility(View.GONE);
mZrtpDialog.findViewById(R.id.dialog_zrtp_layout).setVisibility(View.VISIBLE);
TextView title = mZrtpDialog.findViewById(R.id.dialog_title);
title.setText(getString(R.string.zrtp_dialog_title));
title.setVisibility(View.VISIBLE);
Button delete = mZrtpDialog.findViewById(R.id.dialog_delete_button);
delete.setText(R.string.deny);
Button cancel = mZrtpDialog.findViewById(R.id.dialog_cancel_button);
cancel.setVisibility(View.GONE);
Button accept = mZrtpDialog.findViewById(R.id.dialog_ok_button);
accept.setVisibility(View.VISIBLE);
accept.setText(R.string.accept);
ImageView icon = mZrtpDialog.findViewById(R.id.dialog_icon);
icon.setVisibility(View.VISIBLE);
icon.setImageResource(R.drawable.security_2_indicator);
delete.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
if (call != null) {
call.setAuthenticationTokenVerified(false);
if (mEncryption != null) {
mEncryption.setImageResource(R.drawable.security_ko);
}
}
mZrtpDialog.dismiss();
}
});
accept.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
call.setAuthenticationTokenVerified(true);
if (mEncryption != null) {
mEncryption.setImageResource(R.drawable.security_ok);
}
mZrtpDialog.dismiss();
}
});
mZrtpDialog.show();
}
}
}

View file

@ -0,0 +1,185 @@
package org.linphone.history;
/*
HistoryAdapter.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.utils.LinphoneUtils;
import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper;
import org.linphone.views.ContactAvatar;
public class HistoryAdapter extends SelectableAdapter<HistoryViewHolder> {
private final List<CallLog> mLogs;
private final Context mContext;
private final HistoryViewHolder.ClickListener mClickListener;
public HistoryAdapter(
Context aContext,
List<CallLog> logs,
HistoryViewHolder.ClickListener listener,
SelectableHelper helper) {
super(helper);
mLogs = logs;
mContext = aContext;
mClickListener = listener;
}
public Object getItem(int position) {
return mLogs.get(position);
}
@NonNull
@Override
public HistoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v =
LayoutInflater.from(parent.getContext())
.inflate(R.layout.history_cell, parent, false);
return new HistoryViewHolder(v, mClickListener);
}
@Override
public void onBindViewHolder(@NonNull final HistoryViewHolder holder, final int position) {
final CallLog log = mLogs.get(position);
long timestamp = log.getStartDate() * 1000;
Address address;
holder.contact.setSelected(true); // For automated horizontal scrolling of long texts
Calendar logTime = Calendar.getInstance();
logTime.setTimeInMillis(timestamp);
holder.separatorText.setText(timestampToHumanDate(logTime));
holder.select.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE);
holder.select.setChecked(isSelected(position));
if (position > 0) {
CallLog previousLog = mLogs.get(position - 1);
long previousTimestamp = previousLog.getStartDate() * 1000;
Calendar previousLogTime = Calendar.getInstance();
previousLogTime.setTimeInMillis(previousTimestamp);
if (isSameDay(previousLogTime, logTime)) {
holder.separator.setVisibility(View.GONE);
} else {
holder.separator.setVisibility(View.VISIBLE);
}
} else {
holder.separator.setVisibility(View.VISIBLE);
}
if (log.getDir() == Call.Dir.Incoming) {
address = log.getFromAddress();
if (log.getStatus() == Call.Status.Missed) {
holder.callDirection.setImageResource(R.drawable.call_status_missed);
} else {
holder.callDirection.setImageResource(R.drawable.call_status_incoming);
}
} else {
address = log.getToAddress();
holder.callDirection.setImageResource(R.drawable.call_status_outgoing);
}
LinphoneContact c = ContactsManager.getInstance().findContactFromAddress(address);
String displayName = null;
final String sipUri = (address != null) ? address.asString() : "";
if (c != null) {
displayName = c.getFullName();
}
if (displayName == null) {
holder.contact.setText(LinphoneUtils.getAddressDisplayName(sipUri));
} else {
holder.contact.setText(displayName);
}
if (c != null) {
ContactAvatar.displayAvatar(c, holder.avatarLayout);
} else {
ContactAvatar.displayAvatar(holder.contact.getText().toString(), holder.avatarLayout);
}
holder.detail.setVisibility(isEditionEnabled() ? View.INVISIBLE : View.VISIBLE);
holder.detail.setOnClickListener(
!isEditionEnabled()
? new View.OnClickListener() {
@Override
public void onClick(View v) {
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().displayHistoryDetail(sipUri, log);
}
}
}
: null);
}
@Override
public int getItemCount() {
return mLogs.size();
}
@SuppressLint("SimpleDateFormat")
private String timestampToHumanDate(Calendar cal) {
SimpleDateFormat dateFormat;
if (isToday(cal)) {
return mContext.getString(R.string.today);
} else if (isYesterday(cal)) {
return mContext.getString(R.string.yesterday);
} else {
dateFormat =
new SimpleDateFormat(
mContext.getResources().getString(R.string.history_date_format));
}
return dateFormat.format(cal.getTime());
}
private boolean isSameDay(Calendar cal1, Calendar cal2) {
if (cal1 == null || cal2 == null) {
return false;
}
return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
&& cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
&& cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
}
private boolean isToday(Calendar cal) {
return isSameDay(cal, Calendar.getInstance());
}
private boolean isYesterday(Calendar cal) {
Calendar yesterday = Calendar.getInstance();
yesterday.roll(Calendar.DAY_OF_MONTH, -1);
return isSameDay(cal, yesterday);
}
}

View file

@ -0,0 +1,284 @@
package org.linphone.history;
/*
HistoryDetailFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.CallLog;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomBackend;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.ChatRoomParams;
import org.linphone.core.Core;
import org.linphone.core.Factory;
import org.linphone.core.FriendCapability;
import org.linphone.core.ProxyConfig;
import org.linphone.core.tools.Log;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.settings.LinphonePreferences;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
public class HistoryDetailFragment extends Fragment implements OnClickListener {
private ImageView mDialBack, mChat, mAddToContacts, mGoToContact, mBack;
private View mView;
private TextView mContactName, mContactAddress;
private String mSipUri, mDisplayName;
private RelativeLayout mWaitLayout, mAvatarLayout, mChatSecured;
private LinphoneContact mContact;
private ChatRoom mChatRoom;
private ChatRoomListenerStub mChatRoomCreationListener;
private ListView mLogsList;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mSipUri = getArguments().getString("SipUri");
mDisplayName = getArguments().getString("DisplayName");
mView = inflater.inflate(R.layout.history_detail, container, false);
mWaitLayout = mView.findViewById(R.id.waitScreen);
mWaitLayout.setVisibility(View.GONE);
mDialBack = mView.findViewById(R.id.call);
mDialBack.setOnClickListener(this);
mBack = mView.findViewById(R.id.back);
if (getResources().getBoolean(R.bool.isTablet)) {
mBack.setVisibility(View.INVISIBLE);
} else {
mBack.setOnClickListener(this);
}
mChat = mView.findViewById(R.id.chat);
mChat.setOnClickListener(this);
mChatSecured = mView.findViewById(R.id.chat_secured);
mChatSecured.setOnClickListener(this);
if (getResources().getBoolean(R.bool.disable_chat)) {
mChat.setVisibility(View.GONE);
mChatSecured.setVisibility(View.GONE);
}
mAddToContacts = mView.findViewById(R.id.add_contact);
mAddToContacts.setOnClickListener(this);
mGoToContact = mView.findViewById(R.id.goto_contact);
mGoToContact.setOnClickListener(this);
mAvatarLayout = mView.findViewById(R.id.avatar_layout);
mContactName = mView.findViewById(R.id.contact_name);
mContactAddress = mView.findViewById(R.id.contact_address);
mChatRoomCreationListener =
new ChatRoomListenerStub() {
@Override
public void onStateChanged(ChatRoom cr, ChatRoom.State newState) {
if (newState == ChatRoom.State.Created) {
mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance()
.goToChat(
cr.getLocalAddress().asStringUriOnly(),
cr.getPeerAddress().asStringUriOnly(),
null);
} else if (newState == ChatRoom.State.CreationFailed) {
mWaitLayout.setVisibility(View.GONE);
LinphoneActivity.instance().displayChatRoomError();
Log.e(
"Group mChat room for address "
+ cr.getPeerAddress()
+ " has failed !");
}
}
};
mLogsList = mView.findViewById(R.id.logs_list);
displayHistory();
return mView;
}
private void displayHistory() {
Address lAddress = Factory.instance().createAddress(mSipUri);
mChatSecured.setVisibility(View.GONE);
if (lAddress != null) {
CallLog[] logs =
LinphoneManager.getLcIfManagerNotDestroyedOrNull()
.getCallHistoryForAddress(lAddress);
List<CallLog> logsList = Arrays.asList(logs);
mLogsList.setAdapter(
new HistoryLogAdapter(
LinphoneActivity.instance(), R.layout.history_detail_cell, logsList));
mContactAddress.setText(LinphoneUtils.getDisplayableAddress(lAddress));
mContact = ContactsManager.getInstance().findContactFromAddress(lAddress);
if (mContact != null) {
mContactName.setText(mContact.getFullName());
ContactAvatar.displayAvatar(mContact, mAvatarLayout);
mAddToContacts.setVisibility(View.GONE);
mGoToContact.setVisibility(View.VISIBLE);
if (!getResources().getBoolean(R.bool.disable_chat)
&& mContact.hasPresenceModelForUriOrTelCapability(
mSipUri, FriendCapability.LimeX3Dh)) {
mChatSecured.setVisibility(View.VISIBLE);
}
} else {
mContactName.setText(
mDisplayName == null
? LinphoneUtils.getAddressDisplayName(mSipUri)
: mDisplayName);
ContactAvatar.displayAvatar(
LinphoneUtils.getAddressDisplayName(lAddress), mAvatarLayout);
mAddToContacts.setVisibility(View.VISIBLE);
mGoToContact.setVisibility(View.GONE);
}
} else {
mContactAddress.setText(mSipUri);
mContactName.setText(
mDisplayName == null
? LinphoneUtils.getAddressDisplayName(mSipUri)
: mDisplayName);
}
}
@Override
public void onPause() {
if (mChatRoom != null) {
mChatRoom.removeListener(mChatRoomCreationListener);
}
super.onPause();
}
public void changeDisplayedHistory(String sipUri, String displayName) {
if (displayName == null) {
displayName = LinphoneUtils.getUsernameFromAddress(sipUri);
}
mSipUri = sipUri;
mDisplayName = displayName;
displayHistory();
}
@Override
public void onResume() {
super.onResume();
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.HISTORY_DETAIL);
}
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.back) {
getFragmentManager().popBackStackImmediate();
}
if (id == R.id.call) {
LinphoneActivity.instance().setAddresGoToDialerAndCall(mSipUri, mDisplayName);
} else if (id == R.id.chat || id == R.id.chat_secured) {
boolean isSecured = id == R.id.chat_secured;
Core lc = LinphoneManager.getLc();
Address participant = Factory.instance().createAddress(mSipUri);
ChatRoom room =
lc.findOneToOneChatRoom(
lc.getDefaultProxyConfig().getContact(), participant, isSecured);
if (room != null) {
LinphoneActivity.instance()
.goToChat(
room.getLocalAddress().asStringUriOnly(),
room.getPeerAddress().asStringUriOnly(),
null);
} else {
ProxyConfig lpc = lc.getDefaultProxyConfig();
if (lpc != null
&& lpc.getConferenceFactoryUri() != null
&& (isSecured
|| !LinphonePreferences.instance().useBasicChatRoomFor1To1())) {
mWaitLayout.setVisibility(View.VISIBLE);
ChatRoomParams params = lc.createDefaultChatRoomParams();
params.enableEncryption(isSecured);
params.enableGroup(false);
// We don't want a basic chat room
params.setBackend(ChatRoomBackend.FlexisipChat);
Address participants[] = new Address[1];
participants[0] = participant;
mChatRoom =
lc.createChatRoom(
params,
getString(R.string.dummy_group_chat_subject),
participants);
if (mChatRoom != null) {
mChatRoom.addListener(mChatRoomCreationListener);
} else {
Log.w("[History Detail Fragment] createChatRoom returned null...");
mWaitLayout.setVisibility(View.GONE);
}
} else {
room = lc.getChatRoom(participant);
LinphoneActivity.instance()
.goToChat(
room.getLocalAddress().asStringUriOnly(),
room.getPeerAddress().asStringUriOnly(),
null);
}
}
} else if (id == R.id.add_contact) {
Address addr = Factory.instance().createAddress(mSipUri);
if (addr != null) {
String address =
"sip:" + addr.getUsername() + "@" + addr.getDomain(); // Clean gruu param
if (addr.getDisplayName() != null) {
LinphoneActivity.instance()
.displayContactsForEdition(address, addr.getDisplayName());
} else {
LinphoneActivity.instance().displayContactsForEdition(address);
}
}
} else if (id == R.id.goto_contact) {
LinphoneActivity.instance().displayContact(mContact, false);
}
}
}

View file

@ -1,7 +1,7 @@
package org.linphone.fragments; package org.linphone.history;
/* /*
HistoryListFragment.java HistoryFragment.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France Copyright (C) 2017 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
@ -22,9 +22,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
@ -33,73 +30,78 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.R; import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.call.CallHistoryAdapter;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener; import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.Call; import org.linphone.core.Call;
import org.linphone.core.CallLog; import org.linphone.core.CallLog;
import org.linphone.ui.SelectableHelper; import org.linphone.fragments.FragmentsAvailable;
import org.linphone.utils.SelectableHelper;
import java.util.ArrayList; public class HistoryFragment extends Fragment
import java.util.Arrays; implements OnClickListener,
import java.util.List; OnItemClickListener,
HistoryViewHolder.ClickListener,
public class HistoryListFragment extends Fragment implements OnClickListener, OnItemClickListener, CallHistoryAdapter.ViewHolder.ClickListener, ContactsUpdatedListener, SelectableHelper.DeleteListener { ContactsUpdatedListener,
private RecyclerView historyList; SelectableHelper.DeleteListener {
private TextView noCallHistory, noMissedCallHistory; private RecyclerView mHistoryList;
private ImageView missedCalls, allCalls, edit; private TextView mNoCallHistory, mNoMissedCallHistory;
private View allCallsSelected, missedCallsSelected; private ImageView mMissedCalls, mAllCalls;
private View mAllCallsSelected, mMissedCallsSelected;
private boolean mOnlyDisplayMissedCalls; private boolean mOnlyDisplayMissedCalls;
private List<CallLog> mLogs; private List<CallLog> mLogs;
private CallHistoryAdapter mHistoryAdapter; private HistoryAdapter mHistoryAdapter;
private LinearLayoutManager mLayoutManager; private LinearLayoutManager mLayoutManager;
private Context mContext; private Context mContext;
private SelectableHelper mSelectionHelper; private SelectableHelper mSelectionHelper;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(
Bundle savedInstanceState) { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.history, container, false); View view = inflater.inflate(R.layout.history, container, false);
mContext = getActivity().getApplicationContext(); mContext = getActivity().getApplicationContext();
mSelectionHelper = new SelectableHelper(view, this); mSelectionHelper = new SelectableHelper(view, this);
noCallHistory = view.findViewById(R.id.no_call_history); mNoCallHistory = view.findViewById(R.id.no_call_history);
noMissedCallHistory = view.findViewById(R.id.no_missed_call_history); mNoMissedCallHistory = view.findViewById(R.id.no_missed_call_history);
historyList = view.findViewById(R.id.history_list); mHistoryList = view.findViewById(R.id.history_list);
mLayoutManager = new LinearLayoutManager(mContext); mLayoutManager = new LinearLayoutManager(mContext);
historyList.setLayoutManager(mLayoutManager); mHistoryList.setLayoutManager(mLayoutManager);
//Divider between items // Divider between items
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(historyList.getContext(), DividerItemDecoration dividerItemDecoration =
mLayoutManager.getOrientation()); new DividerItemDecoration(
mHistoryList.getContext(), mLayoutManager.getOrientation());
dividerItemDecoration.setDrawable(mContext.getResources().getDrawable(R.drawable.divider)); dividerItemDecoration.setDrawable(mContext.getResources().getDrawable(R.drawable.divider));
historyList.addItemDecoration(dividerItemDecoration); mHistoryList.addItemDecoration(dividerItemDecoration);
allCalls = view.findViewById(R.id.all_calls); mAllCalls = view.findViewById(R.id.all_calls);
allCalls.setOnClickListener(this); mAllCalls.setOnClickListener(this);
allCallsSelected = view.findViewById(R.id.all_calls_select); mAllCallsSelected = view.findViewById(R.id.all_calls_select);
missedCalls = view.findViewById(R.id.missed_calls); mMissedCalls = view.findViewById(R.id.missed_calls);
missedCalls.setOnClickListener(this); mMissedCalls.setOnClickListener(this);
missedCallsSelected = view.findViewById(R.id.missed_calls_select); mMissedCallsSelected = view.findViewById(R.id.missed_calls_select);
allCalls.setEnabled(false); mAllCalls.setEnabled(false);
mOnlyDisplayMissedCalls = false; mOnlyDisplayMissedCalls = false;
edit = view.findViewById(R.id.edit);
return view; return view;
} }
public void refresh() { private void refresh() {
mLogs = Arrays.asList(LinphoneManager.getLc().getCallLogs()); mLogs = Arrays.asList(LinphoneManager.getLc().getCallLogs());
} }
@ -120,7 +122,7 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
private void removeNotMissedCallsFromLogs() { private void removeNotMissedCallsFromLogs() {
if (mOnlyDisplayMissedCalls) { if (mOnlyDisplayMissedCalls) {
List<CallLog> missedCalls = new ArrayList<CallLog>(); List<CallLog> missedCalls = new ArrayList<>();
for (CallLog log : mLogs) { for (CallLog log : mLogs) {
if (log.getStatus() == Call.Status.Missed) { if (log.getStatus() == Call.Status.Missed) {
missedCalls.add(log); missedCalls.add(log);
@ -130,57 +132,57 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
} }
} }
private boolean hideHistoryListAndDisplayMessageIfEmpty() { private void hideHistoryListAndDisplayMessageIfEmpty() {
removeNotMissedCallsFromLogs(); removeNotMissedCallsFromLogs();
mNoCallHistory.setVisibility(View.GONE);
mNoMissedCallHistory.setVisibility(View.GONE);
if (mLogs.isEmpty()) { if (mLogs.isEmpty()) {
if (mOnlyDisplayMissedCalls) { if (mOnlyDisplayMissedCalls) {
noMissedCallHistory.setVisibility(View.VISIBLE); mNoMissedCallHistory.setVisibility(View.VISIBLE);
} else { } else {
noCallHistory.setVisibility(View.VISIBLE); mNoCallHistory.setVisibility(View.VISIBLE);
} }
historyList.setVisibility(View.GONE); mHistoryList.setVisibility(View.GONE);
edit.setEnabled(false);
return true;
} else { } else {
noCallHistory.setVisibility(View.GONE); mNoCallHistory.setVisibility(View.GONE);
noMissedCallHistory.setVisibility(View.GONE); mNoMissedCallHistory.setVisibility(View.GONE);
historyList.setVisibility(View.VISIBLE); mHistoryList.setVisibility(View.VISIBLE);
edit.setEnabled(true);
return false;
} }
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
ContactsManager.addContactsListener(this); ContactsManager.getInstance().addContactsListener(this);
if (LinphoneActivity.isInstanciated()) { if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().selectMenu(FragmentsAvailable.HISTORY_LIST); LinphoneActivity.instance().selectMenu(FragmentsAvailable.HISTORY_LIST);
LinphoneActivity.instance().hideTabBar(false);
LinphoneActivity.instance().displayMissedCalls(0); LinphoneActivity.instance().displayMissedCalls(0);
} }
mLogs = Arrays.asList(LinphoneManager.getLc().getCallLogs()); mLogs = Arrays.asList(LinphoneManager.getLc().getCallLogs());
if (!hideHistoryListAndDisplayMessageIfEmpty()) { hideHistoryListAndDisplayMessageIfEmpty();
mHistoryAdapter = new CallHistoryAdapter(getActivity().getApplicationContext(), mLogs, this, mSelectionHelper); mHistoryAdapter =
historyList.setAdapter(mHistoryAdapter); new HistoryAdapter(
mSelectionHelper.setAdapter(mHistoryAdapter); getActivity().getApplicationContext(), mLogs, this, mSelectionHelper);
mSelectionHelper.setDialogMessage(R.string.chat_room_delete_dialog); mHistoryList.setAdapter(mHistoryAdapter);
} mSelectionHelper.setAdapter(mHistoryAdapter);
mSelectionHelper.setDialogMessage(R.string.call_log_delete_dialog);
} }
@Override @Override
public void onPause() { public void onPause() {
ContactsManager.removeContactsListener(this); ContactsManager.getInstance().removeContactsListener(this);
super.onPause(); super.onPause();
} }
@Override @Override
public void onContactsUpdated() { public void onContactsUpdated() {
if (!LinphoneActivity.isInstanciated() || LinphoneActivity.instance().getCurrentFragment() != FragmentsAvailable.HISTORY_LIST) if (!LinphoneActivity.isInstanciated()
return; || LinphoneActivity.instance().getCurrentFragment()
CallHistoryAdapter adapter = (CallHistoryAdapter) historyList.getAdapter(); != FragmentsAvailable.HISTORY_LIST) return;
HistoryAdapter adapter = (HistoryAdapter) mHistoryList.getAdapter();
if (adapter != null) { if (adapter != null) {
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
@ -191,28 +193,25 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
int id = v.getId(); int id = v.getId();
if (id == R.id.all_calls) { if (id == R.id.all_calls) {
allCalls.setEnabled(false); mAllCalls.setEnabled(false);
allCallsSelected.setVisibility(View.VISIBLE); mAllCallsSelected.setVisibility(View.VISIBLE);
missedCallsSelected.setVisibility(View.INVISIBLE); mMissedCallsSelected.setVisibility(View.INVISIBLE);
missedCalls.setEnabled(true); mMissedCalls.setEnabled(true);
mOnlyDisplayMissedCalls = false; mOnlyDisplayMissedCalls = false;
refresh(); refresh();
} }
if (id == R.id.missed_calls) { if (id == R.id.missed_calls) {
allCalls.setEnabled(true); mAllCalls.setEnabled(true);
allCallsSelected.setVisibility(View.INVISIBLE); mAllCallsSelected.setVisibility(View.INVISIBLE);
missedCallsSelected.setVisibility(View.VISIBLE); mMissedCallsSelected.setVisibility(View.VISIBLE);
missedCalls.setEnabled(false); mMissedCalls.setEnabled(false);
mOnlyDisplayMissedCalls = true; mOnlyDisplayMissedCalls = true;
} }
if (!hideHistoryListAndDisplayMessageIfEmpty()) { hideHistoryListAndDisplayMessageIfEmpty();
// historyList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE); mHistoryAdapter = new HistoryAdapter(mContext, mLogs, this, mSelectionHelper);
mHistoryAdapter = new CallHistoryAdapter(mContext, mLogs, this, mSelectionHelper); mHistoryList.setAdapter(mHistoryAdapter);
historyList.setAdapter(mHistoryAdapter); mSelectionHelper.setAdapter(mHistoryAdapter);
mSelectionHelper.setAdapter(mHistoryAdapter); mSelectionHelper.setDialogMessage(R.string.chat_room_delete_dialog);
mSelectionHelper.setDialogMessage(R.string.chat_room_delete_dialog);
}
} }
@Override @Override
@ -247,7 +246,9 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
} else { } else {
address = log.getToAddress(); address = log.getToAddress();
} }
LinphoneActivity.instance().setAddresGoToDialerAndCall(address.asStringUriOnly(), address.getDisplayName(), null); LinphoneActivity.instance()
.setAddresGoToDialerAndCall(
address.asStringUriOnly(), address.getDisplayName());
} }
} }
} }
@ -260,4 +261,4 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
mHistoryAdapter.toggleSelection(position); mHistoryAdapter.toggleSelection(position);
return true; return true;
} }
} }

View file

@ -0,0 +1,115 @@
package org.linphone.history;
/*
HistoryLogAdapter.java
Copyright (C) 2019 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import org.linphone.R;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.utils.LinphoneUtils;
class HistoryLogAdapter extends ArrayAdapter<CallLog> {
private Context mContext;
private final List<CallLog> mItems;
private final int mResource;
HistoryLogAdapter(@NonNull Context context, int resource, @NonNull List<CallLog> objects) {
super(context, resource, objects);
mContext = context;
mResource = resource;
mItems = objects;
}
@Nullable
@Override
public CallLog getItem(int position) {
return mItems.get(position);
}
@Override
public int getCount() {
return mItems.size();
}
@SuppressLint("SimpleDateFormat")
private String secondsToDisplayableString(int secs) {
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
Calendar cal = Calendar.getInstance();
cal.set(0, 0, 0, 0, 0, secs);
return dateFormat.format(cal.getTime());
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
LayoutInflater inflater =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(mResource, parent, false);
CallLog callLog = getItem(position);
String callTime = secondsToDisplayableString(callLog.getDuration());
String callDate = String.valueOf(callLog.getStartDate());
String status;
if (callLog.getDir() == Call.Dir.Outgoing) {
status = mContext.getString(R.string.outgoing);
} else {
if (callLog.getStatus() == Call.Status.Missed) {
status = mContext.getString(R.string.missed);
} else {
status = mContext.getString(R.string.incoming);
}
}
TextView date = rowView.findViewById(R.id.date);
TextView time = rowView.findViewById(R.id.time);
ImageView callDirection = rowView.findViewById(R.id.direction);
if (status.equals(mContext.getResources().getString(R.string.missed))) {
callDirection.setImageResource(R.drawable.call_missed);
} else if (status.equals(mContext.getResources().getString(R.string.incoming))) {
callDirection.setImageResource(R.drawable.call_incoming);
} else if (status.equals(mContext.getResources().getString(R.string.outgoing))) {
callDirection.setImageResource(R.drawable.call_outgoing);
}
time.setText(callTime == null ? "" : callTime);
Long longDate = Long.parseLong(callDate);
date.setText(
LinphoneUtils.timestampToHumanDate(
mContext,
longDate,
mContext.getString(R.string.history_detail_date_format)));
return rowView;
}
}

View file

@ -0,0 +1,77 @@
package org.linphone.history;
/*
HistoryViewHolder.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.linphone.R;
public class HistoryViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnLongClickListener {
public final TextView contact;
public final ImageView detail;
public final CheckBox select;
public final ImageView callDirection;
public final RelativeLayout avatarLayout;
public final LinearLayout separator;
public final TextView separatorText;
private final ClickListener mListener;
public HistoryViewHolder(View view, ClickListener listener) {
super(view);
contact = view.findViewById(R.id.sip_uri);
detail = view.findViewById(R.id.detail);
select = view.findViewById(R.id.delete);
callDirection = view.findViewById(R.id.icon);
avatarLayout = view.findViewById(R.id.avatar_layout);
separator = view.findViewById(R.id.separator);
separatorText = view.findViewById(R.id.separator_text);
mListener = listener;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
@Override
public void onClick(View view) {
if (mListener != null) {
mListener.onItemClicked(getAdapterPosition());
}
}
@Override
public boolean onLongClick(View view) {
if (mListener != null) {
return mListener.onItemLongClicked(getAdapterPosition());
}
return false;
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}

View file

@ -0,0 +1,106 @@
package org.linphone.notifications;
/*
Notifiable.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import java.util.ArrayList;
import java.util.List;
public class Notifiable {
private final int mNotificationId;
private List<NotifiableMessage> mMessages;
private boolean mIsGroup;
private String mGroupTitle;
private String mLocalIdentity;
private String mMyself;
private int iconId;
private int textId;
public Notifiable(int id) {
mNotificationId = id;
mMessages = new ArrayList<>();
mIsGroup = false;
iconId = 0;
textId = 0;
}
public int getNotificationId() {
return mNotificationId;
}
public void resetMessages() {
mMessages = new ArrayList<>();
}
public void addMessage(NotifiableMessage notifMessage) {
mMessages.add(notifMessage);
}
public List<NotifiableMessage> getMessages() {
return mMessages;
}
public boolean isGroup() {
return mIsGroup;
}
public void setIsGroup(boolean isGroup) {
mIsGroup = isGroup;
}
public String getGroupTitle() {
return mGroupTitle;
}
public void setGroupTitle(String title) {
mGroupTitle = title;
}
public String getMyself() {
return mMyself;
}
public void setMyself(String myself) {
mMyself = myself;
}
public String getLocalIdentity() {
return mLocalIdentity;
}
public void setLocalIdentity(String localIdentity) {
mLocalIdentity = localIdentity;
}
public int getIconResourceId() {
return iconId;
}
public void setIconResourceId(int id) {
iconId = id;
}
public int getTextResourceId() {
return textId;
}
public void setTextResourceId(int id) {
textId = id;
}
}

View file

@ -0,0 +1,69 @@
package org.linphone.notifications;
/*
NotifiableMessage.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.graphics.Bitmap;
import android.net.Uri;
public class NotifiableMessage {
private final String mMessage;
private final String mSender;
private final long mTime;
private Bitmap mSenderBitmap;
private final Uri mFilePath;
private final String mFileMime;
public NotifiableMessage(
String message, String sender, long time, Uri filePath, String fileMime) {
mMessage = message;
mSender = sender;
mTime = time;
mFilePath = filePath;
mFileMime = fileMime;
}
public String getMessage() {
return mMessage;
}
public String getSender() {
return mSender;
}
public long getTime() {
return mTime;
}
public Bitmap getSenderBitmap() {
return mSenderBitmap;
}
public void setSenderBitmap(Bitmap bm) {
mSenderBitmap = bm;
}
public Uri getFilePath() {
return mFilePath;
}
public String getFileMime() {
return mFileMime;
}
}

View file

@ -0,0 +1,168 @@
package org.linphone.notifications;
/*
NotificationBroadcastReceiver.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Notification;
import android.app.RemoteInput;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import org.linphone.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneService;
import org.linphone.R;
import org.linphone.compatibility.Compatibility;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatMessageListenerStub;
import org.linphone.core.ChatRoom;
import org.linphone.core.Core;
import org.linphone.core.tools.Log;
public class NotificationBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
final int notifId = intent.getIntExtra(Compatibility.INTENT_NOTIF_ID, 0);
final String localyIdentity = intent.getStringExtra(Compatibility.INTENT_LOCAL_IDENTITY);
if (intent.getAction().equals(Compatibility.INTENT_REPLY_NOTIF_ACTION)
|| intent.getAction().equals(Compatibility.INTENT_MARK_AS_READ_ACTION)) {
String remoteSipAddr =
LinphoneService.instance()
.getNotificationManager()
.getSipUriForNotificationId(notifId);
Core core = LinphoneManager.getLc();
if (core == null) {
Log.e("[Notification Broadcast Receiver] Couldn't get Core instance");
onError(context, notifId);
return;
}
Address remoteAddr = core.interpretUrl(remoteSipAddr);
if (remoteAddr == null) {
Log.e(
"[Notification Broadcast Receiver] Couldn't interpret remote address "
+ remoteSipAddr);
onError(context, notifId);
return;
}
Address localAddr = core.interpretUrl(localyIdentity);
if (localAddr == null) {
Log.e(
"[Notification Broadcast Receiver] Couldn't interpret local address "
+ localyIdentity);
onError(context, notifId);
return;
}
ChatRoom room = core.getChatRoom(remoteAddr, localAddr);
if (room == null) {
Log.e(
"[Notification Broadcast Receiver] Couldn't find chat room for remote address "
+ remoteSipAddr
+ " and local address "
+ localyIdentity);
onError(context, notifId);
return;
}
room.markAsRead();
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance()
.displayMissedChats(LinphoneManager.getInstance().getUnreadMessageCount());
}
if (intent.getAction().equals(Compatibility.INTENT_REPLY_NOTIF_ACTION)) {
final String reply = getMessageText(intent).toString();
if (reply == null) {
Log.e("[Notification Broadcast Receiver] Couldn't get reply text");
onError(context, notifId);
return;
}
ChatMessage msg = room.createMessage(reply);
msg.send();
msg.addListener(
new ChatMessageListenerStub() {
@Override
public void onMsgStateChanged(
ChatMessage msg, ChatMessage.State state) {
if (state == ChatMessage.State.Delivered) {
Notification replied =
Compatibility.createRepliedNotification(context, reply);
LinphoneService.instance()
.getNotificationManager()
.sendNotification(notifId, replied);
} else if (state == ChatMessage.State.NotDelivered) {
Log.e(
"[Notification Broadcast Receiver] Couldn't send reply, message is not delivered");
onError(context, notifId);
}
}
});
} else {
LinphoneService.instance().getNotificationManager().dismissNotification(notifId);
}
} else if (intent.getAction().equals(Compatibility.INTENT_ANSWER_CALL_NOTIF_ACTION)
|| intent.getAction().equals(Compatibility.INTENT_HANGUP_CALL_NOTIF_ACTION)) {
String remoteAddr =
LinphoneService.instance()
.getNotificationManager()
.getSipUriForCallNotificationId(notifId);
Core core = LinphoneManager.getLc();
if (core == null) {
Log.e("[Notification Broadcast Receiver] Couldn't get Core instance");
return;
}
Call call = core.findCallFromUri(remoteAddr);
if (call == null) {
Log.e(
"[Notification Broadcast Receiver] Couldn't find call from remote address "
+ remoteAddr);
return;
}
if (intent.getAction().equals(Compatibility.INTENT_ANSWER_CALL_NOTIF_ACTION)) {
call.accept();
} else {
call.terminate();
}
}
}
private void onError(Context context, int notifId) {
Notification replyError =
Compatibility.createRepliedNotification(context, context.getString(R.string.error));
LinphoneService.instance().getNotificationManager().sendNotification(notifId, replyError);
}
private CharSequence getMessageText(Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
return remoteInput.getCharSequence(Compatibility.KEY_TEXT_REPLY);
}
return null;
}
}

Some files were not shown because too many files have changed in this diff Show more