diff --git a/.gitignore b/.gitignore index 81a24e7f4..48eb0334e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,16 +2,12 @@ *.swp .settings -src/conversationsPlaystore/res/values/push.xml -src/quicksyPlaystore/res/values/push.xml - +src/playstore/res/values/gcm.xml # https://github.com/github/gitignore/blob/master/Gradle.gitignore .gradle/ build/ -gradle.properties captures/ -signing.properties # Ignore Gradle GUI config gradle-app.setting @@ -46,3 +42,5 @@ proguard/ import-summary.txt .navigation/ +src/main/res/values/api_keys.xml +src/standardPush/res/values/gcm.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..ca171dde6 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,57 @@ +image: registry.gitlab.com/fdroid/ci-images-client:latest + +cache: + paths: + - .gradle/wrapper + - .gradle/caches + +stages: + - build + +before_script: + - export GRADLE_USER_HOME=$PWD/.gradle + - export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle` + - echo y | sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null + + +build: + stage: build + #except: + #- master + script: + # generate version number + - export versionCode="$CI_JOB_ID" + - export versionName="$(git describe --tag --abbrev=0)-${CI_JOB_ID}_${CI_COMMIT_REF_NAME}" + - echo "set VersionCode '${versionCode}' and VersonName '${versionName}'" + - sed -i "s/^\(\s*versionCode\s*\).*$/\1$versionCode/" build.gradle + - sed -i "0,/versionName/s/^\(\s*versionName\).*/\1 \"$versionName\"/" build.gradle + # build + - ./gradlew assembleStandard + artifacts: + paths: + - build/outputs/apk/standard + +publish: + stage: build + only: + #- master + - master--not-build-on-my-repo + script: + # generate version number + - export versionCode="$CI_JOB_ID" + - export versionName="$(git describe --tag --abbrev=0)-${CI_JOB_ID}_${CI_COMMIT_REF_NAME}" + - echo "set VersionCode '${versionCode}' and VersonName '${versionName}'" + - sed -i "s/^\(\s*versionCode\s*\).*$/\1$versionCode/" build.gradle + - sed -i "0,/versionName/s/^\(\s*versionName\).*/\1 \"$versionName\"/" build.gradle + # build + - ./gradlew assembleStandard + # publish on nightly fdroid repo + - fdroid nightly -v + artifacts: + paths: + - build/outputs/apk/standard + +after_script: + # this file changes every time but should not be cached + - rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock + - rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/ diff --git a/.tx/config b/.tx/config deleted file mode 100644 index 7ed95ad03..000000000 --- a/.tx/config +++ /dev/null @@ -1,21 +0,0 @@ -[main] -host = https://www.transifex.com -lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw - -[conversations.strings] -file_filter = src/main/res/values-/strings.xml -source_file = src/main/res/values/strings.xml -source_lang = en - -[conversations.quicksy-strings] -file_filter = src/quicksy/res/values-/strings.xml -source_file = src/quicksy/res/values/strings.xml -source_lang = en -type = ANDROID - -[conversations.conversations-strings] -file_filter = src/conversations/res/values-/strings.xml -source_file = src/conversations/res/values/strings.xml -source_lang = en -type = ANDROID - diff --git a/CHANGELOG.md b/CHANGELOG.md index 360b1acd3..6c9a8c463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,703 +1,739 @@ -# Changelog +### Changelog -### Version 2.8.2 +#### Version 2.3.7 +* bug fixes -* Add button to switch camea during video call -* Fixed voice calls on tablets +#### Version 2.3.6 +* bug fixes -### Version 2.8.1 - -* Audible feedback (dialing, call started, call ended) for voice calls. -* Fixed issue with retrying failed video call - -### Version 2.8.0 - -* Audio/Video calls (Requires server support in form of STUN and TURN servers discoverable via XEP-0215) - - -### Version 2.7.1 - -* Fix avatar selection on some Android 10 devices -* Fix file transfer for larger files - -### Version 2.7.0 - -* Provide PDF preview on Android 5+ -* Use 12 byte IVs for OMEMO - -### Version 2.6.4 - -* Support automatic theme switching on Android 10 - -### Version 2.6.3 - -* Support for ?register and ?register;preauth XMPP uri parameters - -### Version 2.6.2 -* let users set their own nick name +#### Version 2.3.5 +* start removing OTR +* rework conference and contact details (big avatar is available via long click) (PAM) * resume download of OMEMO encrypted files -* Channels now use '#' as symbol in avatar -* Quicksy uses 'always' as OMEMO encryption default (hides lock icon) +* channels now use '#' as symbol in avatar +* support for ?register and ?register;preauth XMPP uri parameters +* update connection settings +* use ExoPlayer for video playback (PAM) +* show artist - title for audio files (PAM) +* show PDF previews (PAM) +* minor UI improvements (PAM) +* use 12 byte IV for OMEMO +* a lot of bug fixes -### Version 2.6.1 +#### Version 2.3.4 * fixes for Jingle IBB file transfer * fixes for repeated corrections filling up the database * switched to Last Message Correction v1.1 +* increase mam messages catchup (PAM) +* bug fixes -### Version 2.6.0 -* Introduce expert setting to perform channel discovery on local server instead of [search.jabber.network](https://search.jabber.network) -* Enable delivery check marks by default and remove setting -* Enable ‘Send button indicates status’ by default and remove setting -* Move Backup and Foreground Service settings to main screen +#### Version 2.3.3 +* fix missing send button -### Version 2.5.12 -* Jingle file transfer fixes -* Fixed OMEMO self healing (after backup restore) on servers w/o MAM +#### Version 2.3.2 +* fix shareWithActivity -### Version 2.5.11 -* Fixed crash on Android <5.0 +#### Version 2.3.1 +* bug fixes -### Version 2.5.10 -* Fixed crash on Xiaomi devices running Android 8.0 + 8.1 - -### Version 2.5.9 +#### Version 2.3.0 +* show name in quotes (PAM) +* introduce theme based on systems theme (PAM) +* increase default video quality (720p instead of 360p) (PAM) +* replace YouTube links with Invidious links (PAM) +* rework profile view (PAM) +* introduce app intro and some help screens (PAM) * fixed minor security issues -* Share XMPP uri from channel search by long pressing a result +* share XMPP uri from channel search by long pressing a result +* fixed OMEMO self healing (after backup restore) on servers w/o MAM +* introduce expert setting to perform channel discovery on local server instead of [search.jabber.network](https://search.jabber.network) +* introduce new orange theme color (PAM) +* bug fixes -### Version 2.5.8 -* fixed connection issues over Tor -* P2P file transfer (Jingle) now offers direct candidates -* Support XEP-0396: Jingle Encrypted Transports - OMEMO +#### Version 2.2.9 +* bug fixes -### Version 2.5.7 -* fixed crash when scanning QR codes on Android 6 and lower -* when sharing a message from and to Conversations insert it as quote - -### Version 2.5.6 +#### Version 2.2.8 +* stability improvements for group chats and channels +* allow backups to be restored from anywhere +* make short vibrate in open chat configurable and respect silent mode (PAM) * fixes for Jingle file transfer * fixed some rare crashes - -### Version 2.5.5 -* allow backups to be restored from anywhere +* when sharing a message from and to messenger insert it as quote +* find orphaned files and show them in the chat again instead of showing them deleted (PAM) +* introduce file uploads/downloads with queue (PAM) +* fixed connection issues over Tor +* P2P file transfer (Jingle) now offers direct candidates +* support XEP-0396: Jingle Encrypted Transports - OMEMO * bug fixes -### Version 2.5.4 -* stability improvements for group chats and channels +#### Version 2.2.7 +* fixing crashes -### Version 2.5.3 +#### Version 2.2.6 +* fixing connection issues +* fix broken updater + +#### Version 2.2.5 +* make backup compatible to Conversations (only works for Android >= 8) (PAM) +* bug fix + +#### Version 2.2.4 +* added channel search via search.jabbercat.org +* reworked onboarding screens +* warn when trying to enter domain address or channel address in Add Contact dialog +* set own OMEMO devices to inactive after not seeing them for 60 days. (was 7 days) * bug fixes for peer to peer file transfer (Jingle) * fixed server info for unlimited/unknown max file size - -### Version 2.5.2 +* make backup compatible to Conversations (PAM) +* performance improvements * bug fixes -### Version 2.5.1 -* minor bug fixes -* Set own OMEMO devices to inactive after not seeing them for 14 days. (was 7 days) +#### Version 2.2.3 +* bug fixes -### Version 2.5.0 -* Added channel search via search.jabbercat.org -* Reworked onboarding screens -* Warn when trying to enter domain address or channel address in Add Contact dialog +#### Version 2.2.2 +* add activity to define some important privacy settings on first start (PAM) +* add ability to delete account also from server (PAM) +* add ability to kick users from room and not just ban them (PAM) +* bug fixes -### Version 2.4.3 -* Fixed display of private messages sent from another client -* Fixed backup creation on long time installations +#### Version 2.2.1 +* use extra activity for enter name during registration (PAM) +* bug fixes -### Version 2.4.2 -* Fix image preview on older Android version - -### Version 2.4.1 -* Fixed crash in message view - -### Version 2.4.0 -* New Backup / Restore feature -* Clearly distinguish between (private) group chats and (public) channels -* Redesigned participants view for group chats and channels -* Redesigned create new contact/group chat/channel flow in Start Conversation screen - - -### Version 2.3.12 -* Fixed rare crash on start up -* Fixed avatar not being refreshed in group chats - -### Version 2.3.11 -* Support for Android 9 'message style' notifications +#### Version 2.2.0 +* add ability to set/edit nickname in own profile (PAM) +* add ability to configure autojoin flag in groupchat details (PAM) +* support for Android 9 'message style' notifications * OMEMO stability improvements -* Added ability to destroy group chats -* Do not show deleted files in media browser -* Added 'Keep Original' as video quality choice +* do not show deleted files in media browser +* added 'Keep Original' as image and video quality choice (PAM) +* fixed avatar not being refreshed in group chats +* make users avatars configurable (prefer either from xmpp or addressbook) (PAM) +* integrate last message deletion on remote devices (server and client need support for message correction) (PAM) +* clearly distinguish between (private) group chats and (public) channels +* redesigned participants view for group chats and channels +* redesigned create new contact/group chat/channel flow in Start Conversation screen +* reworked backup & restore +* use Ad-Hoc Commands to invite new users (PAM) +* show link preview in chat (PAM) +* bug fixes -### Version 2.3.10 -* lower minimum required Android version to 4.1 -* Synchronize group chat join/leaves across multiple clients -* Fixed sending PGP encrypted messages from quick reply - -### Version 2.3.9 -* OMEMO stability improvements -* Context menu when long pressing avatar in 1:1 chat - -### Version 2.3.8 -* make PEP avatars public to play nice with Prosody 0.11 -* Fixed re-sending failed files in group chats - -### Version 2.3.7 +#### Version 2.1.5 +* improved handling of bookmark nicks * long press on 'allow' or 'add back' snackbar to bring up 'reject' -* bug fixes for Android 9 - -### Version 2.3.6 -* Improved handling of bookmark nicks -* Show send PM menu entry in anonymous MUCs - -### Version 2.3.5 -* Fixed group chat mentions when nick ends in . (dot) -* Fixed Conversations not asking for permissions after direct share -* Fixed CVE-2018-18467 - -### Version 2.3.4 -* Fixed sending OMEMO files to ChatSecure - -### Version 2.3.3 -* Fixed connection issues with user@ip type JIDs - -### Version 2.3.2 -* Fixed OMEMO on Android 5.1 & 6.0 -* Added setting for video quality +* let the user forbid android from taking screenshots (PAM) +* make PEP avatars public to play nice with Prosody 0.11 +* fixed re-sending failed files in group chats +* OMEMO stability improvements +* context menu when long pressing avatar in 1:1 chat +* synchronize group chat join/leaves across multiple clients +* fixed sending PGP encrypted messages from quick reply * bug fixes -### Version 2.3.1 -* Stronger compression for video files -* Use SNI on STARTTLS to fix gtalk -* Fix Quiet Hours on Android 8+ -* Use Consistent Color Generation (XEP-0392) +#### Version 2.1.4 +* fix crash with xmpp uris + +#### Version 2.1.3 +* bring back gif support in internal mediaviewer +* fixed group chat mentions when nick ends in . (dot) +* fixed not asking for permissions after direct share +* fixed CVE-2018-18467 +* implement message deletion (PAM) +* bug fixes -### Version 2.3.0 -* Preview and ask for confirmation before sending media files -* View per conversation media files in contact and conference details screens -* Enable foreground service by default for Android 8 (notification can be disabled by long pressing it) -* Audio player: disable screen and switch to ear piece -* Support TLSv1.3 (ejabberd ≤ 18.06 is incompatible with openssl 1.1.1 - Update ejabberd or downgrade openssl if you get ›Stream opening error‹) +#### Version 2.1.2 +* fix crash with updater +#### Version 2.1.1 +* make quick actions/attachment choice configurable (PAM) +* add a hideable separate quick button for voice messages (PAM) +* switch between speaker/earpiece while playing audios/voice messages +* store bookmarks in PEP if server has ability to convert to old bookmarks +* show Jabber IDs from address book in address book +* preview and ask for confirmation before sending media files +* view per conversation media files in contact and conference details screens +* enable foreground service by default for Android 8 (notification can be disabled by long pressing it) +* reworked MediaViewer (PAM) +* support TLSv1.3 (ejabberd ≤ 18.06 is incompatible with openssl 1.1.1 - Update ejabberd or downgrade openssl if you get ›Stream opening error‹) +* add push messages for playstore versions +* bug fixes -### Version 2.2.9 -* Store bookmarks in PEP if server has ability to convert to old bookmarks -* Show Jabber IDs from address book in Start Conversation screen +#### Version 2.1.0 +* use group chat name as primary identifier +* upload group chat avatar on compatible servers +* show group name and subject in group chat details +* UI improvements +* introduce Expert Setting to enable direct search +* introduce Paste As Quote on Android 6+ +* attempt to delete broken bundles from PEP +* offer Paste as quote for HTML content +* use SNI on STARTTLS to fix gtalk +* use Consistent Color Generation (XEP-0392) +* bug fixes -### Version 2.2.8 -* fixed regression that broke XMPP uris +#### Version 2.0.2 +* bug fixes especially for Android 4 -### Version 2.2.7 -* stability improvements - -### Version 2.2.6 -* support old MAM version to work with Prosody - -### Version 2.2.5 -* Persist MUC avatar across restarts / show in bookmarks -* Offer Paste as quote for HTML content - -### Version 2.2.4 -* Use group chat name as primary identifier -* Show group name and subject in group chat details -* Upload group chat avatar on compatible servers - -### Version 2.2.3 -* Introduce Expert Setting to enable direct search -* Introduce Paste As Quote on Android 6+ -* Fixed issues with HTTP Upload - -### Version 2.2.2 -* Fixed connection problems with TLS1.3 servers -* Attempt to delete broken bundles from PEP -* Use FCM instead of GCM - -### Version 2.2.1 +#### Version 2.0.1 * improved recording quality -* load map tiles over Tor if enabled +* let the user select a public server for account creation (PAM) +* add possibility to de-/activate accounts in multi account mode +* bug fixes -### Version 2.2.0 -* Integrate Voice Recorder -* Integrate Share Location -* Added ability to search messages +#### Version 2.0.0 +* highlight irregular unicode code blocks in Jabber IDs +* integrate QR code scanner (requires camera permission) +* removed support for customizable resources +* reworked message search +* added splash screen at startup +* integrate dark theme +* keep OTR, but without further development and support, just for compatibility (PAM) +* added configurable font size +* added global OMEMO preference +* added scroll to bottom button +* added contact button as android widget +* only mark visible messages as read +* a lot of bug fixes -### Version 2.1.4 +#### Version 1.22.1 +* show extended/TOR connection options in expert settings * bug fixes -### Version 2.1.3 -* Do not process stanzas with invalid JIDs +#### Version 1.22.0 +* integrated expert option to enable multiple account (PAM) +* some UI improvements +* improved MAM support +* bug fixes -### Version 2.1.2 -* Fixed avatars not being displayed on new installs - -### Version 2.1.1 -* Improved start up performance +#### Version 1.21.3 +* small self messages improvement +* small notification improvement * bug fixes -### Version 2.1.0 -* Added configurable font size -* Added global OMEMO preference -* Added scroll to bottom button -* Only mark visible messages as read - - -### Version 2.0.0 -* OMEMO by default for everything but public group chats -* Integrate QR code scanner (requires camera permission) -* Removed support for OTR -* Removed support for customizable resources -* Removed slide out panel for conversation overview -* Add ability to change status message -* Highlight irregular unicode code blocks in Jabber IDs -* Conversations now requires Android 4.4+ - -### Version 1.23.8 +#### Version 1.21.2 * bug fixes -### Version 1.23.7 -* Improved MAM support + bug fixes - -### Version 1.23.6 -* Fixed crash on receiving invalid HTTP slot response - -### Version 1.23.5 -* improved self chat - -### Version 1.23.4 -* keep screen on while playing audio -* send delivery receipts after MAM catch-up +#### Version 1.21.1 +* don't use integrated updater if Messenger is installed from F-Droid (PAM) +* check if app installs from unknown sources are allowed, if not open settings to allow this (PAM) +* show hint in chatview if private message is activated (PAM) +* send delivery receipts after MAM catchup * reduce number of wake locks +* add possibility to destroy group chats (PAM) +* show progress dialog while downloading update with internal updater (PAM) +* implemented message search (PAM) +* improved self chat +* bug fixes -### Version 1.23.3 -* Fixed OMEMO device list not being announced +#### Version 1.21.0 +* replaced google maps location service with open street map services via leaflet (PAM) +* let screen on while playing audio files (PAM) +* add Turkish translations +* bug fixes -### Version 1.23.2 -* Removed NFC support -* upload Avatars as JPEG -* reduce APK size - -### Version 1.23.1 -* Show icon instead of image preview in conversation overview +#### Version 1.20.3 +* show icon instead of image preview in conversation overview * fixed loop when trying to decrypt with YubiKey +* Removed NFC support +* Fixed OMEMO device list no being announced +* bug fixes -### Version 1.23.0 -* Support for read markers in private, non-anonymous group chats +#### Version 1.20.2 +* bug fixes -### Version 1.22.1 -* Disable swipe to left to end conversation -* Fixed 'No permission to access …' when opening files shared from the SD card -* Always open URLs in new tab +#### Version 1.20.1 +* bug fixes -### Version 1.22.0 -* Text markup *bold*, _italic_,`monospace` and ~strikethrough~ -* Use same emoji style on all Android versions -* Display emojis slightly larger within continuous text +#### Version 1.20.0 +* reworked AppUpdater and show a notification if there is an update available (PAM) +* some UI and performance improvements +* add french and spanish translations +* text markup *bold*, _italic_,`monospace` and ~strikethrough~ +* fixed 'No permission to access …' when opening files shared from the SD card +* always open URLs in new tab +* bring back quick share (default off) and return to previous app after sharing +* send and show read markers in private, non-anonymous groups +* warn if chat is unencrypted and switch to OMEMO if available after pressing OK (warning can be disabled in settings) (PAM) +* support sending and receiving opus file +* bug fixes -### Version 1.21.0 -* Inline player for audio messages -* Stronger compression for long videos -* Long press the 'add back' button to show block menu -### Version 1.20.1 +#### Version 1.19.2 +* reworked inline player for audio messages +* long press the 'add back' button to show block menu +* foregroundservice can be switched off +* bug fixes + +#### Version 1.19.1 * fixed OTR encrypted file transfer - -### Version 1.20.0 -* presence subscription no longer required for OMEMO on compatible servers -* display emoji-only messages slightly larger - -### Version 1.19.5 -* fixed connection loop on Android <4.4 - -### Version 1.19.4 -* work around for OpensFire’s self signed certs -* use VPN’s DNS servers first - -### Version 1.19.3 -* Do not create foreground service when all accounts are disabled * bug fixes -### Version 1.19.2 -* bug fixes - -### Version 1.19.1 -* Made DNSSEC hostname validation opt-in - -### Version 1.19.0 +#### Version 1.19.0 * Added 'App Shortcuts' to quickly access frequent contacts * Use DNSSEC to verify hostname instead of domain in certificate * Setting to enable Heads-up notifications -* Added date separators in message view +* Made DNSSEC hostname validation opt-in +* work around for OpensFire's self signed certs +* use VPN's DNS servers first +* fixed connection loop on Android < 4.4 +* presence subscription no longer required for OMEMO on compatible servers +* bug fixes -### Version 1.18.5 + +#### Version 1.18.2 * colorize send button only after history is caught up * improved MAM catchup strategy +* bug fixes -### Version 1.18.4 +#### Version 1.18.1 +* limited GPG encryption for MUC offline members +* show extended connection settings for open version in export settings +* fixed landscape layout problems for tablets * fixed UI freezes during connection timeout * fixed notification sound playing twice * fixed conversations being marked as read * removed 'copy text' in favor of 'select text' and 'share with' +* bug fixes -### Version 1.18.3 -* limited GPG encryption for MUC offline members - -### Version 1.18.2 +#### Version 1.18.0 +* enable OTR and OpenPGP again +* fix bug with updater on devices < SDK 21 (PAM) +* Show colored contact names for their presence status (via settings) +* treat URL as file if URL is in oob or contains key * added support for Android Auto * fixed HTTP Download over Tor * work around for nimbuzz.com MUCs - -### Version 1.18.1 * bug fixes -### Version 1.18.0 -* Conversations <1.16.0 will be unable to receive OMEMO encrypted messages -* OMEMO: put auth tag into key (verify auth tag as well) -* offer to block entire domain in message from stranger snackbar -* treat URL as file if URL is in oob or contains key - -### Version 1.17.1 +#### Version 1.17.1 +* grey out offline contacts in StartConversation (PAM) +* change emoji library which supports newer emojis +* change avatar images to circles * Switch Aztec to QR for faster scans -* Fixed unread counter for image messages +* make automatic fullscreen media rotation configurable (PAM) +* open version can use tor networks +* make image compression configurable (PAM) +* show read/received markers in chatlist (PAM) +* OMEMO: put auth tag into key (verify auth tag as well) +* offer to block entire domain in message from stanger snackbar +* bug fixes -### Version 1.17.0 -* Do not notify for messages from strangers by default -* Blocking a JID closes the corresponding conversation -* Show message sender in conversation overview -* Show unread counter for every conversation -* Send typing notifications in private, non-anonymous MUCs +#### Version 1.17.0 +* add date bubbles in chat view (PAM) +* show last used resource in contact details +* make brightness overwrite in image/video viewer configurable (PAM) +* make video resolution configurable (PAM) +* do not notify for messages from strangers by default +* blocking a JID closes the corresponding conversation +* show message sender in conversation overview * Support for the latest MAM namespace * Icons for attach menu - -### Version 1.16.2 -* change mam catchup strategie. support mam:1 +* send typing notifications in private, non-anonymous MUCs +* change media directory names (PAM) * bug fixes +#### Version 1.16.5 +* show read marker in whisper messages +* bug fixes -### Version 1.16.1 +#### Version 1.16.4 +* show failed file uploads in chatlist (PAM) +* resend failed file uploads automatically (PAM) +* preview files in chatlist +* move (un)mute settings to contact-/conference details * UI performance fixes * bug fixes -### Version 1.16.0 -* configurable client side message retention period -* compress videos before sending them - -### Version 1.15.5 -* show nick as bold text when mentioned in conference +#### Version 1.16.3 +* don't use jingle as fallback if file is too big * bug fixes -### Version 1.15.4 +#### Version 1.16.2 +* show app name and version from shared apk files +* add ability to compress videos with 720p and not only bigger ones + +#### Version 1.16.1 * bug fixes -### Version 1.15.3 -* show offline contacts in MUC as grayed-out -* don't transcode gifs. add overlay indication to gifs +#### Version 1.16.0 +* show unread messages in chatlist (PAM) +* increase image size to 4k UHD (PAM) +* add support for GIF files +* reworked video compression (PAM) +* reworked app updater * bug fixes -### Version 1.15.2 -* bug fixes - -### Version 1.15.1 +#### Version 1.15.4 +* improve video compression quality (PAM) * support for POSH (RFC7711) * support for quoting messages (via select text) -* verified messages show shield icon. unverified messages show lock - -### Version 1.15.0 -* New [Blind Trust Before Verification](https://gultsch.de/trust.html) mode -* Easily share Barcode and XMPP uri from Account details -* Automatically deactivate own devices after 7 day of inactivity -* Improvements fo doze/push mode +* verified messages show shield icon; unverified messages show lock * bug fixes -### Version 1.14.9 -* warn in account details when data saver is enabled -* automatically enable foreground service after detecting frequent restarts +#### Version 1.15.3 +* new [Blind Trust Before Verification](https://gultsch.de/trust.html) mode +* easily share Barcode and XMPP uri from Account details * bug fixes -### Version 1.14.8 +#### Version 1.15.2 +* automatically remove old OMEMO devices after 7 days * bug fixes -### Version 1.14.7 +#### Version 1.15.1 +* introduces preference option to choose if videos should be compressed (always, automatically, never) +* bug fixes + +#### Version 1.15.0 +* make OMEMO working with other clients +* make OMEMO encryption standard for 1:1 chats as default +* start navigation app directly from show location activity (PAM) +* show map preview on shared locations (PAM) +* show contacts name on shared VCARDs (PAM) +* send text directly via ShareWithActivity +* bug fixes + +#### Version 1.14.5 * error message accessible via context menu for failed messages * don't include pgp signature in anonymous mucs * bug fixes -### Version 1.14.6 +#### Version 1.14.4 * make error notification dismissable * bug fixes - -### Version 1.14.5 -* expert setting to delete OMEMO identities -* bug fixes - -### Version 1.14.4 -* bug fixes - -### Version 1.14.3 +#### Version 1.14.3 +* set different auto-download-sizes for mobile, roaming and WiFi connections (PAM) +* add ability to report errors and bugs directly from menu (PAM) * XEP-0377: Spam Reporting * fix rare start up crashes +* bug fixes -### Version 1.14.2 +#### Version 1.14.2 * support ANONYMOUS SASL +* add custom Emojis +* scroll long actionbar titles +* some performance improvements +* some video compression improvements (PAM) * bug fixes -### Version 1.14.1 -* Press lock icon to see why OMEMO is deactivated +#### Version 1.14.1 +* fix crash on taking photos directly within the app + +#### Version 1.14.0 +* compress videos > 5 MB before sending +* improvments for Android N +* quick reply to notifications on Android N +* don't download avatars and files when data saver is on * bug fixes -### Version 1.14.0 -* Improvments for N -* Quick Reply to Notifications on N -* Don't download avatars and files when data saver is on +#### Version 1.13.9 +* add icons for files in chat view +* reworked backup service to automatically backup database encrypted to local storage at 4 am each day (PAM) +* make human readable log export optional (PAM) * bug fixes -### Version 1.13.9 +#### Version 1.13.8 * bug fixes -### Version 1.13.8 -* show identities instead of resources in selection dialog -* allow TLS direct connect when port is set to 5223 +#### Version 1.13.7 +* improved video thumbnails in chatlist * bug fixes -### Version 1.13.7 -* bug fixes - -### Version 1.13.6 -* thumbnails for videos -* bug fixes - -### Version 1.13.5 -* bug fixes - -### Version 1.13.4 +#### Version 1.13.6 +* share image/video directly from fullscreen view (PAM) +* show online status in foreground service * support jingle ft:4 * show contact as DND if one resource is * bug fixes -### Version 1.13.3 +#### Version 1.13.5 +* add image preview before sending single images directly (PAM) +* add vibrate notification when app is open (PAM) +* hide actionbar in fullscreen image/video view (PAM) * bug fixes -### Version 1.13.2 +#### Version 1.13.4 * new PGP decryption logic * bug fixes -### Version 1.13.1 -* changed some colors in dark theme -* fixed fall-back message for OMEMO - -### Version 1.13.0 -* configurable dark theme -* opt-in to share Last User Interaction - -### Version 1.12.9 -* make grace period configurable - -### Version 1.12.8 -* more bug fixes :-( - -### Version 1.12.7 +#### Version 1.13.3 +* new permission check and request at startup (PAM) * bug fixes -### Version 1.12.6 +#### Version 1.13.2 +* refactored lastseen info * bug fixes -### Version 1.12.5 +#### Version 1.13.1 +* bug fixes + +#### Version 1.13.0 +* changed applicationId (PAM) +* play videos directly without touching play button (PAM) +* add database importer from local storage as backup (PAM) +* changed files directories and names (PAM) +* bug fixes + +#### Version 1.12.6 +* bug fixes +* add database exporter to local storage as backup (PAM) + +#### Version 1.12.5 +* bug fixes + +#### Version 1.12.4 * new create conference dialog * show first unread message on top * show geo uri as links * circumvent long message DOS +* integrate simple videoplayer and image viewer (PAM) -### Version 1.12.4 -* show offline members in conference (needs server support) +#### Version 1.12.3 +* show offline members in conferences * various bug fixes -### Version 1.12.3 +#### Version 1.12.2 * make omemo default when all resources support it * show presence of other resources as template * start typing in StartConversationsActivity to search +* show addresses in locations (PAM) +* show video previews in chats * various bug fixes and improvements - -### Version 1.12.2 * fixed pgp presence signing -### Version 1.12.1 +#### Version 1.12.1 +* expert setting to modify presence +* added simple audio player (PAM) +* added audio recorder (PAM) +* added location services (PAM) +* changed theme color from green to blue (PAM) * small bug fixes -### Version 1.12.0 -* new welcome screen that makes it easier to register account -* expert setting to modify presence +#### Version 1.12.0 +* added welcome screen for first start +* use IP/Port instead of query DNS to improve connection performance +* UI improvements +* bug fixes -### Version 1.11.7 +#### Version 1.11.7 * Share xmpp uri from conference details * add setting to allow quick sharing +* use material design icons for android < lollipop +* make foreground service always activated (PAM) +* disable account deactivation +* UI improvements * various bug fixes -### Version 1.11.6 +#### Version 1.11.6 * added preference to disable notification light * various bug fixes -### Version 1.11.5 +#### Version 1.11.5 * check file ownership to not accidentally share private files -### Version 1.11.4 +#### Version 1.11.4 * fixed a bug where contacts are shown as offline * improved broken PEP detection - -### Version 1.11.3 * check maximum file size when using HTTP Upload * properly calculate caps hash +* some UI improvements -### Version 1.11.2 +#### Version 1.11.3 * only add image files to media scanner * allow to delete files * various bug fixes -### Version 1.11.1 +#### Version 1.11.2 +* added voice recorder to plugins +* bug fixes + +#### Version 1.11.1 * fixed some bugs when sharing files with Conversations -### Version 1.11.0 +#### Version 1.11.0 * OMEMO encrypted conferences +* resend failed filetransfers automatically +* Support for XEP-0357: Push Notifications +* disable support for only one account -### Version 1.10.1 -* made message correction opt-in +#### Version 1.10.1 +* support only one account * various bug fixes -### Version 1.10.0 -* Support for XEP-0357: Push Notifications +#### Version 1.10.0 * Support for XEP-0308: Last Message Correction -* introduced build flavors to make dependence on play-services optional -### Version 1.9.4 +#### Version 1.9.4 * prevent cleared Conversations from reloading history with MAM * various MAM fixes -### Version 1.9.3 +#### Version 1.9.3 * expert setting that enables host and port configuration * expert setting opt-out of bookmark autojoin handling * offer to rejoin a conference after server sent unavailable -* internal rewrites +* internal rewrites and optimizations -### Version 1.9.2 +#### Version 1.9.2 * prevent startup crash on Sailfish OS * minor bug fixes +* removed contact-/conferece-details button (PAM) +* touch contact name or conference name in action bar opens contact-/conference-details (PAM) -### Version 1.9.1 +#### Version 1.9.1 * minor bug fixes incl. a workaround for nimbuzz.com -### Version 1.9.0 +#### Version 1.9.0 * Per conference notification settings * Let user decide whether to compress pictures * Support for XEP-0368 * Ask user to exclude Conversations from battery optimizations -### Version 1.8.4 +#### Version 1.8.4 * prompt to trust own OMEMO devices * fixed rotation issues in avatar publication * invite non-contact JIDs to conferences -### Version 1.8.3 +#### Version 1.8.3 * brought text selection back +* hide settings, manage accounts and updater in all menus except in the main activity +* bug fixes -### Version 1.8.2 +#### Version 1.8.2 * fixed stuck at 'connecting...' bug * make message box behave correctly with multiple links +* bug fixes -### Version 1.8.1 +#### Version 1.8.1 * enabled direct share on Android 6.0 * ask for permissions on Android 6.0 * notify on MAM catchup messages * bug fixes -### Version 1.8.0 +#### Version 1.8.0 * TOR/ORBOT support in advanced settings * show vcard avatars of participants in a conference - -### Version 1.7.3 +* Own contact picture in tile for conferences with only one other occupant +* added button to updater dialog to show full changelog +* added plugin loader to settings * fixed PGP encrypted file transfer -* fixed repeating messages in slack conferences +* bug fixes -### Version 1.7.2 -* decode PGP messages in background +#### Versrion 1.7.3 +* changed app name from Conversations to Pix-Art Messenger (PAM) +* changed chat background to light yellow +* added own name for sent locations (PAM) -### Version 1.7.1 +#### Version 1.7.2 +* let users crop their avatars +* bug fixes + +#### Versrion 1.7.1 * performance improvements when opening a conversation +* bug fixes -### Version 1.7.0 +#### Version 1.7.0 +* redownload deleted files from HTTP hosts +* bug fixes +* show lastseen info as subitle in single chats + +#### Version 1.6.13 +* bugfixes +* fetching MUC history via MAM +* Expert setting to automatically set presence +* show client-to-client encryption in chatview +* added changelog to AppUpdater dialog +* delete old version files in download folder on updating +* use standard namespace for file transfers * CAPTCHA support * SASL EXTERNAL (client certifiates) -* fetching MUC history via MAM -* redownload deleted files from HTTP hosts -* Expert setting to automatically set presence -* bug fixes -### Version 1.6.11 +#### Version 1.6.12 +* added blue tick as read indicator * tab completion for MUC nicks -* history export +* history export to SD card * bug fixes -### Version 1.6.10 +#### Version 1.6.11 +* optimized app updater and increased app update check period to once a day + +#### Version 1.6.10 * fixed facebook login * fixed bug with ejabberd mam -* use official HTTP File Upload namespace +* run app updater automatically -### Version 1.6.9 +#### Version 1.6.9 * basic keyboard support +* bug fixes +* update checker with in app version updates -### Version 1.6.8 +#### Version 1.6.8 * reworked 'enter is send' setting * reworked DNS server discovery on lolipop devices * various bug fixes -### Version 1.6.7 +#### Version 1.6.7 * bug fixes -### Version 1.6.6 +#### Version 1.6.6 * best 1.6 release yet -### Version 1.6.5 +#### Version 1.6.5 * more OMEMO fixes -### Version 1.6.4 +#### Version 1.6.4 * setting to enable white chat bubbles * limit OMEMO key publish attempts to work around broken PEP * various bug fixes -### Version 1.6.3 +#### Version 1.6.3 * bug fixes -### Version 1.6.2 +#### Version 1.6.2 * fixed issues with connection time out when server does not support ping -### Version 1.6.1 +#### Version 1.6.1 * fixed crashes -### Version 1.6.0 +#### Version 1.6.0 * new multi-end-to-multi-end encryption method -* redesigned chat bubbles * show unexpected encryption changes as red chat bubbles * always notify in private/non-anonymous conferences +* some bugfixes +* hard coded pix-art.de as standard server -### Version 1.5.1 +#### Version 1.5.2 +* added new message bubbles +* added subtitles to chatviews in ActionBar to display typing info in single chats and participant names in conferences +* some bug fixes + +#### Version 1.5.1 * fixed rare crashes * improved otr support +* moved typing info to ActionBar -### Version 1.5.0 -* upload files to HTTP host and share them in MUCs. requires new [HttpUploadComponent](https://github.com/siacs/HttpUploadComponent) on server side +#### Version 1.5.0 +* new file transfer mode to offline contacts and conferences for files smaller than 20 MB: upload files to HTTP host and share them in MUCs. requires new [HttpUploadComponent](https://github.com/siacs/HttpUploadComponent) on server side +* default image format is JPEG +* small layout modifications with bigger avatars +* show contacts name in locations shared in conferences -### Version 1.4.5 +#### Version 1.4.5 * fixes to message parser to not display some ejabberd muc status messages -### Version 1.4.4 +#### Version 1.4.4 * added unread count badges on supported devices * rewrote message parser -### Version 1.4.0 +#### Version 1.4.0 * send button turns into quick action button to offer faster access to take photo, send location or record audio * visually separate merged messages * faster reconnects of failed accounts after network switches * r/o vcard avatars for contacts * various bug fixes -### Version 1.3.0 +#### Version 1.3.0 * swipe conversations to end them * quickly enable / disable account via slider * share multiple images at once @@ -705,32 +741,32 @@ * mlink compatibility * bug fixes -### Version 1.2.0 +#### Version 1.2.0 * Send current location. (requires [plugin](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation)) * Invite multiple contacts at once * performance improvements * bug fixes -### Version 1.1.0 +#### Version 1.1.0 * Typing notifications (must be turned on in settings) * Various UI performance improvements * bug fixes -### Version 1.0.4 +#### Version 1.0.4 * load avatars asynchronously on start up * support for XEP-0092: Software Version -### Version 1.0.3 +#### Version 1.0.3 * load messages asynchronously on start up * bug fixes -### Version 1.0.2 +#### Version 1.0.2 * skipped -### Version 1.0.1 +#### Version 1.0.1 * accept more ciphers -### Version 1.0 +#### Version 1.0 * MUC controls (Affiliaton changes) * Added download button to notification * Added check box to hide offline contacts @@ -738,7 +774,7 @@ * Improved security * bug fixes + code clean up -### Version 0.10 +#### Version 0.10 * Support for Message Archive Management * Dynamically load message history * Ability to block contacts @@ -748,16 +784,16 @@ * quiet hours * fixed connection issues on ipv6 servers -### Version 0.9.3 +#### Version 0.9.3 * bug fixes -### Version 0.9.2 +#### Version 0.9.2 * more bug fixes -### Version 0.9.1 +#### Version 0.9.1 * bug fixes including some that caused Conversations to crash on start -### Version 0.9 +#### Version 0.9 * arbitrary file transfer * more options to verify OTR (SMP, QR Codes, NFC) * ability to create instant conferences @@ -766,44 +802,44 @@ * added SCRAM-SHA1 login method * bug fixes -### Version 0.8.4 +#### Version 0.8.4 * bug fixes -### Version 0.8.3 +#### Version 0.8.3 * increased UI performance * fixed rotation bugs -### Version 0.8.2 +#### Version 0.8.2 * Share contacts via QR codes or NFC * Slightly improved UI * minor bug fixes -### Version 0.8.1 +#### Version 0.8.1 * minor bug fixes -### Version 0.8 +#### Version 0.8 * Download HTTP images * Show avatars in MUC tiles * Disabled SSLv3 * Performance improvements * bug fixes -### Version 0.7.3 +#### Version 0.7.3 * revised tablet ui * internal rewrites * bug fixes -### Version 0.7.2 +#### Version 0.7.2 * show full timestamp in messages * brought back option to use JID to identify conferences * optionally request delivery receipts (expert option) * more languages * bug fixes -### Version 0.7.1 +#### Version 0.7.1 * Optionally use send button as status indicator -### Version 0.7 +#### Version 0.7 * Ability to disable notifications for single conversations * Merge messages in chat bubbles * Fixes for OpenPGP and OTR (please republish your public key) @@ -812,26 +848,26 @@ * Configurable font size * Expert options for encryption -### Version 0.6 +#### Version 0.6 * Support for server side avatars * save images in gallery * show contact name and picture in non-anonymous conferences * reworked account creation * various bug fixes -### Version 0.5.2 +#### Version 0.5.2 * minor bug fixes -### Version 0.5.1 +#### Version 0.5.1 * couple of small bug fixes that have been missed in 0.5 * complete translations for Swedish, Dutch, German, Spanish, French, Russian -### Version 0.5 +#### Version 0.5 * UI overhaul * MUC / Conference bookmarks * A lot of bug fixes -### Version 0.4 +#### Version 0.4 * OTR file encryption * keep OTR messages and files on device until both parties or online at the same time * XEP-0333. Mark whether the other party has read your messages @@ -840,7 +876,7 @@ * Infinit history scrolling * Mark the last used presence in presence selection dialog -### Version 0.3 +#### Version 0.3 * Mostly bug fixes and internal rewrites * Touch contact picture in conference to highlight * Long press on received image to share @@ -848,27 +884,27 @@ * improved issues with occasional message lost * experimental conference encryption. (see FAQ) -### Version 0.2.3 +#### Version 0.2.3 * regression fix with receiving encrypted images -### Version 0.2.2 +#### Version 0.2.2 * Ability to take photos directly * Improved openPGP offline handling * Various bug fixes * Updated Translations -### Version 0.2.1 +#### Version 0.2.1 * Various bug fixes * Updated Translations -### Version 0.2 +#### Version 0.2 * Image file transfer * Better integration with OpenKeychain (PGP encryption) * Nicer conversation tiles for conferences * Ability to clear conversation history * A lot of bug fixes and code clean up -### Version 0.1.3 +#### Version 0.1.3 * Switched to minidns library to resolve SRV records * Faster DNS in some cases * Enabled stream compression @@ -876,12 +912,12 @@ * Various bug fixes involving message notifications * Added support for DIGEST-MD5 auth -### Version 0.1.2 +#### Version 0.1.2 * Various bug fixes relating to conferences * Further DNS lookup improvements -### Version 0.1.1 +#### Version 0.1.1 * Fixed the 'server not found' bug -### Version 0.1 +#### Version 0.1 * Initial release diff --git a/README-en.md b/README-en.md new file mode 100644 index 000000000..edca8e426 --- /dev/null +++ b/README-en.md @@ -0,0 +1,140 @@ +![logo](/art/icon.png) +# Pix-Art Messenger [![CircleCI](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master.svg?style=shield)](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master) + +🇩🇪 Deutsche Version der Readme hier verfügbar. + +Pix-Art Messenger is a fork of [Conversations](https://github.com/siacs/Conversations). +The changes aim to improve usability and ease transition from pre-installed and other widespread messengers. Here are some screenshots: + + + +(Images used were taken from http://freestockgallery.de) + +## Download +Pix-Art is available for install in the F-Droid and Google-Play stores. +Alternatively release and beta-release APKs are available via github: [Releases](https://github.com/kriztan/Pix-Art-Messenger/releases/latest) + +Get it on Google Play Get it on F-Droid + +#### Pix-Art-Messenger nightly and beta + +##### F-Droid +Scan the QR-Code below and add it to your F-Droid repositories. + + + +#### Google Play +Google-Users can join the Google-Play beta program: +[Pix-Art Messenger beta](https://play.google.com/apps/testing/de.pixart.messenger) + + +## Social Media +Pix-Art Messenger on Mastodon (German) + +There are also English and German speaking XMPP-MUCs focusing on support and development of the Pix-Art Messenger. + +If you are interested in the development of the messenger, here is a MUC for you (English and German speaking): + +Development-Chat: [development@room.pix-art.de](https://jabber.pix-art.de/j/development@room.pix-art.de?join) +[![Users in muc](https://inverse.chat/badge.svg?room=development@room.pix-art.de)](https://jabber.pix-art.de/j/development@room.pix-art.de?join) + + +There also is an Support-MUC where you can ask questions and get help with issues you may encounter, see further below for details. + + +## How can I support translations ? +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/pix-art-messenger/localized.svg)](https://crowdin.com/project/pix-art-messenger) + +Translation of in-app text is organised via [crowdin.com](https://crowdin.com/project/pix-art-messenger). You can add new languages as locales and add and edit translations already existing. + +[Here is the project page on crowdin.com](https://crowdin.com/project/pix-art-messenger/invite?d=75l6j4k6k6k523f4j4m4e473u663d3m4p4t4q4) + +## Help! I've encountered issues! +The easiest way to get some help is to join our support-MUC (both English and German). + +Support-Chat invite link: [support@room.pix-art.de](https://jabber.pix-art.de/j/support@room.pix-art.de?join) +[![Users in muc](https://inverse.chat/badge.svg?room=support@room.pix-art.de)](https://jabber.pix-art.de/j/support@room.pix-art.de?join) + +Or scan this QR-Code: + + + +If we can't fix your problem there, you can open an issue here on github, detailing your problem, how to reproduce it and provide logs. See instructions below on how to create log files. + + + +### How to create debug logs? (adb) + +#### GNU/Linux, OSX and other Unix-like systems: + +1. First install The **A**ndroid **D**ebugging **B**ridge, if not already present. + ###### Ubuntu / Linux Mint + ``` + sudo apt-get update + sudo apt-get install android-tools-adb + ``` + ###### openSUSE 42.2 and 42.3 + ``` + sudo zypper ref + sudo zypper install android-tools + ``` + ###### openSUSE Tumbleweed + here you need to add the following repo (e.g. via Yast): + http://download.opensuse.org/repositories/hardware/openSUSE_Tumbleweed/ + + alternatively you have the option to use the `1 Click installer` + https://software.opensuse.org/package/android-tools + ###### other systems + install adb using a method appropriate for your system + +2. Now open a terminal in a directory of you're choice, or navigate to the directory using `cd`. + +3. Follow steps [6] to [10] of the Windows instructions. + +4. Start outputting your log to a file on your computer. We will be using `logcat.txt`. Enter: + ``` + $ adb -d logcat -v time | grep -i Pix-Art > logcat.txt + ``` + +5. Follow the remaining steps [12] and [13] of the Windows instructions. + + +#### Windows: + +1. Download Google's SDK-platform tools for your operating system: + + https://developer.android.com/studio/releases/platform-tools.html +2. In case they were not included: You also need the ADB_drivers for your version of Microsoft Windows: + + https://developer.android.com/studio/run/win-usb.html +3. Extract the zip-archive (e.g. to `C:\ADB\`) +4. Open the command line (CMD) using the start menu: Start > Execute: cmd +5. Navigate to the directory you extracted the zip to as following. We will be using `C:\ADB\` + ``` + c: + cd ADB + ``` +6. On your smartphone open the settings and search for the item `Developer Options`. If this option is not already present on your phone you will need to unlock it beforehand. To do this navigate to `Settings > About phone`, there locate `Build number` (or similar) and tap it 7-times in succession. You should now see a notification, that you are now a developer. Congrats, `Developer Options` are now available in your settings menu. +7. Inside `Developer Options` search activate the setting `USB-Debugging` (sometimes just called `Android Debugging`). +8. Connect your phone to your computer via USB cable. The necessary drivers should now be downloaded and installed if not already present. On Windows all necessary drivers should be downloaded automatically if you followed step [2] beforehand. On most GNU/Linux systems no additional action is required. +9. If everything worked out, you can now return to the command line and test if your device is being recognised. Enter `adb devices -l`; you should see output similar to: + ``` + > adb devices -l + List of devices attached + * daemon not running. starting it now on port 5037 * + * daemon started successfully * + 042111560169500303f4 unauthorized + ``` +10. If your devices is labelled as `unautorized`, you must first accept a prompt on your phone asking if debugging over USB should be allowed. When rerunning `adb devices` you should now see: + ``` + > adb devices + List of devices attached + 042111560169500303f4 device + ``` +11. Start outputting your log to a file on your computer. We will be using `logcat.txt` in `C:\ADB\`. Just enter the following (without `> ` into the command line): + ``` + > adb -d logcat -v time | FINDSTR Pix-Art > logcat.txt + ``` +12. Now reproduce the issue encountered. + +13. Stop logging. Now take a close look at your log file and remove any personal and private information you may find before sending it together with a detailed description of your issue, instructions on how to reproduce to me. You can use GitHub's issue tracker: [Issues](https://github.com/kriztan/Pix-Art-Messenger/issues) diff --git a/README.md b/README.md index b77a577a5..b24ca9eb6 100644 --- a/README.md +++ b/README.md @@ -1,468 +1,115 @@ -

Conversations

+![logo](/art/icon.png) +# Pix-Art Messenger [![CircleCI](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master.svg?style=shield)](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master) -

Conversations: the very last word in instant messaging

+🇬🇧🇺🇸… English Readme version available here -

- - chat on our conference room - - - build status - -

+Pix-Art Messenger ist eine Kopie der offiziellen Android-App [Conversations](https://github.com/siacs/Conversations) mit einigen Änderungen, insbesondere zur Verbesserung der Benutzerfreundlichkeit, um den Umstieg von oftmals vorinstallierten Messengern zu erleichtern. Die folgenden Bilder geben erste Eindrücke der App: -

- - Google Play - -

+ -![screenshots](https://raw.githubusercontent.com/inputmice/Conversations/master/screenshots.png) +(Bilder wurden über http://freestockgallery.de bezogen) -## Design principles +Download ist hier möglich: -* Be as beautiful and easy to use as possible without sacrificing security or - privacy -* Rely on existing, well established protocols (XMPP) -* Do not require a Google Account or specifically Google Cloud Messaging (GCM) +Jeztz bei Google Play Jetzt bei F-Droid -## Features +Alternativ kannst du den Messenger auch direkt hier von GitHub unter [Releases](https://github.com/kriztan/Pix-Art-Messenger/releases/latest) herunterladen. -* End-to-end encryption with [OMEMO](http://conversations.im/omemo/) or [OpenPGP](http://openpgp.org/about/) -* Send and receive images as well as other kind of files -* Make audio and video calls -* Share your location -* Send voice messages -* Indication when your contact has read your message -* Intuitive UI that follows Android Design guidelines -* Pictures / Avatars for your Contacts -* Synchronizes with desktop client -* Conferences (with support for bookmarks) -* Address book integration -* Multiple accounts / unified inbox -* Very low impact on battery life +Pix-Art Messenger bei Mastodon +#### Pix-Art-Messenger nightly bzw. beta -### XMPP Features +Scanne den QR-Code und füge es zu deinen F-Droid Quellen hinzu: -Conversations works with every XMPP server out there. However XMPP is an -extensible protocol. These extensions are standardized as well in so called -XEP's. Conversations supports a couple of these to make the overall user -experience better. There is a chance that your current XMPP server does not -support these extensions; therefore to get the most out of Conversations you -should consider either switching to an XMPP server that does or — even better — -run your own XMPP server for you and your friends. These XEP's are: + -* [XEP-0065: SOCKS5 Bytestreams](http://xmpp.org/extensions/xep-0065.html) (or mod_proxy65). Will be used to transfer - files if both parties are behind a firewall (NAT). -* [XEP-0163: Personal Eventing Protocol](http://xmpp.org/extensions/xep-0163.html) for avatars and OMEMO. -* [XEP-0191: Blocking command](http://xmpp.org/extensions/xep-0191.html) lets you blacklist spammers or block contacts - without removing them from your roster. -* [XEP-0198: Stream Management](http://xmpp.org/extensions/xep-0198.html) allows XMPP to survive small network outages and - changes of the underlying TCP connection. -* [XEP-0280: Message Carbons](http://xmpp.org/extensions/xep-0280.html) which automatically syncs the messages you send to - your desktop client and thus allows you to switch seamlessly from your mobile - client to your desktop client and back within one conversation. -* [XEP-0237: Roster Versioning](http://xmpp.org/extensions/xep-0237.html) mainly to save bandwidth on poor mobile connections -* [XEP-0313: Message Archive Management](http://xmpp.org/extensions/xep-0313.html) synchronize message history with the - server. Catch up with messages that were sent while Conversations was - offline. -* [XEP-0352: Client State Indication](http://xmpp.org/extensions/xep-0352.html) lets the server know whether or not - Conversations is in the background. Allows the server to save bandwidth by - withholding unimportant packages. -* [XEP-0363: HTTP File Upload](http://xmpp.org/extensions/xep-0363.html) allows you to share files in conferences - and with offline contacts. +Google-Nutzer können dem Betaprogramm beitreten: +[Pix-Art Messenger beta](https://play.google.com/apps/testing/de.pixart.messenger) -## FAQ +#### Wie kann ich bei der Übersetzung helfen? +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/pix-art-messenger/localized.svg)](https://crowdin.com/project/pix-art-messenger) -### General +Übersetzungen werden bei [crowdin.com](https://crowdin.com/project/pix-art-messenger) geführt. Dort können Übersetzungen erstellt, geändert und ergänzt werden. -#### How do I install Conversations? +[Hier geht's zur Projektseite bei crowdin.com](https://crowdin.com/project/pix-art-messenger/invite?d=75l6j4k6k6k523f4j4m4e473u663d3m4p4t4q4) -Conversations is entirely open source and licensed under GPLv3. So if you are a -software developer you can check out the sources from GitHub and use Gradle to -build your apk file. +#### Ich habe Probleme, was soll ich tun? +Am einfachsten ist es, wenn du unserer Support-Gruppe beitrittst, dort werden deine Probleme mit Sicherheit schnell gelöst. -The more convenient way — which not only gives you automatic updates but also -supports the further development of Conversations — is to buy the App in the -Google [Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conversations&referrer=utm_source%3Dgithub). +Support-Chat: [support@room.pix-art.de](https://jabber.pix-art.de/j/support@room.pix-art.de?join) +[![Users in muc](https://inverse.chat/badge.svg?room=support@room.pix-art.de)](https://jabber.pix-art.de/j/support@room.pix-art.de?join) -Buying the App from the Play Store will also give you access to our [beta test](#beta). +Development-Chat: [development@room.pix-art.de](https://jabber.pix-art.de/j/development@room.pix-art.de?join) +[![Users in muc](https://inverse.chat/badge.svg?room=development@room.pix-art.de)](https://jabber.pix-art.de/j/development@room.pix-art.de?join) -#### I don't have a Google Account but I would still like to make a contribution +Scanne den QR-Code um in den Support-Chat zu gelangen: -I accept donations over PayPal, bank transfer and various crypto currencies. For donations via PayPal use the button below: + -[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.me/ConversationsIM) -**Disclaimer:** I'm not a huge fan of PayPal and their business policies. For -larger contributions please get in touch with me beforehand and we can talk -about bank transfer (SEPA). -##### Crypto currencies +Solltest du dort nicht weiter kommen kannst du hier ein Issue erstellen, in welchem du dein Problem genau beschreibst und welche Schritte erforderlich sind, um zu dem Problem zu gelangen. -Bitcoin: `3KAD8vew6tPZDjiUJNnZ3YUoUxrCEVNwFL` +#### Wie erstelle ich Debug- bzw. ADB-Logs? -Bitcoin Cash: `16ABkXzYAwWz8Y5DcWFfbBRqL63g3hzEaU` +##### Linux: -Ether: `0x5c4e5239cd9c6f4a909e4e8361526e2e3c8ba9fa` +1. Installation von ADB + ###### Ubuntu / Linux Mint + ``` + sudo apt-get update + sudo apt-get install android-tools-adb + ``` + ###### openSUSE 42.2 und 42.3 + ``` + sudo zypper ref + sudo zypper install android-tools + ``` + ###### openSUSE Tumbleweed + hier muss für das benötigte Packet folgende Repo eingebunden werden (z.B. bequem über Yast): + http://download.opensuse.org/repositories/hardware/openSUSE_Tumbleweed/ + + alternativ kann auch der `1 Click Installer` benutzt werden: + https://software.opensuse.org/package/android-tools + +2. Navigiere mit `cd` in das Verzeichnis deines Vertrauens und fahre mit Schritt [6] unter [Windows] fort. s.u. -#### How do I create an account? -XMPP, like email, is a federated protocol, which means that there is not one company you can create an *official XMPP account* with. Instead there are hundreds, or even thousands, of providers out there. One of those providers is our very own [conversations.im](https://account.conversations.im). If you don’t like to use *conversations.im* use a web search engine of your choice to find another provider. Or maybe your university has one. Or you can run your own. Or ask a friend to run one. Once you've found one, you can use Conversations to create an account. Just select *register new account* on server within the create account dialog. +##### Windows: -##### Domain hosting -Using your own domain not only gives you a more recognizable Jabber ID, it also gives you the flexibility to migrate your account between different XMPP providers. This is a good compromise between the responsibilities of having to operate your own server and the downsides of being dependent on a single provider. - -Learn more about [conversations.im Jabber/XMPP domain hosting](https://account.conversations.im/domain/). - -##### Running your own -If you already have a server somewhere and are willing and able to put the necessary work in you can run your own XMPP server. - -As of 2019 we recommend you use [ejabberd](https://ejabberd.im). The default configuration file already enables everything you need to pass the [Conversations Compliance Suite](https://compliance.conversations.im). Make sure your Linux distribution ships a fairly recent version. - -With a little bit of effort [Prosody](https://prosody.im) can be configured to support all necessary extensions as well. However you will have to rely on so called [Community Modules](https://modules.prosody.im/) of varying quality. Prosody can be interesting to people who like to modify their server and create / prototype own modules. - -Performance wise - for small deployments - both ejabberd and Prosody should be fine. - -#### Where can I set up a custom hostname / port -Conversations will automatically look up the SRV records for your domain name -which can point to any hostname port combination. If your server doesn’t provide -those please contact your admin and have them read -[this](http://prosody.im/doc/dns#srv_records). If your server operator is unwilling -to fix this you can enable advanced server settings in the expert settings of -Conversations. - -#### I get 'Incompatible Server' - -As regular user you should be picking a different server. The server you selected -is probably insecure and/or very old. - -If you are a server administrator you should make sure that your server provides -either STARTTLS or [XEP-0368: SRV records for XMPP over TLS](https://xmpp.org/extensions/xep-0368.html). - -On rare occasions this error message might also be caused by a server not providing -a login (SASL) mechanism that Conversations is able to handle. Conversations supports -SCRAM-SHA1, PLAIN, EXTERNAL (client certs) and DIGEST-MD5. - -#### I get 'Bind failure'. What does that mean? - -Some Bind failures are transient and resolve themselves after a reconnect. - -When trying to connect to OpenFire the bind failure can be a permanent problem when the domain part of the Jabber ID entered in Conversations doesn’t match the domain the OpenFire server feels responsible for. For example OpenFire is configured to use the domain `a.tld` but the Jabber ID entered is `user@b.tld` where `b.tld` also points to the same host. During bind OpenFire tries to reassign the Jabber to `user@a.tld`. Conversations doesn’t like that. -This can be fixed by creating a new account in Conversations that uses the Jabber ID `user@a.tld`. - -Note: This is kind of a weird quirk in OpenFire. Most other servers would just throw a 'Server not responsible for domain' error instead of attempting to reassign the Jabber ID. - -Maybe you attempted to use the Jabber ID `test@b.tld` because `a.tld` doesn’t point to the correct host. In that case you might have to enable the extended connection settings in the expert settings of Conversations and set a host name. - -### I get 'Stream opening error'. What does that mean? - -In most cases this error is caused by ejabberd advertising support for TLSv1.3 but not properly supporting it. This can happen if the openssl version on the server already supports TLSv1.3 but the fast\_tls wrapper library used by ejabberd not (properly) support it. Upgrading fast\_tls and ejabberd or - theoretically - downgrading openssl should fix the issue. A work around is to explicity disable TLSv1.3 support in the ejabberd configuration. More information can be found on [this issue on the ejabberd issue tracker](https://github.com/processone/ejabberd/issues/2614). - - -#### I’m getting this annoying permanent notification -Starting with Conversations 2.3.6 Conversations releases distributed over the Google Play Store will display a permanent notification if you are running it on Android 8 and above. This is a rule that it is essentially enforced by the Google Play Store. (You won’t have the problem of a *forced* foreground notification if you are getting your app from F-Droid.) - -However you can disable the notification via settings of the operating system. (Not settings in Conversations.) - -**The battery consumption and the entire behaviour of Conversations will remain the same (as good or as bad as it was before). Why is Google doing this to you? We have no idea.** - -##### Android <= 7.1 or Conversations from F-Droid (all Android versions) -The foreground notification is still controlled over the expert settings within Conversations as it always has been. Whether or not you need to enable it depends on how aggressive the non-standard 'power saving' features are that your phone vendor has built into the operating system. - -##### Android 8.x -Long press the permanent notification and disable that particular type of notification by moving the slider to the left. This will make the notification disappear but create another notification (this time created by the operating system itself.) that will complain about Conversations (and other apps) using battery. Starting with Android 8.1 you can disable that notification again with the same method described above. - -##### Android 9.0+ -Long press the permanent notification and press the info `(i)` button to get into the App info screen. In that screen touch the 'Notification' entry. In the next screen remove the checkbox for the 'Foreground service' entry. - -#### How do XEP-0357: Push Notifications work? -You need to be running the Play Store version of Conversations and your server needs to support push notifications.¹ Because *Google’s Firebase Cloud Messaging (FCM)* are tied with an API key to a specific app your server can not initiate the push message directly. Instead your server will send the push notification to the [Conversations App server](https://github.com/iNPUTmice/p2) (operated by us) which then acts as a proxy and initiates the push message for you. The push message sent from our App server through FCM doesn’t contain any personal information. It is just an empty message which will wake up your device and tell Conversations to reconnect to your server. The information sent from your server to our App server depends on the configuration of your server but can be limited to your account name. (In any case the Conversations App server won't redirect any information through FCM even if your server sends this information.) - -In summary Google will never get hold of any personal information besides that *something* happened. (Which doesn’t even have to be a message but can be some automated event as well.) We - as the operator of the App server - will just get hold of your account name (without being able to tie this to your specific device). - -If you don’t want this simply pick a server which does not offer Push Notifications or build Conversations yourself without support for push notifications. (This is available via a gradle build flavor.) Non-play store source of Conversations like the Amazon App store will also offer a version without push notifications. Conversations will just work as before and maintain its own TCP connection in the background. - -You can find a detailed description of how your server, the app server and FCM are interacting with each other in the [README](https://github.com/iNPUTmice/p2/blob/master/README.md) of the Conversations App Server. - - ¹ If you use the Play Store version you do **not** need to run your own app server. Your server only needs to support the server side of [XEP-0357: Push Notifications](http://xmpp.org/extensions/xep-0357.html) and [XEP-0198: Stream Management](https://xmpp.org/extensions/xep-0198.html). The prosody server modules are called *mod_cloud_notify* and *mod_smacks*. The ejabberd server modules are called *mod_push* and *mod_stream_mgmt*. - - -#### But why do I need a permanent notification if I use Google Push? -FCM (Google Push) allows an app to wake up from *Doze* which is (as the name suggests) a hibernation feature of the Android operating system that cuts the network connection and also reduces the number of times the app is allowed to wake up (to ping the server for example). The app can ask to be excluded from doze. Non push variants of the app (from F-Droid or if the server doesn’t support it) will do this on first start up. So if you get exemption from *Doze*, or if you get regular push events sent to you, Doze should not pose a threat to Conversatons working properly. But even with *Doze* the app is still open in the background (kept in memory); it is just limited in the actions it can do. Conversations needs to stay in memory to hold certain session state (online status of contacts, join status of group chats, …). However with Android 8 Google changed all of this again and now an App that wants to stay in memory needs to have a foreground service which is visible to the user via the annoying notification. But why does Conversations need to hold that state? XMPP is a stateful protocol that has a lot of per-session information; packets need to be counted, presence information needs to be held, some features like Message Carbons get activated once per session, MAM catchup happens once, service discovery happens only once; the list goes on. When Conversations was created in early 2014 none of this was a problem because apps were just allowed to stay in memory. Basically every XMPP client out there holds that information in memory because it would be a lot more complicated trying to persist it to disk. An entire rewrite of Conversations in the year 2019 would attempt to do that and would probably succeed however it would require exactly that; a complete rewrite which is not feasible right now. That’s by the way also the reason why it is difficult to write an XMPP client on iOS. Or more broadly put this is also the reason why other protocols are designed as or migrated to stateless protocols (often based on HTTP); take for example the migration of IMAP to [JMAP](https://jmap.io/). - -#### Conversations doesn’t work for me. Where can I get help? - -You can join our conference room on [`conversations@conference.siacs.eu`](https://conversations.im/j/conversations@conference.siacs.eu). -A lot of people in there are able to answer basic questions about the usage of -Conversations or can provide you with tips on running your own XMPP server. If -you found a bug or your app crashes please read the Developer / Report Bugs -section of this document. - -#### I need professional support with Conversations or setting up my server - -I'm available for hire. Contact information can be found on [my website](https://gultsch.de). - -#### How does the address book integration work? - -The address book integration was designed to protect your privacy. Conversations -neither uploads contacts from your address book to your server nor fills your -address book with unnecessary contacts from your online roster. If you manually -add a Jabber ID to your phones address book Conversations will use the name and -the profile picture of this contact. To make the process of adding Jabber IDs to -your address book easier you can click on the profile picture in the contact -details within Conversations. This will start an "add to address book" intent -with the JID as the payload. This doesn't require Conversations to have write -permissions on your address book but also doesn't require you to copy/paste a -JID from one app to another. - -#### I get 'delivery failed' on my messages - -If you get delivery failed on images it's probably because the recipient lost -network connectivity during reception. In that case you can try it again at a -later time. - -For text messages the answer to your question is a little bit more complex. -When you see 'delivery failed' on text messages, it is always something that is -being reported by the server. The most common reason for this is that the -recipient failed to resume a connection. When a client loses connectivity for a -short time the client usually has a five minute window to pick up that -connection again. When the client fails to do so because the network -connectivity is out for longer than that all messages sent to that client will -be returned to the sender resulting in a delivery failed. - -Instead of returning a message to the sender both ejabberd and prosody have the -ability to store messages in offline storage when the disconnecting client is -the only client. In prosody this is available via an extra module called -```mod_smacks_offline```. In ejabberd this is available via some configuration -settings. - -Other less common reasons are that the message you sent didn't meet some -criteria enforced by the server (too large, too many). Another reason could be -that the recipient is offline and the server doesn't provide offline storage. - -Usually you are able to distinguish between these two groups in the fact that -the first one happens always after some time and the second one happens almost -instantly. - -#### Where can I see the status of my contacts? How can I set a status or priority? - -Statuses are a horrible metric. Setting them manually to a proper value rarely -works because users are either lazy or just forget about them. Setting them -automatically does not provide quality results either. Keyboard or mouse -activity as indicator for example fails when the user is just looking at -something (reading an article, watching a movie). Furthermore automatic setting -of status always implies an impact on your privacy (are you sure you want -everybody in your contact list to know that you have been using your computer at -4am‽). - -In the past status has been used to judge the likelihood of whether or not your -messages are being read. This is no longer necessary. With Chat Markers -(XEP-0333, supported by Conversations since 0.4) we have the ability to **know** -whether or not your messages are being read. Similar things can be said for -priorities. In the past priorities have been used (by servers, not by clients!) -to route your messages to one specific client. With carbon messages (XEP-0280, -supported by Conversations since 0.1) this is no longer necessary. Using -priorities to route OTR messages isn't practical either because they are not -changeable on the fly. Metrics like last active client (the client which sent -the last message) are much better. - -Unfortunately these modern replacements for legacy XMPP features are not widely -adopted. However Conversations should be an instant messenger for the future and -instead of making Conversations compatible with the past we should work on -implementing new, improved technologies and getting them into other XMPP clients -as well. - -Making these status and priority optional isn't a solution either because -Conversations is trying to get rid of old behaviours and set an example for -other clients. - -#### Translations -Translations are managed on [Transifex](https://www.transifex.com/projects/p/conversations/). -If you want to become a translator Please register on transifex, apply to join -the translation team and then step by our group chat on -[conversations@conference.siacs.eu](https://conversations.im/j/conversations@conference.siacs.eu) -and introduce yourself to `iNPUTmice` so he can approve your join request. - -#### How do I backup / move Conversations to a new device? -On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.) - -As of version 2.4.0 an integrated Backup & Restore function will help with this, go to Settings and you’ll find a setting called Create backup. A notification will pop-up during the creation process that will announce you when it's ready. After the files, one for each account, are created, you can move the **Conversations** folder *(if you want your old media files too)* or only the **Conversations/Backup** folder *(for OMEMO keys and history only)* to your new device (or to a storage place) where a freshly installed Conversations can restore each account. Don't forget to enable the accounts after a succesful restore. - -This backup method will include your OMEMO keys. Due to forward secrecy you will not be able to recover messages sent and received between creating the backup and restoring it. If you have a server side archive (MAM) those messages will be retrieved but displayed as *unable to decrypt*. For technical reasons you might also lose the first message you either sent or receive after the restore; for each conversation you have. This message will then also show up as *unable to decrypt*, but this will automatically recover itself as long as both participants are on Conversations 2.3.11+. Note that this doesn’t happen if you just transfer to a new phone and no messages have been exchanged between backup and restore. - -In the vast, vast majority of cases you won’t have to manually delete OMEMO keys or do anything like that. Conversations only introduced the offical backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine. - -**WARNING**: Be sure to know your accounts passwords or find ways to reset them **before** doing the backup as the files are encrypted using those passwords and the Restore process will ask for them. -**WARNING**: Do not use the restore backup feature in an attempt to clone (run simultaneously) an installation. Restoring a backup is only meant for migrations or in case you’ve lost the original device. - -#### Conversations is missing a certain feature - -I'm open for new feature suggestions. You can use the [issue tracker][issues] on -GitHub. Please take some time to browse through the issues to see if someone -else already suggested it. Be assured that I read each and every ticket. If I -like it I will leave it open until it's implemented. If I don't like it I will -close it (usually with a short comment). If I don't comment on an feature -request that's probably a good sign because this means I agree with you. -Commenting with +1 on either open or closed issues won't change my mind, nor -will it accelerate the development. - -#### You closed my feature request but I want it really really badly - -Just write it yourself and send me a pull request. If I like it I will happily -merge it if I don't at least you and like minded people get to enjoy it. - -#### I need a feature and I need it now! - -I am available for hire. Find contact information on [my website](https://gultsch.de). - -### Security - -#### Why are there two end-to-end encryption methods and which one should I choose? - -* OMEMO works even when a contact is offline, and works with multiple devices. It also allows asynchronous file-transfer when the server has [HTTP File Upload](http://xmpp.org/extensions/xep-0363.html). However, OMEMO not widely support and is currently implemented only [by a handful of clients](https://omemo.top). -* OpenPGP (XEP-0027) is a very old encryption method that has some advantages over OMEMO but should only be used by people who know what they are doing. - -#### How do I use OpenPGP - -Before you continue reading you should note that the OpenPGP support in -Conversations is experimental. This is not because it will make the app unstable -but because the fundamental concepts of PGP aren't ready for widespread use. -The way PGP works is that you trust Key IDs instead of JID's or email addresses. -So in theory your contact list should consist of Public-Key-IDs instead of -JID's. But of course no email or XMPP client out there implements these -concepts. Plus PGP in the context of instant messaging has a couple of -downsides: It is vulnerable to replay attacks and it is rather verbose. - -To use OpenPGP you have to install the open source app -[OpenKeychain](http://www.openkeychain.org) and then long press on the account in -manage accounts and choose renew PGP announcement from the contextual menu. - -#### OMEMO is grayed out. What do I do? -OMEMO is only available in 1:1 chats and private (members-only, non-anonymous) group chats. Encrypting public group chats makes little to no sense since anyone (including a hypothetical attacker) can join and a user couldn’t possibily verify all participants anyway. Furthermore for a lot of public group chat it is desirable to give new comers access to the full history. - -#### OMEMO doesn’t work. I get a 'Something went wrong' message in the 'Trust OMEMO Fingerprints' screen. -OMEMO has two requirements: Your server and the server of your contact need to support PEP. Both of you can verify that individually by opening your account details and selecting ```Server info``` from the menu. The appearing table should list PEP as available. The second requirement is that the initial sender needs to have access to the published key material. This can either be achieved by having mutual presence subscription (you can verify that by opening the contact details and see if both check boxes *Send presence updates* and *Receive presence updates* are checked) or by using a server that makes the public key material accessible to anyone. In the [Compliance Tester](https://compliance.conversations.im) this is indicated by the 'OMEMO' feature. Since it is very common that the first messages are exchanged *before* adding each other to the contact list it is desirable to use servers that have 'OMEMO support'. - -#### How does the encryption for group chats work? - -##### OMEMO - -OMEMO encryption works only in private (members only) conferences that are non-anonymous. Non-anonymous (being able to discover the real JID of other participants) is a technical requirement to discover the key material. Members only is a sort of arbitrary requirement imposed by Conversations. (see 'OMEMO is grayed out') - -The server of all participants need to pass the OMEMO [Compliance Test](https://conversations.im/compliance/). -In other words they either need to run Ejabberd 18.01+ or Prosody 0.11+. - -(Alternatively it would also work if all participants had each other in their contact list; But that rarely is the case in larger group chats.) - -The owner of a conference can make a public conference private by going into the conference -details and hit the settings button (the one with the gears) and select both *private* and -*members only*. - -##### OpenPGP - -Every participant has to announce their OpenPGP key (see answer above). -If you would like to send encrypted messages to a conference you have to make -sure that you have every participant's public key in your OpenKeychain. -Right now there is no check in Conversations to ensure that. -You have to take care of that yourself. Go to the conference details and -touch every key id (The hexadecimal number below a contact). This will send you -to OpenKeychain which will assist you on adding the key. This works best in -very small conferences with contacts you are already using OpenPGP with. This -feature is regarded experimental. Conversations is the only client that uses -XEP-0027 with conferences. (The XEP neither specifically allows nor disallows -this.) - -#### What is Blind Trust Before Verification / why are messages marked with a red lock? - -Read more about the concept on https://gultsch.de/trust.html - -#### What happened to OTR support? -OTR was removed because it was highly unreliable. It didn’t work with multiple devices and was never really specified to work with XMPP. The codebase was a mess (There was an HTML parser in there for crying out loud to deal with the garbage some OTR clients would send.) Verification was implemented in a non-blocking way. It would tell you if the current session was using an unknown fingerprint but it didn’t actively stopped you from sending messages until you have confirmed the new fingerprint. (Like Conversations would do now with BTBV after verification or when BTBV is turned off.) Considering the previous points there was little to no desire from my point to fix this potential security issue or clean up the code base. Another reason for the removal was that people would use it *accidentally* even to communicate between two Conversations clients because they read somewhere that OTR is good. - -### What clients do I use on other platforms -There are XMPP Clients available for all major platforms. -#### Windows / Linux -For your desktop computer we recommend that you use [Gajim](https://gajim.org). You need to install the plugins `OMEMO`, `HTTP Upload` and `URL image preview` to get the best compatibility with Conversations. Plugins can be installed from within the app. -#### iOS -Unfortunately we don‘t have a recommendation for iPhones right now. There are two clients available [ChatSecure](https://chatsecure.org/) and [Monal](https://monal.im/). Both with their own pros and cons. - - -### Development - - -#### Beta testing -If you bought the App on [Google Play](https://play.google.com/store/apps/details?id=eu.siacs.conversations) -you can get access to the the latest beta version by signing up using [this link](https://play.google.com/apps/testing/eu.siacs.conversations). - -#### How do I build Conversations - -**Note:** Starting with version 2.8.0 you will need to compile libwebrtc. -[Instructions](https://webrtc.github.io/webrtc-org/native-code/android/) can be found on the WebRTC -website. Place the resulting libwebrtc.aar in the `libs/` directory. The PlayStore release currently -uses the stable M81 release and renamed the file name to `libwebrtc-m81.aar` put potentially you can -reference any file name by modifying `build.gradle`. - -Make sure to have ANDROID_HOME point to your Android SDK. Use the Android SDK Manager to install missing dependencies. - - git clone https://github.com/inputmice/Conversations.git - cd Conversations - ./gradlew assembleConversationsFreeSystemDebug - -There are two build flavors available. *free* and *playstore*. Unless you know what you are doing you only need *free*. - - -[![Build Status](https://travis-ci.org/inputmice/Conversations.svg?branch=development)](https://travis-ci.org/inputmice/Conversations) - -#### How do I update/add external libraries? - -If the library you want to update is in Maven Central or JCenter (or has its own -Maven repo), add it or update its version in `build.gradle`. If the library is -in the `libs/` directory, you can update it using a subtree merge by doing the -following (using `minidns` as an example): - - git remote add minidns https://github.com/rtreffer/minidns.git - git fetch minidns - git merge -s subtree minidns master - -To add a new dependency to the `libs/` directory (replacing "name", "branch" and -"url" as necessary): - - git remote add name url - git merge -s ours --no-commit name/branch - git read-tree --prefix=libs/name -u name/branch - git commit -m "Subtree merged in name" - -#### How do I debug Conversations - -If something goes wrong Conversations usually exposes very little information in -the UI (other than the fact that something didn't work). However with adb -(android debug bridge) you can squeeze some more information out of Conversations. -These information are especially useful if you are experiencing trouble with -your connection or with file transfer. - -To use adb you have to connect your mobile phone to your computer with an USB cable -and install `adb`. Most Linux systems have prebuilt packages for that tool. On -Debian/Ubuntu for example it is called `android-tools-adb`. - -Furthermore you might have to enable 'USB debugging' in the Developer options of your -phone. After that you can just execute the following on your computer: - - adb -d logcat -v time -s conversations - -If need be there are also some Apps on the PlayStore that can be used to show the logcat -directly on your rooted phone. (Search for logcat). However in regards to further processing -(for example to create an issue here on Github) it is more convenient to just use your PC. - -#### I found a bug - -Please report it to our [issue tracker][issues]. If your app crashes please -provide a stack trace. If you are experiencing misbehavior please provide -detailed steps to reproduce. Always mention whether you are running the latest -Play Store version or the current HEAD. If you are having problems connecting to -your XMPP server your file transfer doesn’t work as expected please always -include a logcat debug output with your issue (see above). - -[issues]: https://github.com/inputmice/Conversations/issues +1. Lade dir die SDK-Plattform-Tools für dein Betriebssystem von Google herunter: + + https://developer.android.com/studio/releases/platform-tools.html +2. Falls noch nicht getan, lade dir die ADB Treiber für dein Betriebssystem von Google herunter, für Windows hier: + + https://developer.android.com/studio/run/win-usb.html +3. Entpacke die zip (z.B. nach C:\ADB\) +4. Öffne die Kommandozeile (CMD) mit Start > Ausführen: cmd +5. Wechsele in der Kommandozeile in das Verzeichnis C:\ADB wie folgt + ``` + c: + cd ADB + ``` +6. Auf deinem Telefon gehst du in die Einstellungen und suchst nach dem Punkt `Entwickleroptionen`. Sollte dieser bei dir nicht vorhanden sein, musst du diese Optionen erst noch freischalten. Dazu wechselst du in den Einstellungen in den Punkt `über das Telefon` und suchst dort nach `Buildnummer` oder Ähnlichem. Diese Zeile musst Du mindestens 7 mal hintereinander antippen, es sollte dann ein Hinweis eingeblendet werden, der dir bestätigt, dass du nun Entwickler bist. +7. In den `Entwickleroptionen` suchst du nach dem Eintrag `USB-Debugging` und aktivierst ihn. +8. Schließe dein Handy mit dem USB-Kabel an deinen PC an. Die erforderlichen Treiber sollten zumindest in Windows automatisch installiert werden. +9. Wenn alles ohne Fehler geklappt hat, kannst du wieder in die Kommandozeile gehen und testen, ob alles funktioniert. Gib dazu in CMD `adb devices -l` ein, es sollte in etwa sowas bei dir stehen: + ``` + > adb devices -l + List of devices attached + * daemon not running. starting it now on port 5037 * + * daemon started successfully * + 042111560169500303f4 unauthorized + ``` +10. Falls dein Handy als `unauthorized` markiert wird, sollte am Handy eine Meldung `USB-Debugging zulassen?` kommen, diese mit `OK` bestätigen, sodass bei `adb devices` folgendes dort stehen sollte: + ``` + > adb devices + List of devices attached + 042111560169500303f4 device + ``` +11. Nun kannst du mit der Ausgabe der Debug-Logs beginnen. Dazu gibst du im CMD folgendes ein und die Ausgabe beginnt in die Datei `logcat.txt` im Verzeichnis `C:\ADB`: + ``` + > adb -d logcat -v time | FINDSTR Pix-Art > logcat.txt + ``` +12. Führe nun die Schritte aus, die zum Fehler führen. + +13. Zum Schluss schaue dir die `logcat.txt` an, lösche ggf. persönliche Angaben und sende diese Datei zur Problemlösung mit einer Beschreibung des Fehlers und was man tun muss, um diesen Fehler zu erhalten, an mich. Nutz dafür den Menüpunkt [Issues](https://github.com/kriztan/Pix-Art-Messenger/issues) diff --git a/art/Feature_OMEMO.png b/art/Feature_OMEMO.png new file mode 100644 index 000000000..1afdb8d6e Binary files /dev/null and b/art/Feature_OMEMO.png differ diff --git a/art/conversations_baloon.svg b/art/conversations_baloon.svg new file mode 100644 index 000000000..9058187aa --- /dev/null +++ b/art/conversations_baloon.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/art/conversations_mono.svg b/art/conversations_mono.svg index 8bd294736..c1428ea29 100644 --- a/art/conversations_mono.svg +++ b/art/conversations_mono.svg @@ -1,337 +1,129 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/conversations_mono_dashed.png b/art/conversations_mono_dashed.png new file mode 100644 index 000000000..7e36cde09 Binary files /dev/null and b/art/conversations_mono_dashed.png differ diff --git a/art/conversations_mono_dashed.svg b/art/conversations_mono_dashed.svg index a8bba9414..985964a3f 100644 --- a/art/conversations_mono_dashed.svg +++ b/art/conversations_mono_dashed.svg @@ -1,400 +1,176 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/art/date_bubble_grey.svg b/art/date_bubble_grey.svg deleted file mode 100644 index 38db49f9b..000000000 --- a/art/date_bubble_grey.svg +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/art/date_bubble_white.svg b/art/date_bubble_white.svg deleted file mode 100644 index 452ae9272..000000000 --- a/art/date_bubble_white.svg +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/art/ic_launcher.svg b/art/ic_launcher.svg deleted file mode 100644 index 96b17d2b4..000000000 --- a/art/ic_launcher.svg +++ /dev/null @@ -1,427 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/art/ic_no_results_black.svg b/art/ic_no_results_black.svg deleted file mode 100644 index fb0f78117..000000000 --- a/art/ic_no_results_black.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_no_results_white.svg b/art/ic_no_results_white.svg deleted file mode 100644 index 744616a48..000000000 --- a/art/ic_no_results_white.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_notifications_none_white80.svg b/art/ic_notifications_none_white80.svg deleted file mode 100644 index d333130ff..000000000 --- a/art/ic_notifications_none_white80.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_notifications_off_white80.svg b/art/ic_notifications_off_white80.svg deleted file mode 100644 index f0af47b61..000000000 --- a/art/ic_notifications_off_white80.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_notifications_paused_white80.svg b/art/ic_notifications_paused_white80.svg deleted file mode 100644 index 4f92e8475..000000000 --- a/art/ic_notifications_paused_white80.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_notifications_white80.svg b/art/ic_notifications_white80.svg deleted file mode 100644 index 398e51385..000000000 --- a/art/ic_notifications_white80.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_read_indicator.svg b/art/ic_read_indicator.svg new file mode 100644 index 000000000..7580e2ae6 --- /dev/null +++ b/art/ic_read_indicator.svg @@ -0,0 +1,30 @@ + + + + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_received_indicator.svg b/art/ic_received_indicator.svg new file mode 100644 index 000000000..c7833a741 --- /dev/null +++ b/art/ic_received_indicator.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/art/ic_search_black.svg b/art/ic_search_black.svg deleted file mode 100644 index e3d0e8096..000000000 --- a/art/ic_search_black.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_search_white.svg b/art/ic_search_white.svg deleted file mode 100644 index 7186d8e23..000000000 --- a/art/ic_search_white.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_cancel_away.svg b/art/ic_send_cancel_away.svg index 1ee9c40f6..fa803779c 100644 --- a/art/ic_send_cancel_away.svg +++ b/art/ic_send_cancel_away.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_cancel_dnd.svg b/art/ic_send_cancel_dnd.svg index 67a562b2f..9501f7441 100644 --- a/art/ic_send_cancel_dnd.svg +++ b/art/ic_send_cancel_dnd.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_cancel_offline.svg b/art/ic_send_cancel_offline.svg index b88ade09d..9d37a350f 100644 --- a/art/ic_send_cancel_offline.svg +++ b/art/ic_send_cancel_offline.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_cancel_offline_white.svg b/art/ic_send_cancel_offline_white.svg deleted file mode 100644 index f84f62f57..000000000 --- a/art/ic_send_cancel_offline_white.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_cancel_online.svg b/art/ic_send_cancel_online.svg index 133e69f11..53ac9c8b1 100644 --- a/art/ic_send_cancel_online.svg +++ b/art/ic_send_cancel_online.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_location_away.svg b/art/ic_send_location_away.svg index fcd50b521..8a171774e 100644 --- a/art/ic_send_location_away.svg +++ b/art/ic_send_location_away.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_location_dnd.svg b/art/ic_send_location_dnd.svg index 705cdb6f4..6fc835bc7 100644 --- a/art/ic_send_location_dnd.svg +++ b/art/ic_send_location_dnd.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_location_offline.svg b/art/ic_send_location_offline.svg index 56529b723..e957a15b2 100644 --- a/art/ic_send_location_offline.svg +++ b/art/ic_send_location_offline.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_location_offline_white.svg b/art/ic_send_location_offline_white.svg deleted file mode 100644 index f7f60a8f4..000000000 --- a/art/ic_send_location_offline_white.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_location_online.svg b/art/ic_send_location_online.svg index 76d146ccf..d82369d69 100644 --- a/art/ic_send_location_online.svg +++ b/art/ic_send_location_online.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_photo_away.svg b/art/ic_send_photo_away.svg index 31a20e09f..9c605e7cf 100644 --- a/art/ic_send_photo_away.svg +++ b/art/ic_send_photo_away.svg @@ -1,60 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_photo_dnd.svg b/art/ic_send_photo_dnd.svg index 9ef8b7821..6e5562cd9 100644 --- a/art/ic_send_photo_dnd.svg +++ b/art/ic_send_photo_dnd.svg @@ -1,60 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_photo_offline.svg b/art/ic_send_photo_offline.svg index b2ca20a6f..fcbab71e4 100644 --- a/art/ic_send_photo_offline.svg +++ b/art/ic_send_photo_offline.svg @@ -1,60 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_photo_offline_white.svg b/art/ic_send_photo_offline_white.svg deleted file mode 100644 index 45875731c..000000000 --- a/art/ic_send_photo_offline_white.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/art/ic_send_photo_online.svg b/art/ic_send_photo_online.svg index f29c3c7c7..3c4ccceae 100644 --- a/art/ic_send_photo_online.svg +++ b/art/ic_send_photo_online.svg @@ -1,60 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_picture_away.svg b/art/ic_send_picture_away.svg index a85a1eecb..5a2bfaf28 100644 --- a/art/ic_send_picture_away.svg +++ b/art/ic_send_picture_away.svg @@ -1,55 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_picture_dnd.svg b/art/ic_send_picture_dnd.svg index 0c7d06356..2666b76ee 100644 --- a/art/ic_send_picture_dnd.svg +++ b/art/ic_send_picture_dnd.svg @@ -1,55 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_picture_offline.svg b/art/ic_send_picture_offline.svg index 048508a35..cb77ee998 100644 --- a/art/ic_send_picture_offline.svg +++ b/art/ic_send_picture_offline.svg @@ -1,55 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_picture_offline_white.svg b/art/ic_send_picture_offline_white.svg deleted file mode 100644 index 16131740f..000000000 --- a/art/ic_send_picture_offline_white.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/art/ic_send_picture_online.svg b/art/ic_send_picture_online.svg index ff388a4d4..ab8109c7f 100644 --- a/art/ic_send_picture_online.svg +++ b/art/ic_send_picture_online.svg @@ -1,55 +1,27 @@ - - - - - image/svg+xml - - - - - - - - + + + + + image/svg+xml + + + + + + + + diff --git a/art/ic_send_text_away.svg b/art/ic_send_text_away.svg index ea83086ae..f6b02b4f9 100644 --- a/art/ic_send_text_away.svg +++ b/art/ic_send_text_away.svg @@ -1,69 +1,33 @@ - - + - - - - - image/svg+xml - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_text_dnd.svg b/art/ic_send_text_dnd.svg index 1b7ad51f4..0ebb40f41 100644 --- a/art/ic_send_text_dnd.svg +++ b/art/ic_send_text_dnd.svg @@ -1,69 +1,33 @@ - - + - - - - - image/svg+xml - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_text_offline.svg b/art/ic_send_text_offline.svg index c87bfaac6..25ffc5f61 100644 --- a/art/ic_send_text_offline.svg +++ b/art/ic_send_text_offline.svg @@ -1,69 +1,34 @@ - - + - - - - - image/svg+xml - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_text_offline_white.svg b/art/ic_send_text_offline_white.svg deleted file mode 100644 index 4434d33db..000000000 --- a/art/ic_send_text_offline_white.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/art/ic_send_text_online.svg b/art/ic_send_text_online.svg index ff01bd6ef..aec29e306 100644 --- a/art/ic_send_text_online.svg +++ b/art/ic_send_text_online.svg @@ -1,69 +1,33 @@ - - + - - - - - image/svg+xml - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_video_away.svg b/art/ic_send_video_away.svg new file mode 100644 index 000000000..c98417a27 --- /dev/null +++ b/art/ic_send_video_away.svg @@ -0,0 +1,28 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_video_dnd.svg b/art/ic_send_video_dnd.svg new file mode 100644 index 000000000..cbf7d5775 --- /dev/null +++ b/art/ic_send_video_dnd.svg @@ -0,0 +1,28 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_video_offline.svg b/art/ic_send_video_offline.svg new file mode 100644 index 000000000..0426a7233 --- /dev/null +++ b/art/ic_send_video_offline.svg @@ -0,0 +1,28 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_video_online.svg b/art/ic_send_video_online.svg new file mode 100644 index 000000000..021da4eb5 --- /dev/null +++ b/art/ic_send_video_online.svg @@ -0,0 +1,28 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/ic_send_videocam_away.svg b/art/ic_send_videocam_away.svg deleted file mode 100644 index 52b8478cf..000000000 --- a/art/ic_send_videocam_away.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_videocam_dnd.svg b/art/ic_send_videocam_dnd.svg deleted file mode 100644 index 254e3859a..000000000 --- a/art/ic_send_videocam_dnd.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_videocam_offline.svg b/art/ic_send_videocam_offline.svg deleted file mode 100644 index 91a7778d6..000000000 --- a/art/ic_send_videocam_offline.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_videocam_offline_white.svg b/art/ic_send_videocam_offline_white.svg deleted file mode 100644 index 67f5563a6..000000000 --- a/art/ic_send_videocam_offline_white.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_videocam_online.svg b/art/ic_send_videocam_online.svg deleted file mode 100644 index 7ee2fff97..000000000 --- a/art/ic_send_videocam_online.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/art/ic_send_voice_away.svg b/art/ic_send_voice_away.svg index 379f55b7d..f1c303984 100644 --- a/art/ic_send_voice_away.svg +++ b/art/ic_send_voice_away.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_voice_dnd.svg b/art/ic_send_voice_dnd.svg index b1b7a7a97..fdd21b126 100644 --- a/art/ic_send_voice_dnd.svg +++ b/art/ic_send_voice_dnd.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_voice_offline.svg b/art/ic_send_voice_offline.svg index 64ea44731..41edd5b65 100644 --- a/art/ic_send_voice_offline.svg +++ b/art/ic_send_voice_offline.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_send_voice_offline_white.svg b/art/ic_send_voice_offline_white.svg deleted file mode 100644 index 25ffe3223..000000000 --- a/art/ic_send_voice_offline_white.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/art/ic_send_voice_online.svg b/art/ic_send_voice_online.svg index 2c5405236..e73c6697b 100644 --- a/art/ic_send_voice_online.svg +++ b/art/ic_send_voice_online.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/ic_verified_fingerprint.svg b/art/ic_verified_fingerprint.svg index 689c42ebd..625b29759 100644 --- a/art/ic_verified_fingerprint.svg +++ b/art/ic_verified_fingerprint.svg @@ -1,54 +1,26 @@ - - - - - image/svg+xml - - - - - - - + + + + + image/svg+xml + + + + + + + diff --git a/art/icon.png b/art/icon.png new file mode 100644 index 000000000..fdbce8a00 Binary files /dev/null and b/art/icon.png differ diff --git a/art/logo.png b/art/logo.png index c606a5829..3fad7101a 100644 Binary files a/art/logo.png and b/art/logo.png differ diff --git a/art/logo_highres.png b/art/logo_highres.png new file mode 100644 index 000000000..370b4a2f3 Binary files /dev/null and b/art/logo_highres.png differ diff --git a/art/main_logo.svg b/art/main_logo.svg deleted file mode 120000 index e62f735a0..000000000 --- a/art/main_logo.svg +++ /dev/null @@ -1 +0,0 @@ -ic_launcher.svg \ No newline at end of file diff --git a/art/marker.svg b/art/marker.svg deleted file mode 100644 index f73c1537b..000000000 --- a/art/marker.svg +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - diff --git a/art/message_bubble_received.svg b/art/message_bubble_received.svg index 61f2e54dc..5e3d9fe0e 100644 --- a/art/message_bubble_received.svg +++ b/art/message_bubble_received.svg @@ -1,165 +1,60 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + - diff --git a/art/message_bubble_received_dark.svg b/art/message_bubble_received_dark.svg deleted file mode 100644 index b613f19f0..000000000 --- a/art/message_bubble_received_dark.svg +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/art/message_bubble_received_grey.svg b/art/message_bubble_received_grey.svg deleted file mode 100644 index e1d8347fc..000000000 --- a/art/message_bubble_received_grey.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/art/message_bubble_received_warning.svg b/art/message_bubble_received_warning.svg index 765ca7041..e24f27b94 100644 --- a/art/message_bubble_received_warning.svg +++ b/art/message_bubble_received_warning.svg @@ -1,165 +1,60 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + - diff --git a/art/message_bubble_received_white.svg b/art/message_bubble_received_white.svg index 52e599f05..5e3d9fe0e 100644 --- a/art/message_bubble_received_white.svg +++ b/art/message_bubble_received_white.svg @@ -1,165 +1,60 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + - diff --git a/art/message_bubble_sent.svg b/art/message_bubble_sent.svg index 90ad5091a..025ea3a73 100644 --- a/art/message_bubble_sent.svg +++ b/art/message_bubble_sent.svg @@ -1,165 +1,60 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + - diff --git a/art/message_bubble_sent_grey.svg b/art/message_bubble_sent_grey.svg deleted file mode 100644 index 23e13d665..000000000 --- a/art/message_bubble_sent_grey.svg +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/art/message_bubble_sent_white.svg b/art/message_bubble_sent_white.svg new file mode 100644 index 000000000..53ea90940 --- /dev/null +++ b/art/message_bubble_sent_white.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/art/nightly-qr.png b/art/nightly-qr.png new file mode 100644 index 000000000..00d7fe2d6 Binary files /dev/null and b/art/nightly-qr.png differ diff --git a/art/omemo_logo.svg b/art/omemo_logo.svg index ca20a5b94..d6980027c 100644 --- a/art/omemo_logo.svg +++ b/art/omemo_logo.svg @@ -1,273 +1,163 @@ - - + - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/art/open_pdf_black.svg b/art/open_pdf_black.svg deleted file mode 100644 index 0fa22285a..000000000 --- a/art/open_pdf_black.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/art/open_pdf_white.svg b/art/open_pdf_white.svg deleted file mode 100644 index a307529bb..000000000 --- a/art/open_pdf_white.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/art/play_gif_black.svg b/art/play_gif.svg similarity index 68% rename from art/play_gif_black.svg rename to art/play_gif.svg index a2b426a24..7315ba4f3 100644 --- a/art/play_gif_black.svg +++ b/art/play_gif.svg @@ -1,20 +1,8 @@ - + @@ -64,5 +52,5 @@ d="M11.5 9H13v6h-1.5zM9 9H6c-.6 0-1 .5-1 1v4c0 .5.4 1 1 1h3c.6 0 1-.5 1-1v-2H8.5v1.5h-2v-3H10V10c0-.5-.4-1-1-1zm10 1.5V9h-4.5v6H16v-2h2v-1.5h-2v-1z" clip-path="url(#b)" id="path10" - style="fill:#000000;fill-opacity:0.54" /> + style="fill:#ffffff;fill-opacity:0.7019608" /> diff --git a/art/play_gif_white.svg b/art/play_gif_white.svg deleted file mode 100644 index f8ec27426..000000000 --- a/art/play_gif_white.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/art/play_video.svg b/art/play_video.svg new file mode 100644 index 000000000..72d07433a --- /dev/null +++ b/art/play_video.svg @@ -0,0 +1,29 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/art/play_video_black.svg b/art/play_video_black.svg deleted file mode 100644 index 72d6e756f..000000000 --- a/art/play_video_black.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/art/play_video_white.svg b/art/play_video_white.svg deleted file mode 100644 index c8a1558ba..000000000 --- a/art/play_video_white.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/art/qr-code_suport.png b/art/qr-code_suport.png new file mode 100644 index 000000000..a65d6da90 Binary files /dev/null and b/art/qr-code_suport.png differ diff --git a/art/qr-code_suport_small.png b/art/qr-code_suport_small.png new file mode 100644 index 000000000..e74e47c30 Binary files /dev/null and b/art/qr-code_suport_small.png differ diff --git a/art/qrcode-scan.svg b/art/qrcode-scan.svg deleted file mode 100644 index 63f1f6a15..000000000 --- a/art/qrcode-scan.svg +++ /dev/null @@ -1,48 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/art/quicksy.svg b/art/quicksy.svg deleted file mode 100644 index e70bf4f01..000000000 --- a/art/quicksy.svg +++ /dev/null @@ -1,366 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/art/quicksy_main_logo.svg b/art/quicksy_main_logo.svg deleted file mode 120000 index 6fce608d9..000000000 --- a/art/quicksy_main_logo.svg +++ /dev/null @@ -1 +0,0 @@ -quicksy.svg \ No newline at end of file diff --git a/art/quicksy_mono.svg b/art/quicksy_mono.svg deleted file mode 100644 index 8ca8d3e47..000000000 --- a/art/quicksy_mono.svg +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/art/quicksy_splash_logo.svg b/art/quicksy_splash_logo.svg deleted file mode 120000 index 6fce608d9..000000000 --- a/art/quicksy_splash_logo.svg +++ /dev/null @@ -1 +0,0 @@ -quicksy.svg \ No newline at end of file diff --git a/art/render.rb b/art/render.rb index 863a5cca3..6b075b1ec 100755 --- a/art/render.rb +++ b/art/render.rb @@ -11,81 +11,50 @@ resolutions = { } images = { - 'main_logo.svg' => ['conversations/main_logo', 200], - 'quicksy_main_logo.svg' => ['quicksy/main_logo', 200], - 'splash_logo.svg' => ['conversations/splash_logo', 144], - 'quicksy_splash_logo.svg' => ['quicksy/splash_logo', 144], - 'ic_search_black.svg' => ['ic_search_background_black', 144], - 'ic_search_white.svg' => ['ic_search_background_white', 144], - 'ic_no_results_white.svg' => ['ic_no_results_background_white', 144], - 'ic_no_results_black.svg' => ['ic_no_results_background_black', 144], - 'play_video_white.svg' => ['play_video_white', 128], - 'play_gif_white.svg' => ['play_gif_white', 128], - 'play_video_black.svg' => ['play_video_black', 128], - 'play_gif_black.svg' => ['play_gif_black', 128], - 'open_pdf_black.svg' => ['open_pdf_black', 128], - 'open_pdf_white.svg' => ['open_pdf_white', 128], - 'conversations_mono.svg' => ['conversations/ic_notification', 24], - 'quicksy_mono.svg' => ['quicksy/ic_notification', 24], - 'flip_camera_android-black-24dp.svg' => ['ic_flip_camera_android_black_24dp', 24], + 'ic_launcher.svg' => ['ic_launcher', 48], + 'main_logo.svg' => ['main_logo', 200], + 'play_video.svg' => ['play_video', 128], + 'play_gif.svg' => ['play_gif', 128], + 'conversations_mono.svg' => ['ic_notification', 24], + 'ic_received_indicator.svg' => ['ic_received_indicator', 12], + 'ic_read_indicator.svg' => ['ic_read_indicator', 12], 'ic_send_text_offline.svg' => ['ic_send_text_offline', 36], - 'ic_send_text_offline_white.svg' => ['ic_send_text_offline_white', 36], 'ic_send_text_online.svg' => ['ic_send_text_online', 36], 'ic_send_text_away.svg' => ['ic_send_text_away', 36], 'ic_send_text_dnd.svg' => ['ic_send_text_dnd', 36], 'ic_send_photo_online.svg' => ['ic_send_photo_online', 36], 'ic_send_photo_offline.svg' => ['ic_send_photo_offline', 36], - 'ic_send_photo_offline_white.svg' => ['ic_send_photo_offline_white', 36], 'ic_send_photo_away.svg' => ['ic_send_photo_away', 36], 'ic_send_photo_dnd.svg' => ['ic_send_photo_dnd', 36], 'ic_send_location_online.svg' => ['ic_send_location_online', 36], 'ic_send_location_offline.svg' => ['ic_send_location_offline', 36], - 'ic_send_location_offline_white.svg' => ['ic_send_location_offline_white', 36], 'ic_send_location_away.svg' => ['ic_send_location_away', 36], 'ic_send_location_dnd.svg' => ['ic_send_location_dnd', 36], 'ic_send_voice_online.svg' => ['ic_send_voice_online', 36], 'ic_send_voice_offline.svg' => ['ic_send_voice_offline', 36], - 'ic_send_voice_offline_white.svg' => ['ic_send_voice_offline_white', 36], 'ic_send_voice_away.svg' => ['ic_send_voice_away', 36], 'ic_send_voice_dnd.svg' => ['ic_send_voice_dnd', 36], 'ic_send_cancel_online.svg' => ['ic_send_cancel_online', 36], 'ic_send_cancel_offline.svg' => ['ic_send_cancel_offline', 36], - 'ic_send_cancel_offline_white.svg' => ['ic_send_cancel_offline_white', 36], 'ic_send_cancel_away.svg' => ['ic_send_cancel_away', 36], 'ic_send_cancel_dnd.svg' => ['ic_send_cancel_dnd', 36], 'ic_send_picture_online.svg' => ['ic_send_picture_online', 36], 'ic_send_picture_offline.svg' => ['ic_send_picture_offline', 36], - 'ic_send_picture_offline_white.svg' => ['ic_send_picture_offline_white', 36], 'ic_send_picture_away.svg' => ['ic_send_picture_away', 36], 'ic_send_picture_dnd.svg' => ['ic_send_picture_dnd', 36], - 'ic_send_videocam_online.svg' => ['ic_send_videocam_online', 36], - 'ic_send_videocam_offline.svg' => ['ic_send_videocam_offline', 36], - 'ic_send_videocam_offline_white.svg' => ['ic_send_videocam_offline_white', 36], - 'ic_send_videocam_away.svg' => ['ic_send_videocam_away', 36], - 'ic_send_videocam_dnd.svg' => ['ic_send_videocam_dnd', 36], - 'ic_notifications_none_white80.svg' => ['ic_notifications_none_white80', 24], - 'ic_notifications_off_white80.svg' => ['ic_notifications_off_white80', 24], - 'ic_notifications_paused_white80.svg' => ['ic_notifications_paused_white80', 24], - 'ic_notifications_white80.svg' => ['ic_notifications_white80', 24], 'ic_verified_fingerprint.svg' => ['ic_verified_fingerprint', 36], - 'qrcode-scan.svg' => ['ic_qr_code_scan_white_24dp', 24], 'message_bubble_received.svg' => ['message_bubble_received.9', 0], - 'message_bubble_received_grey.svg' => ['message_bubble_received_grey.9', 0], - 'message_bubble_received_dark.svg' => ['message_bubble_received_dark.9', 0], 'message_bubble_received_warning.svg' => ['message_bubble_received_warning.9', 0], 'message_bubble_received_white.svg' => ['message_bubble_received_white.9', 0], 'message_bubble_sent.svg' => ['message_bubble_sent.9', 0], - 'message_bubble_sent_grey.svg' => ['message_bubble_sent_grey.9', 0], - 'date_bubble_white.svg' => ['date_bubble_white.9', 0], - 'date_bubble_grey.svg' => ['date_bubble_grey.9', 0], - 'marker.svg' => ['marker', 0] + 'message_bubble_sent_white.svg' => ['message_bubble_sent_white.9', 0], } # Executable paths for Mac OSX # "/Applications/Inkscape.app/Contents/Resources/bin/inkscape" inkscape = "inkscape" -imagemagick = "magick" +imagemagick = "convert" def execute_cmd(cmd) puts cmd @@ -99,7 +68,7 @@ images.each do |source_filename, settings| base_width = svg.root["width"].to_i base_height = svg.root["height"].to_i - guides = svg.find(".//sodipodi:guide","sodipodi:http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd") + guides = svg.find(".//sodipodi:guide") resolutions.each do |resolution, factor| output_filename, base_size = settings @@ -112,13 +81,7 @@ images.each do |source_filename, settings| height = factor * base_height end - output_parts = output_filename.split('/') - - if output_parts.count != 2 - path = "../src/main/res/drawable-#{resolution}/#{output_filename}.png" - else - path = "../src/#{output_parts[0]}/res/drawable-#{resolution}/#{output_parts[1]}.png" - end + path = "../src/main/res/drawable-#{resolution}/#{output_filename}.png" execute_cmd "#{inkscape} -f #{source_filename} -z -C -w #{width} -h #{height} -e #{path}" top = [] @@ -155,7 +118,7 @@ images.each do |source_filename, settings| execute_cmd "#{imagemagick} -background none PNG32:#{path} -gravity center -extent #{width+2}x#{height+2} PNG32:#{path}" - draw_format = "-draw \"line %d,%d %d,%d\"" + draw_format = "-draw \"rectangle %d,%d %d,%d\"" top_line = draw_format % [top.min + 1, 0, top.max, 0] right_line = draw_format % [width + 1, right.min + 1, width + 1, right.max] bottom_line = draw_format % [bottom.min + 1, height + 1, bottom.max, height + 1] diff --git a/art/schulchat/logo.png b/art/schulchat/logo.png new file mode 100644 index 000000000..e134d3e91 Binary files /dev/null and b/art/schulchat/logo.png differ diff --git a/art/screenshots/de/Screenshot_20180128-201225.png b/art/screenshots/de/Screenshot_20180128-201225.png new file mode 100644 index 000000000..e789a511f Binary files /dev/null and b/art/screenshots/de/Screenshot_20180128-201225.png differ diff --git a/art/screenshots/de/Screenshot_20180128-201239.png b/art/screenshots/de/Screenshot_20180128-201239.png new file mode 100644 index 000000000..c92ef50bc Binary files /dev/null and b/art/screenshots/de/Screenshot_20180128-201239.png differ diff --git a/art/screenshots/de/Screenshot_20180128-201246.png b/art/screenshots/de/Screenshot_20180128-201246.png new file mode 100644 index 000000000..6d6a9eac0 Binary files /dev/null and b/art/screenshots/de/Screenshot_20180128-201246.png differ diff --git a/art/screenshots/de/Screenshot_20180128-201335.png b/art/screenshots/de/Screenshot_20180128-201335.png new file mode 100644 index 000000000..29b840376 Binary files /dev/null and b/art/screenshots/de/Screenshot_20180128-201335.png differ diff --git a/art/screenshots/de/Screenshot_20180128-201731.png b/art/screenshots/de/Screenshot_20180128-201731.png new file mode 100644 index 000000000..a25fe1e60 Binary files /dev/null and b/art/screenshots/de/Screenshot_20180128-201731.png differ diff --git a/art/screenshots/de/Screenshot_20180128-201735.png b/art/screenshots/de/Screenshot_20180128-201735.png new file mode 100644 index 000000000..f6c9794dc Binary files /dev/null and b/art/screenshots/de/Screenshot_20180128-201735.png differ diff --git a/art/screenshots/de/Screenshot_20180220-113650.png b/art/screenshots/de/Screenshot_20180220-113650.png new file mode 100644 index 000000000..66d3eb34e Binary files /dev/null and b/art/screenshots/de/Screenshot_20180220-113650.png differ diff --git a/art/screenshots/de/Screenshot_20180220-113816.png b/art/screenshots/de/Screenshot_20180220-113816.png new file mode 100644 index 000000000..d7162d906 Binary files /dev/null and b/art/screenshots/de/Screenshot_20180220-113816.png differ diff --git a/art/screenshots/de/Screenshot_20180220-113836.png b/art/screenshots/de/Screenshot_20180220-113836.png new file mode 100644 index 000000000..b2b1cf8d4 Binary files /dev/null and b/art/screenshots/de/Screenshot_20180220-113836.png differ diff --git a/art/screenshots/de/Screenshot_20180220-113952.png b/art/screenshots/de/Screenshot_20180220-113952.png new file mode 100644 index 000000000..d48bbedda Binary files /dev/null and b/art/screenshots/de/Screenshot_20180220-113952.png differ diff --git a/art/screenshots/de/Screenshot_20180220-114022.png b/art/screenshots/de/Screenshot_20180220-114022.png new file mode 100644 index 000000000..2bbd7f6d3 Binary files /dev/null and b/art/screenshots/de/Screenshot_20180220-114022.png differ diff --git a/art/screenshots/de/index b/art/screenshots/de/index new file mode 100644 index 000000000..12b04f958 --- /dev/null +++ b/art/screenshots/de/index @@ -0,0 +1 @@ +German screenshots diff --git a/art/screenshots/en/Screenshot_20180220-114149.png b/art/screenshots/en/Screenshot_20180220-114149.png new file mode 100644 index 000000000..9b377eefd Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-114149.png differ diff --git a/art/screenshots/en/Screenshot_20180220-114227.png b/art/screenshots/en/Screenshot_20180220-114227.png new file mode 100644 index 000000000..79c0ff9ab Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-114227.png differ diff --git a/art/screenshots/en/Screenshot_20180220-114239.png b/art/screenshots/en/Screenshot_20180220-114239.png new file mode 100644 index 000000000..cbeb1cf15 Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-114239.png differ diff --git a/art/screenshots/en/Screenshot_20180220-114301.png b/art/screenshots/en/Screenshot_20180220-114301.png new file mode 100644 index 000000000..64eedc964 Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-114301.png differ diff --git a/art/screenshots/en/Screenshot_20180220-114333.png b/art/screenshots/en/Screenshot_20180220-114333.png new file mode 100644 index 000000000..4d6abdf1a Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-114333.png differ diff --git a/art/screenshots/en/Screenshot_20180220-114345.png b/art/screenshots/en/Screenshot_20180220-114345.png new file mode 100644 index 000000000..b4318fb8b Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-114345.png differ diff --git a/art/screenshots/en/Screenshot_20180220-115013.png b/art/screenshots/en/Screenshot_20180220-115013.png new file mode 100644 index 000000000..dfdf4b54e Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-115013.png differ diff --git a/art/screenshots/en/Screenshot_20180220-115016.png b/art/screenshots/en/Screenshot_20180220-115016.png new file mode 100644 index 000000000..2511bb7f6 Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-115016.png differ diff --git a/art/screenshots/en/Screenshot_20180220-115506.png b/art/screenshots/en/Screenshot_20180220-115506.png new file mode 100644 index 000000000..6470f2fed Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-115506.png differ diff --git a/art/screenshots/en/Screenshot_20180220-115525.png b/art/screenshots/en/Screenshot_20180220-115525.png new file mode 100644 index 000000000..7b4446b11 Binary files /dev/null and b/art/screenshots/en/Screenshot_20180220-115525.png differ diff --git a/art/screenshots/en/index b/art/screenshots/en/index new file mode 100644 index 000000000..c5c714f84 --- /dev/null +++ b/art/screenshots/en/index @@ -0,0 +1 @@ +English screenshots diff --git a/art/splash_logo.svg b/art/splash_logo.svg deleted file mode 120000 index e62f735a0..000000000 --- a/art/splash_logo.svg +++ /dev/null @@ -1 +0,0 @@ -ic_launcher.svg \ No newline at end of file diff --git a/build.gradle b/build.gradle index 462d60db0..e062094cd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,123 +1,146 @@ -import com.android.build.OutputFile - // Top-level build file where you can add configuration options common to all // sub-projects/modules. buildscript { repositories { - google() jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:3.6.1' } } apply plugin: 'com.android.application' +allprojects { + repositories { + google() + jcenter() + mavenCentral() + maven { + url "https://jitpack.io" + } + } +} + repositories { google() jcenter() mavenCentral() + maven { + url "https://jitpack.io" + } } configurations { playstoreImplementation - compatImplementation - conversationsFreeCompatImplementation - conversationsPlaystoreCompatImplementation - conversationsPlaystoreSystemImplementation - quicksyFreeCompatImplementation - quicksyImplementation -} - -ext { - supportLibVersion = '28.0.0' + gitImplementation + compile.exclude group: 'org.jetbrains' , module:'annotations' } dependencies { - //should remain that low because later versions introduce dependency to androidx (not sure exactly from what version) - playstoreImplementation('com.google.firebase:firebase-messaging:17.3.4') { + implementation project(':libs:android-transcoder') + playstoreImplementation('com.google.firebase:firebase-messaging:20.1.3') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } - conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:1.1.2") - conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:1.1.2") + playstoreImplementation 'com.android.installreferrer:installreferrer:1.1.2' implementation 'org.sufficientlysecure:openpgp-api:10.0' - implementation('com.theartofdev.edmodo:android-image-cropper:2.7.+') { + implementation('com.theartofdev.edmodo:android-image-cropper:2.8.0') { exclude group: 'com.android.support', module: 'appcompat-v7' exclude group: 'com.android.support', module: 'exifinterface' } - implementation "com.android.support:support-v13:$supportLibVersion" - implementation "com.android.support:appcompat-v7:$supportLibVersion" - implementation "com.android.support:exifinterface:$supportLibVersion" - implementation "com.android.support:cardview-v7:$supportLibVersion" - implementation "com.android.support:support-emoji:$supportLibVersion" - implementation "com.android.support:design:$supportLibVersion" - compatImplementation "com.android.support:support-emoji-appcompat:$supportLibVersion" - conversationsFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion" - quicksyFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion" implementation 'org.bouncycastle:bcmail-jdk15on:1.64' - //zxing stopped supporting Java 7 so we have to stick with 3.3.3 - //https://github.com/zxing/zxing/issues/1170 - implementation 'com.google.zxing:core:3.3.3' + implementation 'org.jitsi:org.otr4j:0.22' + implementation 'org.gnu.inet:libidn:1.15' + implementation 'com.google.zxing:core:3.3.3' // > 3.3.x not working below SDK 24 implementation 'de.measite.minidns:minidns-hla:0.2.4' implementation 'me.leolin:ShortcutBadger:1.1.22@aar' implementation 'org.whispersystems:signal-protocol-java:2.6.2' implementation 'com.makeramen:roundedimageview:2.3.0' - implementation "com.wefika:flowlayout:0.4.1" - implementation 'net.ypresto.androidtranscoder:android-transcoder:0.3.0' + implementation 'jetty:javax.servlet:5.1.12' + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.legacy:legacy-support-v13:1.0.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.exifinterface:exifinterface:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.emoji:emoji:1.0.0' + gitImplementation 'androidx.emoji:emoji-appcompat:1.0.0' + gitImplementation 'androidx.emoji:emoji-bundled:1.0.0' + implementation 'com.google.android.material:material:1.0.0' // higher versions cause strange fab design + implementation 'androidx.cardview:cardview:1.0.0' // for compatibility + implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' + implementation 'com.google.android.exoplayer:exoplayer-core:2.11.3' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.3' + implementation 'pub.devrel:easypermissions:3.0.0' // version >= 3.0.0 needs android X libraries + implementation 'com.wefika:flowlayout:0.4.1' + implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.5' implementation project(':libs:xmpp-addr') - implementation 'org.osmdroid:osmdroid-android:6.1.5' implementation 'org.hsluv:hsluv:0.2' implementation 'org.conscrypt:conscrypt-android:2.2.1' + implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' // 1.2.15 is last working version for minSDK 16 implementation 'me.drakeet.support:toastcompat:1.1.0' - implementation "com.leinardi.android:speed-dial:2.0.1" - //retrofit needs to stick with 2.6.x (https://github.com/square/retrofit/blob/master/CHANGELOG.md) - implementation "com.squareup.retrofit2:retrofit:2.6.4" - implementation "com.squareup.retrofit2:converter-gson:2.6.4" - //okhttp needs to stick with 3.12.x - implementation 'com.squareup.okhttp3:okhttp:3.12.10' - implementation 'com.google.guava:guava:27.1-android' - quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.11.1' - implementation fileTree(include: ['libwebrtc-m81.aar'], dir: 'libs') + implementation 'org.osmdroid:osmdroid-android:6.1.5' + implementation 'com.leinardi.android:speed-dial:3.1.1' // version >= 3.0.0 needs android X libraries + implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'com.squareup.okhttp3:okhttp:3.12.10' // versions > 3.12.x don't support API level < 21 anymore + implementation 'com.squareup.retrofit2:retrofit:2.6.4' //retrofit needs to stick with 2.6.x (https://github.com/square/retrofit/blob/master/CHANGELOG.md) + implementation 'com.squareup.retrofit2:converter-gson:2.6.4' + implementation 'com.google.guava:guava:28.2-android' + implementation 'com.github.AppIntro:AppIntro:5.1.0' } ext { travisBuild = System.getenv("TRAVIS") == "true" preDexEnabled = System.getProperty("pre-dex", "true") - abiCodes = ['armeabi-v7a': 1, 'x86': 2, 'x86_64': 3, 'arm64-v8a': 4] } android { + compileSdkVersion 29 defaultConfig { minSdkVersion 16 - targetSdkVersion 28 - versionCode 382 - versionName "2.8.2" - archivesBaseName += "-$versionName" - applicationId "eu.siacs.conversations" - resValue "string", "applicationId", applicationId - resValue "string", "app_name", "Conversations" - buildConfigField "String", "LOGTAG", "\"conversations\"" - } + targetSdkVersion 29 - splits { - abi { - universalApk true - enable true - } + versionCode 299 + versionName "2.3.7" + //versionNameSuffix "beta_(2020-04-01)" // "-beta_(XXXX-XX-XX)" // activate for beta versions + //resConfigs "en" + + archivesBaseName += "-$versionName" + //archivesBaseName += "-$versionNameSuffix" // activate for beta versions + applicationId "de.pixart.messenger" + multiDexEnabled true + + buildConfigField("String", "LOGTAG", '"Pix-Art_Messenger"') + buildConfigField("String", "DOMAIN_LOCK", 'null') + buildConfigField("String", "MAGIC_CREATE_DOMAIN", '"blabber.im"') + buildConfigField("boolean", "SHOW_INTRO", 'true') + buildConfigField("String", "UPDATE_URL", '"https://xmpp.pix-art.de/Pix-Art_Messenger/update/"') + resValue "string", "applicationId", applicationId + resValue "string", "app_name", "Pix-Art Messenger" } dataBinding { enabled true } + packagingOptions { + //X86 + exclude "lib/x86/**" + //X86_64 + exclude "lib/x86_64/**" + //armeabi + exclude "lib/armeabi/**" + } + dexOptions { // Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false. preDexLibraries = preDexEnabled && !travisBuild + javaMaxHeapSize "4g" jumboMode true } @@ -126,124 +149,54 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - flavorDimensions("mode", "distribution", "emoji") + flavorDimensions("distribution") productFlavors { - - quicksy { - dimension "mode" - applicationId = "im.quicksy.client" - resValue "string", "app_name", "Quicksy" - resValue "string", "applicationId", applicationId - buildConfigField "String", "LOGTAG", "\"quicksy\"" - } - - conversations { - dimension "mode" - } - playstore { dimension "distribution" - versionNameSuffix "+p" + versionNameSuffix "-playstore" } - free { + git { dimension "distribution" - versionNameSuffix "+f" - } - system { - dimension "emoji" - versionNameSuffix "s" - } - compat { - dimension "emoji" - versionNameSuffix "c" } } - - sourceSets { - quicksyFreeCompat { - java { - srcDir 'src/freeCompat/java' - } - } - quicksyPlaystoreCompat { - java { - srcDir 'src/playstoreCompat/java' - } - res { - srcDir 'src/playstoreCompat/res' - srcDir 'src/quicksyPlaystore/res' - } - } - quicksyPlaystoreSystem { - res { - srcDir 'src/quicksyPlaystore/res' - } - } - conversationsFreeCompat { - java { - srcDir 'src/freeCompat/java' - srcDir 'src/conversationsFree/java' - } - } - conversationsFreeSystem { - java { - srcDir 'src/conversationsFree/java' - } - } - conversationsPlaystoreCompat { - java { - srcDir 'src/playstoreCompat/java' - srcDir 'src/conversationsPlaystore/java' - } - res { - srcDir 'src/playstoreCompat/res' - srcDir 'src/conversationsPlaystore/res' - } - } - conversationsPlaystoreSystem { - java { - srcDir 'src/conversationsPlaystore/java' - } - res { - srcDir 'src/conversationsPlaystore/res' - } - } - } - - buildTypes { - release { - shrinkResources true - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - versionNameSuffix "r" - } - debug { - shrinkResources true - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - versionNameSuffix "d" - } - } - - - if (new File("signing.properties").exists()) { - Properties props = new Properties() - props.load(new FileInputStream(file("signing.properties"))) - + if (project.hasProperty('mStoreFile') && + project.hasProperty('mStorePassword') && + project.hasProperty('mKeyAlias') && + project.hasProperty('mKeyPassword')) { signingConfigs { release { - storeFile file(props['keystore']) - storePassword props['keystore.password'] - keyAlias props['keystore.alias'] - keyPassword props['keystore.password'] + storeFile file(mStoreFile) + storePassword mStorePassword + keyAlias mKeyAlias + keyPassword mKeyPassword } } - buildTypes.release.signingConfig = signingConfigs.release + buildTypes { + release { + debuggable false + signingConfig = signingConfigs.release + minifyEnabled true + shrinkResources true + runProguard true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + debug { + debuggable true + buildTypes.release.signingConfig = null + minifyEnabled true + shrinkResources true + runProguard true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + } } lintOptions { - disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource' + disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource', 'RestrictedApi' + //abortOnError false } subprojects { @@ -264,16 +217,4 @@ android { exclude 'META-INF/BCKEY.DSA' exclude 'META-INF/BCKEY.SF' } - - android.applicationVariants.all { variant -> - variant.outputs.each { output -> - def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) - if (baseAbiVersionCode != null) { - output.versionCodeOverride = (100 * variant.versionCode) + baseAbiVersionCode - } else { - output.versionCodeOverride = (100 * variant.versionCode) - } - } - - } -} +} \ No newline at end of file diff --git a/contributing.json b/contributing.json new file mode 100644 index 000000000..32b3ddfb4 --- /dev/null +++ b/contributing.json @@ -0,0 +1,40 @@ +// https://gitmagic.io/rules +{ + "commit": { + "subject_cannot_be_empty": true, + "subject_must_be_longer_than": 4, + "subject_must_be_shorter_than": 101, + "subject_lines_must_be_shorter_than": 51, + "subject_must_be_single_line": true, + "subject_must_be_in_tense": "imperative", + "subject_must_start_with_case": "lower", + "subject_must_not_end_with_dot": true, + + "body_lines_must_be_shorter_than": 73 + }, + "pull_request": { + "subject_cannot_be_empty": true, + "subject_must_be_longer_than": 4, + "subject_must_be_shorter_than": 101, + "subject_must_be_in_tense": "imperative", + "subject_must_start_with_case": "upper", + "subject_must_not_end_with_dot": true, + + "body_cannot_be_empty": true, + "body_must_include_verification_steps": true, + "body_must_include_screenshot": ["html", "css"] + }, + "issue": { + "subject_cannot_be_empty": true, + "subject_must_be_longer_than": 4, + "subject_must_be_shorter_than": 101, + "subject_must_be_in_tense": "imperative", + "subject_must_start_with_case": "upper", + "subject_must_not_end_with_dot": true, + + "body_cannot_be_empty": true, + "body_must_include_reproduction_steps": ["bug"], + + "label_must_be_set": true + } +} diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..4602c9426 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,6 @@ +files: + - source: /src/main/res/values/strings.xml + translation: /src/main/res/values-%two_letters_code%/strings.xml + translate_content: '0' + translate_attributes: '0' + content_segmentation: '0' diff --git a/doap.rdf b/doap.rdf deleted file mode 100644 index 7dfd8f065..000000000 --- a/doap.rdf +++ /dev/null @@ -1,340 +0,0 @@ - - - - - Conversations - - 2014-01-14 - - Android XMPP Client - - Conversations is an open source XMPP/Jabber client for the Android platform - - - - - - - - - - - - en - - - - Java - - Android - - - - - - - - - Daniel Gultsch - - - - - - - - - - - - - - - - - - - - complete - 1.4 - - - - - - complete - 2.5rc3 - - - - - - complete - 1.32.0 - - - - - - complete - 1.1 - - - - - - complete - 1.1.3 - - - - - - complete - 2.1 - - - - - - complete - 1.1 - - - - - - complete - 1.5.1 - - - - - - complete - 1.2.1 - Avatar, Nick, OMEMO - - - - - - complete - 1.1.2 - File transfer only - - - - - - complete - 1.1 - read only - - - - - - complete - 1.4.0 - - - - - - complete - 1.3 - - - - - - complete - 1.6 - - - - - - complete - 2.0.1 - - - - - - complete - 0.19.1 - - - - - - complete - 1.3 - - - - - - complete - 1.0 - - - - - - complete - 1.2 - - - - - - complete - 1.0.3 - - - - - - complete - 1.0 - - - - - - complete - 0.13.1 - - - - - - complete - 1.0 - - - - - - complete - 0.6.3 - - - - - - complete - 1.0.2 - opt-in - - - - - - complete - 0.3 - - - - - - complete - 0.3.0 - - - - - - complete - 0.4.0 - Only available in the version distributed over Google Play - - - - - - complete - 1.1.0 - - - - - - complete - 0.2 - - - - - - complete - 0.3.0 - - - - - - complete - 0.1.2 - 2.5.8 - - - - - - complete - 0.6.0 - 2.3.1 - - - - - - complete - 0.1.4 - 1.22.0 - - - - - - complete - 0.1 - 2.5.8 - - - - - - complete - 0.2.1 - - - - - - complete - 1.0.1 - 2.5.4 - - - - - - complete - 0.2.0 - - - - - - 2.5.8 - 2019-09-12 - - - - - diff --git a/docs/XEPs.md b/docs/XEPs.md index eab494fc1..19aa9b64e 100644 --- a/docs/XEPs.md +++ b/docs/XEPs.md @@ -28,5 +28,5 @@ * XEP-0357: Push Notifications * XEP-0363: HTTP File Upload * XEP-0368: SRV records for XMPP over TLS -* XEP-0377: Spam Reporting +* XEP-0377: Spam Reporting * XEP-0384: OMEMO Encryption diff --git a/docs/encryption.md b/docs/encryption.md new file mode 100644 index 000000000..ee17352ee --- /dev/null +++ b/docs/encryption.md @@ -0,0 +1,18 @@ +Dear users, + +Pix-Art Messenger will remove the implementation of OTR encryption by 30th June of 2020. + +Unfortunately, it does not make sense to continue support on a rather out-dated technology, even as we see some users keep using it. For the moment, OTR (as well as OpenPGP) can be activated via the expert settings for advanced users. Pix-Art Messenger always tries to be usable for even [non-technical users](https://github.com/kriztan/Pix-Art-Messenger/issues/227), OTR however is not counted as appropriate for this. + +Please consider to use OMEMO in the future, many other clients has implemented this, have a look at [omemo.top](https://omemo.top/). However, if you really need to continue using it, please refer to e.g. [Miranda](https://www.miranda-ng.org/de/), [Pidgin plugin](https://github.com/gkdr/lurch/), [Profanity](https://profanity-im.github.io/) or [Coy.im](https://coy.im/). You are also able to fork Pix-Art Messenger and continue the implementation on your own. + +Please consider the differences between encryption protocols and also advantages of OMEMO: + + +Source: [https://conversations.im/omemo](https://conversations.im/omemo) + +Some limitations of OTR: [wikipedia.org/wiki/Off-the-Record](https://en.wikipedia.org/wiki/Off-the-Record_Messaging#Limitations) + +Please inform your contacts who may also use it. + +Mastodon: https://social.tchncs.de/@pixart diff --git a/docs/observations.md b/docs/observations.md index 0c0ef857e..71502424c 100644 --- a/docs/observations.md +++ b/docs/observations.md @@ -8,7 +8,7 @@ which make the life on mobile devices a lot easier but states that they are currently very few implementations of those XEPs. So I went ahead and implemented all of them in my Android XMPP client. -### General observations +###General observations The first thing I noticed is that XMPP is actually okish designed. If you were to design a new chat protocol today you probably wouldn’t choose XML again however the protocol basically consists of only three different packages which @@ -16,7 +16,7 @@ are quickly hidden under some sort of abstraction layer within your library. Getting from zero to sending messages to other users actually was very simple and straight forward. But then came the XEPs. -### Multi-User Chat +###Multi-User Chat The first one was XEP-0045 Multi-User Chat. This is the one XEP of the XEPs I’m going to mention in my article which is actually wildly adopted. Most clients and servers I know of support MUC. However the level of completeness varies. @@ -34,7 +34,7 @@ desktop for example) one wouldn’t have to name oneself `userDesktop` and strange side effects. Prosody for example doesn’t allow a user to change its name once two clients are “merged” by having the same nick. -### Carbons and Stream Management +###Carbons and Stream Management Two of the other XEPs Lukas mentions — Carbons (XEP-0280) and Stream Management (XEP-0198) — were actually fairly easy to implement. The only challenges were to find a server to support them (I ended up running my own Prosody server) and a @@ -44,7 +44,7 @@ mobile device. I had sessions running for up to 24 hours with a walking outside, loosing mobile coverage for a few minutes and so on. The only limitation was that I had to keep on developing and reinstalling my app. -### Off the record +###Off the record And then came OTR... This is were I spend the most time debugging stuff and trying to get things right and compatible with other clients. This is the part were I want to help other developers not to make the same mistakes and maybe @@ -73,7 +73,7 @@ honor the private tag on outgoing messages. While this is easily fixed I presume that having both the private and the no-copy tag will make it more compatible with servers or clients I don’t know about yet. -#### Rules to follow when implementing OTR +####Rules to follow when implementing OTR To summarize my observations on implementing OTR in XMPP let me make the following three statements. diff --git a/fastlane/metadata/android/en-US/changelogs/349.txt b/fastlane/metadata/android/en-US/changelogs/349.txt deleted file mode 100644 index b8f22adef..000000000 --- a/fastlane/metadata/android/en-US/changelogs/349.txt +++ /dev/null @@ -1,4 +0,0 @@ -* Introduce expert setting to perform channel discovery on local server instead of search.jabber.network -* Enable delivery check marks by default and remove setting -* Enable ‘Send button indicates status’ by default and remove setting -* Move Backup and Foreground Service settings to main screen diff --git a/fastlane/metadata/android/en-US/changelogs/351.txt b/fastlane/metadata/android/en-US/changelogs/351.txt deleted file mode 100644 index 8fabff2f1..000000000 --- a/fastlane/metadata/android/en-US/changelogs/351.txt +++ /dev/null @@ -1,3 +0,0 @@ -* fixes for Jingle IBB file transfer -* fixes for repeated corrections filling up the database -* switched to Last Message Correction v1.1 diff --git a/fastlane/metadata/android/en-US/changelogs/353.txt b/fastlane/metadata/android/en-US/changelogs/353.txt deleted file mode 100644 index 63b829a80..000000000 --- a/fastlane/metadata/android/en-US/changelogs/353.txt +++ /dev/null @@ -1,4 +0,0 @@ -* let users set their own nick name -* resume download of OMEMO encrypted files -* Channels now use '#' as symbol in avatar -* Quicksy uses 'always' as OMEMO encryption default (hides lock icon) diff --git a/fastlane/metadata/android/en-US/changelogs/360.txt b/fastlane/metadata/android/en-US/changelogs/360.txt deleted file mode 100644 index 87b92f033..000000000 --- a/fastlane/metadata/android/en-US/changelogs/360.txt +++ /dev/null @@ -1 +0,0 @@ -* Support for ?register and ?register;preauth XMPP uri parameters diff --git a/fastlane/metadata/android/en-US/changelogs/362.txt b/fastlane/metadata/android/en-US/changelogs/362.txt deleted file mode 100644 index f4bbc2ab1..000000000 --- a/fastlane/metadata/android/en-US/changelogs/362.txt +++ /dev/null @@ -1 +0,0 @@ -* Support automatic theme switching on Android 10 diff --git a/fastlane/metadata/android/en-US/changelogs/364.txt b/fastlane/metadata/android/en-US/changelogs/364.txt deleted file mode 100644 index ed2c80678..000000000 --- a/fastlane/metadata/android/en-US/changelogs/364.txt +++ /dev/null @@ -1,2 +0,0 @@ -* Provide PDF preview on Android 5+ -* Use 12 byte IVs for OMEMO diff --git a/fastlane/metadata/android/en-US/changelogs/367.txt b/fastlane/metadata/android/en-US/changelogs/367.txt deleted file mode 100644 index 2f42bd95f..000000000 --- a/fastlane/metadata/android/en-US/changelogs/367.txt +++ /dev/null @@ -1,2 +0,0 @@ -* Fix avatar selection on some Android 10 devices -* Fix file transfer for larger files diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt deleted file mode 100644 index d03cefd88..000000000 --- a/fastlane/metadata/android/en-US/full_description.txt +++ /dev/null @@ -1,39 +0,0 @@ -Easy to use, reliable, battery friendly. With built-in support for images, group chats and e2e encryption. - -Design principles: - -* Be as beautiful and easy to use as possible without sacrificing security or privacy -* Rely on existing, well established protocols -* Do not require a Google Account or specifically Google Cloud Messaging (GCM) -* Require as few permissions as possible - -Features: - -* End-to-end encryption with either OMEMO or OpenPGP -* Sending and receiving images -* Make audio and video calls -* Intuitive UI that follows Android Design guidelines -* Pictures / Avatars for your Contacts -* Syncs with desktop client -* Conferences (with support for bookmarks) -* Address book integration -* Multiple accounts / unified inbox -* Very low impact on battery life - -Conversations makes it very easy to create an account on the conversations.im server. Using that server comes with an annual fee of 8 Euro after a 6 month trial period. However Conversations will work with any other XMPP server as well. A lot of XMPP servers are run by volunteers and are free of charge. - -XMPP Features: - -Conversations works with every XMPP server out there. However XMPP is an extensible protocol. These extensions are standardized as well in so called XEP’s. Conversations supports a couple of those to make the overall user experience better. There is a chance that your current XMPP server does not support these extensions. Therefore to get the most out of Conversations you should consider either switching to an XMPP server that does or - even better - run your own XMPP server for you and your friends. - -These XEPs are - as of now: - -* XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Will be used to transfer files if both parties are behind a firewall (NAT). -* XEP-0163: Personal Eventing Protocol for avatars -* XEP-0191: Blocking command lets you blacklist spammers or block contacts without removing them from your roster. -* XEP-0198: Stream Management allows XMPP to survive small network outages and changes of the underlying TCP connection. -* XEP-0280: Message Carbons which automatically syncs the messages you send to your desktop client and thus allows you to switch seamlessly from your mobile client to your desktop client and back within one conversation. -* XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections -* XEP-0313: Message Archive Management synchronize message history with the server. Catch up with messages that were sent while Conversations was offline. -* XEP-0352: Client State Indication lets the server know whether or not Conversations is in the background. Allows the server to save bandwidth by withholding unimportant packages. -* XEP-0363: HTTP File Upload allows you to share files in conferences and with offline contacts. Requires an additional component on your server. diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png deleted file mode 100644 index 28128322f..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png deleted file mode 100644 index a3ea7d690..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png deleted file mode 100644 index 76d112650..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png deleted file mode 100644 index bf13cd25d..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png deleted file mode 100644 index 44ce5f93b..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png deleted file mode 100644 index 94840e6c6..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png deleted file mode 100644 index 0d18d8ce6..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/01.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/01.png deleted file mode 100644 index 790cb6820..000000000 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/01.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/02.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/02.png deleted file mode 100644 index a20c67f25..000000000 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/02.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/03.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/03.png deleted file mode 100644 index 03b513ea4..000000000 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/03.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/04.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/04.png deleted file mode 100644 index f9391b38b..000000000 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/04.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt deleted file mode 100644 index 4b41cb27b..000000000 --- a/fastlane/metadata/android/en-US/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -An encrypted, user friendly XMPP instant messaging client optimized for mobile \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..4ae591228 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +android.enableJetifier=true +android.useAndroidX=true +org.gradle.jvmargs=-Xmx2048M +org.gradle.parallel=true +android.enableR8=true +android.enableR8.fullMode=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8..f6b961fd5 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 852a0aba2..32b31286a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Thu Mar 19 11:51:26 CET 2020 +#Fri Sep 06 22:42:08 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 91a7e269e..cccdd3d51 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,47 +6,6 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" @@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do fi done SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" -cd "$SAVED" >&- +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730b..e95643d6a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/libs/android-transcoder/.gitignore b/libs/android-transcoder/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/libs/android-transcoder/.gitignore @@ -0,0 +1 @@ +/build diff --git a/libs/android-transcoder/CHANGELOG.md b/libs/android-transcoder/CHANGELOG.md new file mode 100644 index 000000000..a79483bd0 --- /dev/null +++ b/libs/android-transcoder/CHANGELOG.md @@ -0,0 +1,17 @@ +## 0.3.0 +- Fix cancel() sometimes not working. (Thanks @strayerM and @PinkFloyded) +- Geolocation support on API>=19. (Thanks @hkurokawa) + +## 0.2.0 +- Experimental audio transcoding support. (Thanks @aaron112) +- Fix transcode does not run on Huawei Ascend P7. (Thanks @spiritedRunning) +- Fix race condition caused by not closing output before callback. (Thanks @ryanwilliams83) + +## 0.1.10 +- `Future` support. (Thanks @MaiKambayashi) + +## 0.1.X +- Stability updates. (Thanks @ozyozyo) + +## 0.1.0 +- First release. diff --git a/libs/android-transcoder/build.gradle b/libs/android-transcoder/build.gradle new file mode 100644 index 000000000..14f5614e6 --- /dev/null +++ b/libs/android-transcoder/build.gradle @@ -0,0 +1,30 @@ +buildscript { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation 'androidx.legacy:legacy-support-v13:1.0.0' +} + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + defaultConfig { + minSdkVersion 18 + targetSdkVersion 28 + } + + buildTypes { + release { + zipAlignEnabled true + minifyEnabled true + shrinkResources false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} \ No newline at end of file diff --git a/libs/android-transcoder/proguard-rules.pro b/libs/android-transcoder/proguard-rules.pro new file mode 100644 index 000000000..91dd3543e --- /dev/null +++ b/libs/android-transcoder/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/yuya.tanaka/devel/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/libs/android-transcoder/src/main/AndroidManifest.xml b/libs/android-transcoder/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8f4da3821 --- /dev/null +++ b/libs/android-transcoder/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java new file mode 100644 index 000000000..7ec32bc97 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder; + +import android.media.MediaFormat; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import net.ypresto.androidtranscoder.engine.MediaTranscoderEngine; +import net.ypresto.androidtranscoder.format.MediaFormatPresets; +import net.ypresto.androidtranscoder.format.MediaFormatStrategy; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class MediaTranscoder { + private static final String TAG = "MediaTranscoder"; + private static final int MAXIMUM_THREAD = 1; // TODO + private static volatile MediaTranscoder sMediaTranscoder; + private ThreadPoolExecutor mExecutor; + + private MediaTranscoder() { + mExecutor = new ThreadPoolExecutor( + 0, MAXIMUM_THREAD, 60, TimeUnit.SECONDS, + new LinkedBlockingQueue(), + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "MediaTranscoder-Worker"); + } + }); + } + + public static MediaTranscoder getInstance() { + if (sMediaTranscoder == null) { + synchronized (MediaTranscoder.class) { + if (sMediaTranscoder == null) { + sMediaTranscoder = new MediaTranscoder(); + } + } + } + return sMediaTranscoder; + } + + /** + * Transcodes video file asynchronously. + * Audio track will be kept unchanged. + * + * @param inFileDescriptor FileDescriptor for input. + * @param outPath File path for output. + * @param listener Listener instance for callback. + * @deprecated Use {@link #transcodeVideo(FileDescriptor, String, MediaFormatStrategy, MediaTranscoder.Listener)} which accepts output video format. + */ + @Deprecated + public Future transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final Listener listener) { + return transcodeVideo(inFileDescriptor, outPath, new MediaFormatStrategy() { + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + return MediaFormatPresets.getExportPreset960x540(); + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + return null; + } + }, listener); + } + + /** + * Transcodes video file asynchronously. + * Audio track will be kept unchanged. + * + * @param inPath File path for input. + * @param outPath File path for output. + * @param outFormatStrategy Strategy for output video format. + * @param listener Listener instance for callback. + * @throws IOException if input file could not be read. + */ + public Future transcodeVideo(final String inPath, final String outPath, final MediaFormatStrategy outFormatStrategy, final Listener listener) throws IOException { + FileInputStream fileInputStream = null; + FileDescriptor inFileDescriptor; + try { + fileInputStream = new FileInputStream(inPath); + inFileDescriptor = fileInputStream.getFD(); + } catch (IOException e) { + if (fileInputStream != null) { + try { + fileInputStream.close(); + } catch (IOException eClose) { + Log.e(TAG, "Can't close input stream: ", eClose); + } + } + throw e; + } + final FileInputStream finalFileInputStream = fileInputStream; + return transcodeVideo(inFileDescriptor, outPath, outFormatStrategy, new Listener() { + @Override + public void onTranscodeProgress(double progress) { + listener.onTranscodeProgress(progress); + } + + @Override + public void onTranscodeCompleted() { + closeStream(); + listener.onTranscodeCompleted(); + } + + @Override + public void onTranscodeCanceled() { + closeStream(); + listener.onTranscodeCanceled(); + } + + @Override + public void onTranscodeFailed(Exception exception) { + closeStream(); + listener.onTranscodeFailed(exception); + } + + private void closeStream() { + try { + finalFileInputStream.close(); + } catch (IOException e) { + Log.e(TAG, "Can't close input stream: ", e); + } + } + }); + } + + /** + * Transcodes video file asynchronously. + * Audio track will be kept unchanged. + * + * @param inFileDescriptor FileDescriptor for input. + * @param outPath File path for output. + * @param outFormatStrategy Strategy for output video format. + * @param listener Listener instance for callback. + */ + public Future transcodeVideo(final FileDescriptor inFileDescriptor, final String outPath, final MediaFormatStrategy outFormatStrategy, final Listener listener) { + Looper looper = Looper.myLooper(); + if (looper == null) looper = Looper.getMainLooper(); + final Handler handler = new Handler(looper); + final AtomicReference> futureReference = new AtomicReference<>(); + final Future createdFuture = mExecutor.submit(new Callable() { + @Override + public Void call() throws Exception { + Exception caughtException = null; + try { + MediaTranscoderEngine engine = new MediaTranscoderEngine(); + engine.setProgressCallback(new MediaTranscoderEngine.ProgressCallback() { + @Override + public void onProgress(final double progress) { + handler.post(new Runnable() { // TODO: reuse instance + @Override + public void run() { + listener.onTranscodeProgress(progress); + } + }); + } + }); + engine.setDataSource(inFileDescriptor); + engine.transcodeVideo(outPath, outFormatStrategy); + } catch (IOException e) { + Log.w(TAG, "Transcode failed: input file (fd: " + inFileDescriptor.toString() + ") not found" + + " or could not open output file ('" + outPath + "') .", e); + caughtException = e; + } catch (InterruptedException e) { + Log.i(TAG, "Cancel transcode video file.", e); + caughtException = e; + } catch (RuntimeException e) { + Log.e(TAG, "Fatal error while transcoding, this might be invalid format or bug in engine or Android.", e); + caughtException = e; + } + + final Exception exception = caughtException; + handler.post(new Runnable() { + @Override + public void run() { + if (exception == null) { + listener.onTranscodeCompleted(); + } else { + Future future = futureReference.get(); + if (future != null && future.isCancelled()) { + listener.onTranscodeCanceled(); + } else { + listener.onTranscodeFailed(exception); + } + } + } + }); + + if (exception != null) throw exception; + return null; + } + }); + futureReference.set(createdFuture); + return createdFuture; + } + + public interface Listener { + /** + * Called to notify progress. + * + * @param progress Progress in [0.0, 1.0] range, or negative value if progress is unknown. + */ + void onTranscodeProgress(double progress); + + /** + * Called when transcode completed. + */ + void onTranscodeCompleted(); + + /** + * Called when transcode canceled. + */ + void onTranscodeCanceled(); + + /** + * Called when transcode failed. + * + * @param exception Exception thrown from {@link MediaTranscoderEngine#transcodeVideo(String, MediaFormatStrategy)}. + * Note that it IS NOT {@link java.lang.Throwable}. This means {@link java.lang.Error} won't be caught. + */ + void onTranscodeFailed(Exception exception); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/compat/MediaCodecBufferCompatWrapper.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/compat/MediaCodecBufferCompatWrapper.java new file mode 100644 index 000000000..e36db4b72 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/compat/MediaCodecBufferCompatWrapper.java @@ -0,0 +1,42 @@ +package net.ypresto.androidtranscoder.compat; + +import android.media.MediaCodec; +import android.os.Build; + +import java.nio.ByteBuffer; + +/** + * A Wrapper to MediaCodec that facilitates the use of API-dependent get{Input/Output}Buffer methods, + * in order to prevent: http://stackoverflow.com/q/30646885 + */ +public class MediaCodecBufferCompatWrapper { + + final MediaCodec mMediaCodec; + final ByteBuffer[] mInputBuffers; + final ByteBuffer[] mOutputBuffers; + + public MediaCodecBufferCompatWrapper(MediaCodec mediaCodec) { + mMediaCodec = mediaCodec; + + if (Build.VERSION.SDK_INT < 21) { + mInputBuffers = mediaCodec.getInputBuffers(); + mOutputBuffers = mediaCodec.getOutputBuffers(); + } else { + mInputBuffers = mOutputBuffers = null; + } + } + + public ByteBuffer getInputBuffer(final int index) { + if (Build.VERSION.SDK_INT >= 21) { + return mMediaCodec.getInputBuffer(index); + } + return mInputBuffers[index]; + } + + public ByteBuffer getOutputBuffer(final int index) { + if (Build.VERSION.SDK_INT >= 21) { + return mMediaCodec.getOutputBuffer(index); + } + return mOutputBuffers[index]; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/compat/MediaCodecListCompat.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/compat/MediaCodecListCompat.java new file mode 100644 index 000000000..cc4c7a025 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/compat/MediaCodecListCompat.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.compat; + +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * This class emulates basic behavior of MediaCodecList in API level >= 21. + * TODO: implement delegate to MediaCodecList in newer API. + */ +public class MediaCodecListCompat { + public static final int REGULAR_CODECS = 0; + public static final int ALL_CODECS = 1; + + public MediaCodecListCompat(int kind) { + if (kind != REGULAR_CODECS) { + throw new UnsupportedOperationException("kind other than REGULAR_CODECS is not implemented."); + } + } + + public final String findDecoderForFormat(MediaFormat format) { + return findCoderForFormat(format, false); + } + + public final String findEncoderForFormat(MediaFormat format) { + return findCoderForFormat(format, true); + } + + private String findCoderForFormat(MediaFormat format, boolean findEncoder) { + String mimeType = format.getString(MediaFormat.KEY_MIME); + Iterator iterator = new MediaCodecInfoIterator(); + while (iterator.hasNext()) { + MediaCodecInfo codecInfo = iterator.next(); + if (codecInfo.isEncoder() != findEncoder) continue; + if (Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) { + return codecInfo.getName(); + } + } + return null; + } + + public final MediaCodecInfo[] getCodecInfos() { + int codecCount = getCodecCount(); + MediaCodecInfo[] codecInfos = new MediaCodecInfo[codecCount]; + Iterator iterator = new MediaCodecInfoIterator(); + for (int i = 0; i < codecCount; i++) { + codecInfos[i] = getCodecInfoAt(i); + } + return codecInfos; + } + + private static int getCodecCount() { + return MediaCodecList.getCodecCount(); + } + + private static MediaCodecInfo getCodecInfoAt(int index) { + return MediaCodecList.getCodecInfoAt(index); + } + + private final class MediaCodecInfoIterator implements Iterator { + private int mCodecCount = getCodecCount(); + private int mIndex = -1; + + @Override + public boolean hasNext() { + return mIndex + 1 < mCodecCount; + } + + @Override + public MediaCodecInfo next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + mIndex++; + return getCodecInfoAt(mIndex); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioChannel.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioChannel.java new file mode 100644 index 000000000..ac3e43efb --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioChannel.java @@ -0,0 +1,231 @@ +package net.ypresto.androidtranscoder.engine; + +import android.media.MediaCodec; +import android.media.MediaFormat; + +import net.ypresto.androidtranscoder.compat.MediaCodecBufferCompatWrapper; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Channel of raw audio from decoder to encoder. + * Performs the necessary conversion between different input & output audio formats. + * + * We currently support upmixing from mono to stereo & downmixing from stereo to mono. + * Sample rate conversion is not supported yet. + */ +class AudioChannel { + + private static class AudioBuffer { + int bufferIndex; + long presentationTimeUs; + ShortBuffer data; + } + + public static final int BUFFER_INDEX_END_OF_STREAM = -1; + + private static final int BYTES_PER_SHORT = 2; + private static final long MICROSECS_PER_SEC = 1000000; + + private final Queue mEmptyBuffers = new ArrayDeque<>(); + private final Queue mFilledBuffers = new ArrayDeque<>(); + + private final MediaCodec mDecoder; + private final MediaCodec mEncoder; + private final MediaFormat mEncodeFormat; + + private int mInputSampleRate; + private int mInputChannelCount; + private int mOutputChannelCount; + + private AudioRemixer mRemixer; + + private final MediaCodecBufferCompatWrapper mDecoderBuffers; + private final MediaCodecBufferCompatWrapper mEncoderBuffers; + + private final AudioBuffer mOverflowBuffer = new AudioBuffer(); + + private MediaFormat mActualDecodedFormat; + + + public AudioChannel(final MediaCodec decoder, + final MediaCodec encoder, final MediaFormat encodeFormat) { + mDecoder = decoder; + mEncoder = encoder; + mEncodeFormat = encodeFormat; + + mDecoderBuffers = new MediaCodecBufferCompatWrapper(mDecoder); + mEncoderBuffers = new MediaCodecBufferCompatWrapper(mEncoder); + } + + public void setActualDecodedFormat(final MediaFormat decodedFormat) { + mActualDecodedFormat = decodedFormat; + + mInputSampleRate = mActualDecodedFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); + if (mInputSampleRate != mEncodeFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)) { + throw new UnsupportedOperationException("Audio sample rate conversion not supported yet."); + } + + mInputChannelCount = mActualDecodedFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); + mOutputChannelCount = mEncodeFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); + + if (mInputChannelCount != 1 && mInputChannelCount != 2) { + throw new UnsupportedOperationException("Input channel count (" + mInputChannelCount + ") not supported."); + } + + if (mOutputChannelCount != 1 && mOutputChannelCount != 2) { + throw new UnsupportedOperationException("Output channel count (" + mOutputChannelCount + ") not supported."); + } + + if (mInputChannelCount > mOutputChannelCount) { + mRemixer = AudioRemixer.DOWNMIX; + } else if (mInputChannelCount < mOutputChannelCount) { + mRemixer = AudioRemixer.UPMIX; + } else { + mRemixer = AudioRemixer.PASSTHROUGH; + } + + mOverflowBuffer.presentationTimeUs = 0; + } + + public void drainDecoderBufferAndQueue(final int bufferIndex, final long presentationTimeUs) { + if (mActualDecodedFormat == null) { + throw new RuntimeException("Buffer received before format!"); + } + + final ByteBuffer data = + bufferIndex == BUFFER_INDEX_END_OF_STREAM ? + null : mDecoderBuffers.getOutputBuffer(bufferIndex); + + AudioBuffer buffer = mEmptyBuffers.poll(); + if (buffer == null) { + buffer = new AudioBuffer(); + } + + buffer.bufferIndex = bufferIndex; + buffer.presentationTimeUs = presentationTimeUs; + buffer.data = data == null ? null : data.asShortBuffer(); + + if (mOverflowBuffer.data == null) { + mOverflowBuffer.data = ByteBuffer + .allocateDirect(data.capacity()) + .order(ByteOrder.nativeOrder()) + .asShortBuffer(); + mOverflowBuffer.data.clear().flip(); + } + + mFilledBuffers.add(buffer); + } + + public boolean feedEncoder(long timeoutUs) { + final boolean hasOverflow = mOverflowBuffer.data != null && mOverflowBuffer.data.hasRemaining(); + if (mFilledBuffers.isEmpty() && !hasOverflow) { + // No audio data - Bail out + return false; + } + + final int encoderInBuffIndex = mEncoder.dequeueInputBuffer(timeoutUs); + if (encoderInBuffIndex < 0) { + // Encoder is full - Bail out + return false; + } + + // Drain overflow first + final ShortBuffer outBuffer = mEncoderBuffers.getInputBuffer(encoderInBuffIndex).asShortBuffer(); + if (hasOverflow) { + final long presentationTimeUs = drainOverflow(outBuffer); + mEncoder.queueInputBuffer(encoderInBuffIndex, + 0, outBuffer.position() * BYTES_PER_SHORT, + presentationTimeUs, 0); + return true; + } + + final AudioBuffer inBuffer = mFilledBuffers.poll(); + if (inBuffer.bufferIndex == BUFFER_INDEX_END_OF_STREAM) { + mEncoder.queueInputBuffer(encoderInBuffIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + return false; + } + + final long presentationTimeUs = remixAndMaybeFillOverflow(inBuffer, outBuffer); + mEncoder.queueInputBuffer(encoderInBuffIndex, + 0, outBuffer.position() * BYTES_PER_SHORT, + presentationTimeUs, 0); + if (inBuffer != null) { + mDecoder.releaseOutputBuffer(inBuffer.bufferIndex, false); + mEmptyBuffers.add(inBuffer); + } + + return true; + } + + private static long sampleCountToDurationUs(final int sampleCount, + final int sampleRate, + final int channelCount) { + return (sampleCount / (sampleRate * MICROSECS_PER_SEC)) / channelCount; + } + + private long drainOverflow(final ShortBuffer outBuff) { + final ShortBuffer overflowBuff = mOverflowBuffer.data; + final int overflowLimit = overflowBuff.limit(); + final int overflowSize = overflowBuff.remaining(); + + final long beginPresentationTimeUs = mOverflowBuffer.presentationTimeUs + + sampleCountToDurationUs(overflowBuff.position(), mInputSampleRate, mOutputChannelCount); + + outBuff.clear(); + // Limit overflowBuff to outBuff's capacity + overflowBuff.limit(outBuff.capacity()); + // Load overflowBuff onto outBuff + outBuff.put(overflowBuff); + + if (overflowSize >= outBuff.capacity()) { + // Overflow fully consumed - Reset + overflowBuff.clear().limit(0); + } else { + // Only partially consumed - Keep position & restore previous limit + overflowBuff.limit(overflowLimit); + } + + return beginPresentationTimeUs; + } + + private long remixAndMaybeFillOverflow(final AudioBuffer input, + final ShortBuffer outBuff) { + final ShortBuffer inBuff = input.data; + final ShortBuffer overflowBuff = mOverflowBuffer.data; + + outBuff.clear(); + + // Reset position to 0, and set limit to capacity (Since MediaCodec doesn't do that for us) + inBuff.clear(); + + if (inBuff.remaining() > outBuff.remaining()) { + // Overflow + // Limit inBuff to outBuff's capacity + inBuff.limit(outBuff.capacity()); + mRemixer.remix(inBuff, outBuff); + + // Reset limit to its own capacity & Keep position + inBuff.limit(inBuff.capacity()); + + // Remix the rest onto overflowBuffer + // NOTE: We should only reach this point when overflow buffer is empty + final long consumedDurationUs = + sampleCountToDurationUs(inBuff.position(), mInputSampleRate, mInputChannelCount); + mRemixer.remix(inBuff, overflowBuff); + + // Seal off overflowBuff & mark limit + overflowBuff.flip(); + mOverflowBuffer.presentationTimeUs = input.presentationTimeUs + consumedDurationUs; + } else { + // No overflow + mRemixer.remix(inBuff, outBuff); + } + + return input.presentationTimeUs; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioRemixer.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioRemixer.java new file mode 100644 index 000000000..5255a2219 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioRemixer.java @@ -0,0 +1,66 @@ +package net.ypresto.androidtranscoder.engine; + +import java.nio.ShortBuffer; + +public interface AudioRemixer { + void remix(final ShortBuffer inSBuff, final ShortBuffer outSBuff); + + AudioRemixer DOWNMIX = new AudioRemixer() { + private static final int SIGNED_SHORT_LIMIT = 32768; + private static final int UNSIGNED_SHORT_MAX = 65535; + + @Override + public void remix(final ShortBuffer inSBuff, final ShortBuffer outSBuff) { + // Down-mix stereo to mono + // Viktor Toth's algorithm - + // See: http://www.vttoth.com/CMS/index.php/technical-notes/68 + // http://stackoverflow.com/a/25102339 + final int inRemaining = inSBuff.remaining() / 2; + final int outSpace = outSBuff.remaining(); + + final int samplesToBeProcessed = Math.min(inRemaining, outSpace); + for (int i = 0; i < samplesToBeProcessed; ++i) { + // Convert to unsigned + final int a = inSBuff.get() + SIGNED_SHORT_LIMIT; + final int b = inSBuff.get() + SIGNED_SHORT_LIMIT; + int m; + // Pick the equation + if ((a < SIGNED_SHORT_LIMIT) || (b < SIGNED_SHORT_LIMIT)) { + // Viktor's first equation when both sources are "quiet" + // (i.e. less than middle of the dynamic range) + m = a * b / SIGNED_SHORT_LIMIT; + } else { + // Viktor's second equation when one or both sources are loud + m = 2 * (a + b) - (a * b) / SIGNED_SHORT_LIMIT - UNSIGNED_SHORT_MAX; + } + // Convert output back to signed short + if (m == UNSIGNED_SHORT_MAX + 1) m = UNSIGNED_SHORT_MAX; + outSBuff.put((short) (m - SIGNED_SHORT_LIMIT)); + } + } + }; + + AudioRemixer UPMIX = new AudioRemixer() { + @Override + public void remix(final ShortBuffer inSBuff, final ShortBuffer outSBuff) { + // Up-mix mono to stereo + final int inRemaining = inSBuff.remaining(); + final int outSpace = outSBuff.remaining() / 2; + + final int samplesToBeProcessed = Math.min(inRemaining, outSpace); + for (int i = 0; i < samplesToBeProcessed; ++i) { + final short inSample = inSBuff.get(); + outSBuff.put(inSample); + outSBuff.put(inSample); + } + } + }; + + AudioRemixer PASSTHROUGH = new AudioRemixer() { + @Override + public void remix(final ShortBuffer inSBuff, final ShortBuffer outSBuff) { + // Passthrough + outSBuff.put(inSBuff); + } + }; +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java new file mode 100644 index 000000000..47e0a61d9 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java @@ -0,0 +1,209 @@ +package net.ypresto.androidtranscoder.engine; + +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; + +import net.ypresto.androidtranscoder.compat.MediaCodecBufferCompatWrapper; + +import java.io.IOException; + +public class AudioTrackTranscoder implements TrackTranscoder { + + private static final QueuedMuxer.SampleType SAMPLE_TYPE = QueuedMuxer.SampleType.AUDIO; + + private static final int DRAIN_STATE_NONE = 0; + private static final int DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY = 1; + private static final int DRAIN_STATE_CONSUMED = 2; + + private final MediaExtractor mExtractor; + private final QueuedMuxer mMuxer; + private long mWrittenPresentationTimeUs; + + private final int mTrackIndex; + private final MediaFormat mInputFormat; + private final MediaFormat mOutputFormat; + + private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); + private MediaCodec mDecoder; + private MediaCodec mEncoder; + private MediaFormat mActualOutputFormat; + + private MediaCodecBufferCompatWrapper mDecoderBuffers; + private MediaCodecBufferCompatWrapper mEncoderBuffers; + + private boolean mIsExtractorEOS; + private boolean mIsDecoderEOS; + private boolean mIsEncoderEOS; + private boolean mDecoderStarted; + private boolean mEncoderStarted; + + private AudioChannel mAudioChannel; + + public AudioTrackTranscoder(MediaExtractor extractor, int trackIndex, + MediaFormat outputFormat, QueuedMuxer muxer) { + mExtractor = extractor; + mTrackIndex = trackIndex; + mOutputFormat = outputFormat; + mMuxer = muxer; + + mInputFormat = mExtractor.getTrackFormat(mTrackIndex); + } + + @Override + public void setup() { + mExtractor.selectTrack(mTrackIndex); + try { + mEncoder = MediaCodec.createEncoderByType(mOutputFormat.getString(MediaFormat.KEY_MIME)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + mEncoder.configure(mOutputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mEncoder.start(); + mEncoderStarted = true; + mEncoderBuffers = new MediaCodecBufferCompatWrapper(mEncoder); + + final MediaFormat inputFormat = mExtractor.getTrackFormat(mTrackIndex); + try { + mDecoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + mDecoder.configure(inputFormat, null, null, 0); + mDecoder.start(); + mDecoderStarted = true; + mDecoderBuffers = new MediaCodecBufferCompatWrapper(mDecoder); + + mAudioChannel = new AudioChannel(mDecoder, mEncoder, mOutputFormat); + } + + @Override + public MediaFormat getDeterminedFormat() { + return mInputFormat; + } + + @Override + public boolean stepPipeline() { + boolean busy = false; + + int status; + while (drainEncoder(0) != DRAIN_STATE_NONE) busy = true; + do { + status = drainDecoder(0); + if (status != DRAIN_STATE_NONE) busy = true; + // NOTE: not repeating to keep from deadlock when encoder is full. + } while (status == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY); + + while (mAudioChannel.feedEncoder(0)) busy = true; + while (drainExtractor(0) != DRAIN_STATE_NONE) busy = true; + + return busy; + } + + private int drainExtractor(long timeoutUs) { + if (mIsExtractorEOS) return DRAIN_STATE_NONE; + int trackIndex = mExtractor.getSampleTrackIndex(); + if (trackIndex >= 0 && trackIndex != mTrackIndex) { + return DRAIN_STATE_NONE; + } + + final int result = mDecoder.dequeueInputBuffer(timeoutUs); + if (result < 0) return DRAIN_STATE_NONE; + if (trackIndex < 0) { + mIsExtractorEOS = true; + mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + return DRAIN_STATE_NONE; + } + + final int sampleSize = mExtractor.readSampleData(mDecoderBuffers.getInputBuffer(result), 0); + final boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0; + mDecoder.queueInputBuffer(result, 0, sampleSize, mExtractor.getSampleTime(), isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0); + mExtractor.advance(); + return DRAIN_STATE_CONSUMED; + } + + private int drainDecoder(long timeoutUs) { + if (mIsDecoderEOS) return DRAIN_STATE_NONE; + + int result = mDecoder.dequeueOutputBuffer(mBufferInfo, timeoutUs); + switch (result) { + case MediaCodec.INFO_TRY_AGAIN_LATER: + return DRAIN_STATE_NONE; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: + mAudioChannel.setActualDecodedFormat(mDecoder.getOutputFormat()); + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + } + + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + mIsDecoderEOS = true; + mAudioChannel.drainDecoderBufferAndQueue(AudioChannel.BUFFER_INDEX_END_OF_STREAM, 0); + } else if (mBufferInfo.size > 0) { + mAudioChannel.drainDecoderBufferAndQueue(result, mBufferInfo.presentationTimeUs); + } + + return DRAIN_STATE_CONSUMED; + } + + private int drainEncoder(long timeoutUs) { + if (mIsEncoderEOS) return DRAIN_STATE_NONE; + + int result = mEncoder.dequeueOutputBuffer(mBufferInfo, timeoutUs); + switch (result) { + case MediaCodec.INFO_TRY_AGAIN_LATER: + return DRAIN_STATE_NONE; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: + if (mActualOutputFormat != null) { + throw new RuntimeException("Audio output format changed twice."); + } + mActualOutputFormat = mEncoder.getOutputFormat(); + mMuxer.setOutputFormat(SAMPLE_TYPE, mActualOutputFormat); + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: + mEncoderBuffers = new MediaCodecBufferCompatWrapper(mEncoder); + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + } + + if (mActualOutputFormat == null) { + throw new RuntimeException("Could not determine actual output format."); + } + + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + mIsEncoderEOS = true; + mBufferInfo.set(0, 0, 0, mBufferInfo.flags); + } + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // SPS or PPS, which should be passed by MediaFormat. + mEncoder.releaseOutputBuffer(result, false); + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + } + mMuxer.writeSampleData(SAMPLE_TYPE, mEncoderBuffers.getOutputBuffer(result), mBufferInfo); + mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs; + mEncoder.releaseOutputBuffer(result, false); + return DRAIN_STATE_CONSUMED; + } + + @Override + public long getWrittenPresentationTimeUs() { + return mWrittenPresentationTimeUs; + } + + @Override + public boolean isFinished() { + return mIsEncoderEOS; + } + + @Override + public void release() { + if (mDecoder != null) { + if (mDecoderStarted) mDecoder.stop(); + mDecoder.release(); + mDecoder = null; + } + if (mEncoder != null) { + if (mEncoderStarted) mEncoder.stop(); + mEncoder.release(); + mEncoder = null; + } + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/InputSurface.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/InputSurface.java new file mode 100644 index 000000000..9793c8911 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/InputSurface.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// from: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/InputSurface.java +// blob: 157ed88d143229e4edb6889daf18fb73aa2fc5a5 +package net.ypresto.androidtranscoder.engine; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.view.Surface; +/** + * Holds state associated with a Surface used for MediaCodec encoder input. + *

+ * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that + * to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to be sent + * to the video encoder. + */ +class InputSurface { + private static final String TAG = "InputSurface"; + private static final int EGL_RECORDABLE_ANDROID = 0x3142; + private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; + private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; + private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; + private Surface mSurface; + /** + * Creates an InputSurface from a Surface. + */ + public InputSurface(Surface surface) { + if (surface == null) { + throw new NullPointerException(); + } + mSurface = surface; + eglSetup(); + } + /** + * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording. + */ + private void eglSetup() { + mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL14 display"); + } + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { + mEGLDisplay = null; + throw new RuntimeException("unable to initialize EGL14"); + } + // Configure EGL for recordable and OpenGL ES 2.0. We want enough RGB bits + // to minimize artifacts from possible YUV conversion. + int[] attribList = { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL_RECORDABLE_ANDROID, 1, + EGL14.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, + numConfigs, 0)) { + throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); + } + // Configure context for OpenGL ES 2.0. + int[] attrib_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, + attrib_list, 0); + checkEglError("eglCreateContext"); + if (mEGLContext == null) { + throw new RuntimeException("null context"); + } + // Create a window surface, and attach it to the Surface we received. + int[] surfaceAttribs = { + EGL14.EGL_NONE + }; + mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, + surfaceAttribs, 0); + checkEglError("eglCreateWindowSurface"); + if (mEGLSurface == null) { + throw new RuntimeException("surface was null"); + } + } + /** + * Discard all resources held by this class, notably the EGL context. Also releases the + * Surface that was passed to our constructor. + */ + public void release() { + if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { + EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); + EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); + EGL14.eglReleaseThread(); + EGL14.eglTerminate(mEGLDisplay); + } + mSurface.release(); + mEGLDisplay = EGL14.EGL_NO_DISPLAY; + mEGLContext = EGL14.EGL_NO_CONTEXT; + mEGLSurface = EGL14.EGL_NO_SURFACE; + mSurface = null; + } + /** + * Makes our EGL context and surface current. + */ + public void makeCurrent() { + if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + public void makeUnCurrent() { + if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, + EGL14.EGL_NO_CONTEXT)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + /** + * Calls eglSwapBuffers. Use this to "publish" the current frame. + */ + public boolean swapBuffers() { + return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); + } + /** + * Returns the Surface that the MediaCodec receives buffers from. + */ + public Surface getSurface() { + return mSurface; + } + /** + * Queries the surface's width. + */ + public int getWidth() { + int[] value = new int[1]; + EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0); + return value[0]; + } + /** + * Queries the surface's height. + */ + public int getHeight() { + int[] value = new int[1]; + EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0); + return value[0]; + } + /** + * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. + */ + public void setPresentationTime(long nsecs) { + EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); + } + /** + * Checks for EGL errors. + */ + private void checkEglError(String msg) { + int error; + if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { + throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/InvalidOutputFormatException.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/InvalidOutputFormatException.java new file mode 100644 index 000000000..4c6bbd9a4 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/InvalidOutputFormatException.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.engine; + +public class InvalidOutputFormatException extends RuntimeException { + public InvalidOutputFormatException(String detailMessage) { + super(detailMessage); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java new file mode 100644 index 000000000..67ea5ba85 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.engine; + +import android.media.MediaFormat; + +import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants; +import net.ypresto.androidtranscoder.utils.AvcCsdUtils; +import net.ypresto.androidtranscoder.utils.AvcSpsUtils; + +import java.nio.ByteBuffer; + +class MediaFormatValidator { + // Refer: http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC#Profiles + private static final byte PROFILE_IDC_BASELINE = 66; + + public static void validateVideoOutputFormat(MediaFormat format) { + String mime = format.getString(MediaFormat.KEY_MIME); + // Refer: http://developer.android.com/guide/appendix/media-formats.html#core + // Refer: http://en.wikipedia.org/wiki/MPEG-4_Part_14#Data_streams + if (!MediaFormatExtraConstants.MIMETYPE_VIDEO_AVC.equals(mime)) { + throw new InvalidOutputFormatException("Video codecs other than AVC is not supported, actual mime type: " + mime); + } + ByteBuffer spsBuffer = AvcCsdUtils.getSpsBuffer(format); + byte profileIdc = AvcSpsUtils.getProfileIdc(spsBuffer); + if (profileIdc != PROFILE_IDC_BASELINE) { + throw new InvalidOutputFormatException("Non-baseline AVC video profile is not supported by Android OS, actual profile_idc: " + profileIdc); + } + } + + public static void validateAudioOutputFormat(MediaFormat format) { + String mime = format.getString(MediaFormat.KEY_MIME); + if (!MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC.equals(mime)) { + throw new InvalidOutputFormatException("Audio codecs other than AAC is not supported, actual mime type: " + mime); + } + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java new file mode 100644 index 000000000..86637fd0e --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.engine; + +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.media.MediaMetadataRetriever; +import android.media.MediaMuxer; +import android.util.Log; + +import net.ypresto.androidtranscoder.format.MediaFormatStrategy; +import net.ypresto.androidtranscoder.utils.MediaExtractorUtils; + +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * Internal engine, do not use this directly. + */ +// TODO: treat encrypted data +public class MediaTranscoderEngine { + private static final String TAG = "MediaTranscoderEngine"; + private static final double PROGRESS_UNKNOWN = -1.0; + private static final long SLEEP_TO_WAIT_TRACK_TRANSCODERS = 10; + private static final long PROGRESS_INTERVAL_STEPS = 10; + private FileDescriptor mInputFileDescriptor; + private TrackTranscoder mVideoTrackTranscoder; + private TrackTranscoder mAudioTrackTranscoder; + private MediaExtractor mExtractor; + private MediaMuxer mMuxer; + private volatile double mProgress; + private ProgressCallback mProgressCallback; + private long mDurationUs; + + /** + * Do not use this constructor unless you know what you are doing. + */ + public MediaTranscoderEngine() { + } + + public void setDataSource(FileDescriptor fileDescriptor) { + mInputFileDescriptor = fileDescriptor; + } + + public ProgressCallback getProgressCallback() { + return mProgressCallback; + } + + public void setProgressCallback(ProgressCallback progressCallback) { + mProgressCallback = progressCallback; + } + + /** + * NOTE: This method is thread safe. + */ + public double getProgress() { + return mProgress; + } + + /** + * Run video transcoding. Blocks current thread. + * Audio data will not be transcoded; original stream will be wrote to output file. + * + * @param outputPath File path to output transcoded video file. + * @param formatStrategy Output format strategy. + * @throws IOException when input or output file could not be opened. + * @throws InvalidOutputFormatException when output format is not supported. + * @throws InterruptedException when cancel to transcode. + */ + public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy) throws IOException, InterruptedException { + if (outputPath == null) { + throw new NullPointerException("Output path cannot be null."); + } + if (mInputFileDescriptor == null) { + throw new IllegalStateException("Data source is not set."); + } + try { + // NOTE: use single extractor to keep from running out audio track fast. + mExtractor = new MediaExtractor(); + mExtractor.setDataSource(mInputFileDescriptor); + mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + setupMetadata(); + setupTrackTranscoders(formatStrategy); + runPipelines(); + mMuxer.stop(); + } finally { + try { + if (mVideoTrackTranscoder != null) { + mVideoTrackTranscoder.release(); + mVideoTrackTranscoder = null; + } + if (mAudioTrackTranscoder != null) { + mAudioTrackTranscoder.release(); + mAudioTrackTranscoder = null; + } + if (mExtractor != null) { + mExtractor.release(); + mExtractor = null; + } + } catch (RuntimeException e) { + // Too fatal to make alive the app, because it may leak native resources. + //noinspection ThrowFromFinallyBlock + throw new Error("Could not shutdown extractor, codecs and muxer pipeline.", e); + } + try { + if (mMuxer != null) { + mMuxer.release(); + mMuxer = null; + } + } catch (RuntimeException e) { + Log.e(TAG, "Failed to release muxer.", e); + } + } + } + + private void setupMetadata() throws IOException { + MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); + mediaMetadataRetriever.setDataSource(mInputFileDescriptor); + + String rotationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + try { + mMuxer.setOrientationHint(Integer.parseInt(rotationString)); + } catch (NumberFormatException e) { + // skip + } + + try { + mDurationUs = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000; + } catch (NumberFormatException e) { + mDurationUs = -1; + } + Log.d(TAG, "Duration (us): " + mDurationUs); + } + + private void setupTrackTranscoders(MediaFormatStrategy formatStrategy) { + MediaExtractorUtils.TrackResult trackResult = MediaExtractorUtils.getFirstVideoAndAudioTrack(mExtractor); + MediaFormat videoOutputFormat = formatStrategy.createVideoOutputFormat(trackResult.mVideoTrackFormat); + MediaFormat audioOutputFormat = formatStrategy.createAudioOutputFormat(trackResult.mAudioTrackFormat); + if (videoOutputFormat == null && audioOutputFormat == null) { + throw new InvalidOutputFormatException("MediaFormatStrategy returned pass-through for both video and audio. No transcoding is necessary."); + } + QueuedMuxer queuedMuxer = new QueuedMuxer(mMuxer, new QueuedMuxer.Listener() { + @Override + public void onDetermineOutputFormat() { + MediaFormatValidator.validateVideoOutputFormat(mVideoTrackTranscoder.getDeterminedFormat()); + MediaFormatValidator.validateAudioOutputFormat(mAudioTrackTranscoder.getDeterminedFormat()); + } + }); + + if (videoOutputFormat == null) { + mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, queuedMuxer, QueuedMuxer.SampleType.VIDEO); + } else { + mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, queuedMuxer); + } + mVideoTrackTranscoder.setup(); + if (audioOutputFormat == null) { + mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, queuedMuxer, QueuedMuxer.SampleType.AUDIO); + } else { + mAudioTrackTranscoder = new AudioTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, audioOutputFormat, queuedMuxer); + } + mAudioTrackTranscoder.setup(); + mExtractor.selectTrack(trackResult.mVideoTrackIndex); + mExtractor.selectTrack(trackResult.mAudioTrackIndex); + } + + private void runPipelines() throws InterruptedException { + long loopCount = 0; + if (mDurationUs <= 0) { + double progress = PROGRESS_UNKNOWN; + mProgress = progress; + if (mProgressCallback != null) mProgressCallback.onProgress(progress); // unknown + } + while (!(mVideoTrackTranscoder.isFinished() && mAudioTrackTranscoder.isFinished())) { + boolean stepped = mVideoTrackTranscoder.stepPipeline() + || mAudioTrackTranscoder.stepPipeline(); + loopCount++; + if (mDurationUs > 0 && loopCount % PROGRESS_INTERVAL_STEPS == 0) { + double videoProgress = mVideoTrackTranscoder.isFinished() ? 1.0 : Math.min(1.0, (double) mVideoTrackTranscoder.getWrittenPresentationTimeUs() / mDurationUs); + double audioProgress = mAudioTrackTranscoder.isFinished() ? 1.0 : Math.min(1.0, (double) mAudioTrackTranscoder.getWrittenPresentationTimeUs() / mDurationUs); + double progress = (videoProgress + audioProgress) / 2.0; + mProgress = progress; + if (mProgressCallback != null) mProgressCallback.onProgress(progress); + } + if (!stepped) { + Thread.sleep(SLEEP_TO_WAIT_TRACK_TRANSCODERS); + } + } + } + + public interface ProgressCallback { + /** + * Called to notify progress. Same thread which initiated transcode is used. + * + * @param progress Progress in [0.0, 1.0] range, or negative value if progress is unknown. + */ + void onProgress(double progress); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java new file mode 100644 index 000000000..e52ba0217 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// from: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/OutputSurface.java +// blob: fc8ad9cd390c5c311f015d3b7c1359e4d295bc52 +// modified: change TIMEOUT_MS from 500 to 10000 +package net.ypresto.androidtranscoder.engine; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.util.Log; +import android.view.Surface; +/** + * Holds state associated with a Surface used for MediaCodec decoder output. + *

+ * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture, + * and then create a Surface for that SurfaceTexture. The Surface can be passed to + * MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the + * texture with updateTexImage, then render the texture with GL to a pbuffer. + *

+ * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer. + * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives + * we just draw it on whatever surface is current. + *

+ * By default, the Surface will be using a BufferQueue in asynchronous mode, so we + * can potentially drop frames. + */ +class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { + private static final String TAG = "OutputSurface"; + private static final boolean VERBOSE = false; + private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; + private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; + private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; + private SurfaceTexture mSurfaceTexture; + private Surface mSurface; + private Object mFrameSyncObject = new Object(); // guards mFrameAvailable + private boolean mFrameAvailable; + private TextureRender mTextureRender; + /** + * Creates an OutputSurface backed by a pbuffer with the specifed dimensions. The new + * EGL context and surface will be made current. Creates a Surface that can be passed + * to MediaCodec.configure(). + */ + public OutputSurface(int width, int height) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException(); + } + eglSetup(width, height); + makeCurrent(); + setup(); + } + /** + * Creates an OutputSurface using the current EGL context (rather than establishing a + * new one). Creates a Surface that can be passed to MediaCodec.configure(). + */ + public OutputSurface() { + setup(); + } + /** + * Creates instances of TextureRender and SurfaceTexture, and a Surface associated + * with the SurfaceTexture. + */ + private void setup() { + mTextureRender = new TextureRender(); + mTextureRender.surfaceCreated(); + // Even if we don't access the SurfaceTexture after the constructor returns, we + // still need to keep a reference to it. The Surface doesn't retain a reference + // at the Java level, so if we don't either then the object can get GCed, which + // causes the native finalizer to run. + if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); + mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); + // This doesn't work if OutputSurface is created on the thread that CTS started for + // these test cases. + // + // The CTS-created thread has a Looper, and the SurfaceTexture constructor will + // create a Handler that uses it. The "frame available" message is delivered + // there, but since we're not a Looper-based thread we'll never see it. For + // this to do anything useful, OutputSurface must be created on a thread without + // a Looper, so that SurfaceTexture uses the main application Looper instead. + // + // Java language note: passing "this" out of a constructor is generally unwise, + // but we should be able to get away with it here. + mSurfaceTexture.setOnFrameAvailableListener(this); + mSurface = new Surface(mSurfaceTexture); + } + /** + * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer. + */ + private void eglSetup(int width, int height) { + mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL14 display"); + } + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { + mEGLDisplay = null; + throw new RuntimeException("unable to initialize EGL14"); + } + // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits + // to be able to tell if the frame is reasonable. + int[] attribList = { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, + EGL14.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, + numConfigs, 0)) { + throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); + } + // Configure context for OpenGL ES 2.0. + int[] attrib_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, + attrib_list, 0); + checkEglError("eglCreateContext"); + if (mEGLContext == null) { + throw new RuntimeException("null context"); + } + // Create a pbuffer surface. By using this for output, we can use glReadPixels + // to test values in the output. + int[] surfaceAttribs = { + EGL14.EGL_WIDTH, width, + EGL14.EGL_HEIGHT, height, + EGL14.EGL_NONE + }; + mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0); + checkEglError("eglCreatePbufferSurface"); + if (mEGLSurface == null) { + throw new RuntimeException("surface was null"); + } + } + /** + * Discard all resources held by this class, notably the EGL context. + */ + public void release() { + if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { + EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); + EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); + EGL14.eglReleaseThread(); + EGL14.eglTerminate(mEGLDisplay); + } + mSurface.release(); + // this causes a bunch of warnings that appear harmless but might confuse someone: + // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned! + //mSurfaceTexture.release(); + mEGLDisplay = EGL14.EGL_NO_DISPLAY; + mEGLContext = EGL14.EGL_NO_CONTEXT; + mEGLSurface = EGL14.EGL_NO_SURFACE; + mTextureRender = null; + mSurface = null; + mSurfaceTexture = null; + } + /** + * Makes our EGL context and surface current. + */ + public void makeCurrent() { + if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + /** + * Returns the Surface that we draw onto. + */ + public Surface getSurface() { + return mSurface; + } + /** + * Replaces the fragment shader. + */ + public void changeFragmentShader(String fragmentShader) { + mTextureRender.changeFragmentShader(fragmentShader); + } + /** + * Latches the next buffer into the texture. Must be called from the thread that created + * the OutputSurface object, after the onFrameAvailable callback has signaled that new + * data is available. + */ + public void awaitNewImage() { + final int TIMEOUT_MS = 10000; + synchronized (mFrameSyncObject) { + while (!mFrameAvailable) { + try { + // Wait for onFrameAvailable() to signal us. Use a timeout to avoid + // stalling the test if it doesn't arrive. + mFrameSyncObject.wait(TIMEOUT_MS); + if (!mFrameAvailable) { + // TODO: if "spurious wakeup", continue while loop + throw new RuntimeException("Surface frame wait timed out"); + } + } catch (InterruptedException ie) { + // shouldn't happen + throw new RuntimeException(ie); + } + } + mFrameAvailable = false; + } + // Latch the data. + mTextureRender.checkGlError("before updateTexImage"); + mSurfaceTexture.updateTexImage(); + } + /** + * Wait up to given timeout until new image become available. + * @param timeoutMs + * @return true if new image is available. false for no new image until timeout. + */ + public boolean checkForNewImage(int timeoutMs) { + synchronized (mFrameSyncObject) { + while (!mFrameAvailable) { + try { + // Wait for onFrameAvailable() to signal us. Use a timeout to avoid + // stalling the test if it doesn't arrive. + mFrameSyncObject.wait(timeoutMs); + if (!mFrameAvailable) { + return false; + } + } catch (InterruptedException ie) { + // shouldn't happen + throw new RuntimeException(ie); + } + } + mFrameAvailable = false; + } + // Latch the data. + mTextureRender.checkGlError("before updateTexImage"); + mSurfaceTexture.updateTexImage(); + return true; + } + /** + * Draws the data from SurfaceTexture onto the current EGL surface. + */ + public void drawImage() { + mTextureRender.drawFrame(mSurfaceTexture); + } + @Override + public void onFrameAvailable(SurfaceTexture st) { + if (VERBOSE) Log.d(TAG, "new frame available"); + synchronized (mFrameSyncObject) { + if (mFrameAvailable) { + throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); + } + mFrameAvailable = true; + mFrameSyncObject.notifyAll(); + } + } + /** + * Checks for EGL errors. + */ + private void checkEglError(String msg) { + int error; + if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { + throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/PassThroughTrackTranscoder.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/PassThroughTrackTranscoder.java new file mode 100644 index 000000000..7608dac86 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/PassThroughTrackTranscoder.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.engine; + +import android.annotation.SuppressLint; +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class PassThroughTrackTranscoder implements TrackTranscoder { + private final MediaExtractor mExtractor; + private final int mTrackIndex; + private final QueuedMuxer mMuxer; + private final QueuedMuxer.SampleType mSampleType; + private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); + private int mBufferSize; + private ByteBuffer mBuffer; + private boolean mIsEOS; + private MediaFormat mActualOutputFormat; + private long mWrittenPresentationTimeUs; + + public PassThroughTrackTranscoder(MediaExtractor extractor, int trackIndex, + QueuedMuxer muxer, QueuedMuxer.SampleType sampleType) { + mExtractor = extractor; + mTrackIndex = trackIndex; + mMuxer = muxer; + mSampleType = sampleType; + + mActualOutputFormat = mExtractor.getTrackFormat(mTrackIndex); + mMuxer.setOutputFormat(mSampleType, mActualOutputFormat); + mBufferSize = mActualOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); + mBuffer = ByteBuffer.allocateDirect(mBufferSize).order(ByteOrder.nativeOrder()); + } + + @Override + public void setup() { + } + + @Override + public MediaFormat getDeterminedFormat() { + return mActualOutputFormat; + } + + @SuppressLint("Assert") + @Override + public boolean stepPipeline() { + if (mIsEOS) return false; + int trackIndex = mExtractor.getSampleTrackIndex(); + if (trackIndex < 0) { + mBuffer.clear(); + mBufferInfo.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo); + mIsEOS = true; + return true; + } + if (trackIndex != mTrackIndex) return false; + + mBuffer.clear(); + int sampleSize = mExtractor.readSampleData(mBuffer, 0); + assert sampleSize <= mBufferSize; + boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0; + int flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0; + mBufferInfo.set(0, sampleSize, mExtractor.getSampleTime(), flags); + mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo); + mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs; + + mExtractor.advance(); + return true; + } + + @Override + public long getWrittenPresentationTimeUs() { + return mWrittenPresentationTimeUs; + } + + @Override + public boolean isFinished() { + return mIsEOS; + } + + @Override + public void release() { + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/QueuedMuxer.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/QueuedMuxer.java new file mode 100644 index 000000000..df58e9923 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/QueuedMuxer.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2015 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.engine; + +import android.media.MediaCodec; +import android.media.MediaFormat; +import android.media.MediaMuxer; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * This class queues until all output track formats are determined. + */ +public class QueuedMuxer { + private static final String TAG = "QueuedMuxer"; + private static final int BUFFER_SIZE = 64 * 1024; // I have no idea whether this value is appropriate or not... + private final MediaMuxer mMuxer; + private final Listener mListener; + private MediaFormat mVideoFormat; + private MediaFormat mAudioFormat; + private int mVideoTrackIndex; + private int mAudioTrackIndex; + private ByteBuffer mByteBuffer; + private final List mSampleInfoList; + private boolean mStarted; + + public QueuedMuxer(MediaMuxer muxer, Listener listener) { + mMuxer = muxer; + mListener = listener; + mSampleInfoList = new ArrayList<>(); + } + + public void setOutputFormat(SampleType sampleType, MediaFormat format) { + switch (sampleType) { + case VIDEO: + mVideoFormat = format; + break; + case AUDIO: + mAudioFormat = format; + break; + default: + throw new AssertionError(); + } + onSetOutputFormat(); + } + + private void onSetOutputFormat() { + if (mVideoFormat == null || mAudioFormat == null) return; + mListener.onDetermineOutputFormat(); + + mVideoTrackIndex = mMuxer.addTrack(mVideoFormat); + Log.v(TAG, "Added track #" + mVideoTrackIndex + " with " + mVideoFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); + mAudioTrackIndex = mMuxer.addTrack(mAudioFormat); + Log.v(TAG, "Added track #" + mAudioTrackIndex + " with " + mAudioFormat.getString(MediaFormat.KEY_MIME) + " to muxer"); + mMuxer.start(); + mStarted = true; + + if (mByteBuffer == null) { + mByteBuffer = ByteBuffer.allocate(0); + } + mByteBuffer.flip(); + Log.v(TAG, "Output format determined, writing " + mSampleInfoList.size() + + " samples / " + mByteBuffer.limit() + " bytes to muxer."); + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + int offset = 0; + for (SampleInfo sampleInfo : mSampleInfoList) { + sampleInfo.writeToBufferInfo(bufferInfo, offset); + mMuxer.writeSampleData(getTrackIndexForSampleType(sampleInfo.mSampleType), mByteBuffer, bufferInfo); + offset += sampleInfo.mSize; + } + mSampleInfoList.clear(); + mByteBuffer = null; + } + + public void writeSampleData(SampleType sampleType, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo) { + if (mStarted) { + mMuxer.writeSampleData(getTrackIndexForSampleType(sampleType), byteBuf, bufferInfo); + return; + } + byteBuf.limit(bufferInfo.offset + bufferInfo.size); + byteBuf.position(bufferInfo.offset); + if (mByteBuffer == null) { + mByteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE).order(ByteOrder.nativeOrder()); + } + mByteBuffer.put(byteBuf); + mSampleInfoList.add(new SampleInfo(sampleType, bufferInfo.size, bufferInfo)); + } + + private int getTrackIndexForSampleType(SampleType sampleType) { + switch (sampleType) { + case VIDEO: + return mVideoTrackIndex; + case AUDIO: + return mAudioTrackIndex; + default: + throw new AssertionError(); + } + } + + public enum SampleType {VIDEO, AUDIO} + + private static class SampleInfo { + private final SampleType mSampleType; + private final int mSize; + private final long mPresentationTimeUs; + private final int mFlags; + + private SampleInfo(SampleType sampleType, int size, MediaCodec.BufferInfo bufferInfo) { + mSampleType = sampleType; + mSize = size; + mPresentationTimeUs = bufferInfo.presentationTimeUs; + mFlags = bufferInfo.flags; + } + + private void writeToBufferInfo(MediaCodec.BufferInfo bufferInfo, int offset) { + bufferInfo.set(offset, mSize, mPresentationTimeUs, mFlags); + } + } + + public interface Listener { + void onDetermineOutputFormat(); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/TextureRender.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/TextureRender.java new file mode 100644 index 000000000..571427d30 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/TextureRender.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// from: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/TextureRender.java +// blob: 4125dcfcfed6ed7fddba5b71d657dec0d433da6a +// modified: removed unused method bodies +// modified: use GL_LINEAR for GL_TEXTURE_MIN_FILTER to improve quality. +package net.ypresto.androidtranscoder.engine; +import android.graphics.SurfaceTexture; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.Matrix; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +/** + * Code for rendering a texture onto a surface using OpenGL ES 2.0. + */ +class TextureRender { + private static final String TAG = "TextureRender"; + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + private final float[] mTriangleVerticesData = { + // X, Y, Z, U, V + -1.0f, -1.0f, 0, 0.f, 0.f, + 1.0f, -1.0f, 0, 1.f, 0.f, + -1.0f, 1.0f, 0, 0.f, 1.f, + 1.0f, 1.0f, 0, 1.f, 1.f, + }; + private FloatBuffer mTriangleVertices; + private static final String VERTEX_SHADER = + "uniform mat4 uMVPMatrix;\n" + + "uniform mat4 uSTMatrix;\n" + + "attribute vec4 aPosition;\n" + + "attribute vec4 aTextureCoord;\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + + "}\n"; + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + // highp here doesn't seem to matter + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; + private float[] mMVPMatrix = new float[16]; + private float[] mSTMatrix = new float[16]; + private int mProgram; + private int mTextureID = -12345; + private int muMVPMatrixHandle; + private int muSTMatrixHandle; + private int maPositionHandle; + private int maTextureHandle; + public TextureRender() { + mTriangleVertices = ByteBuffer.allocateDirect( + mTriangleVerticesData.length * FLOAT_SIZE_BYTES) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + mTriangleVertices.put(mTriangleVerticesData).position(0); + Matrix.setIdentityM(mSTMatrix, 0); + } + public int getTextureId() { + return mTextureID; + } + public void drawFrame(SurfaceTexture st) { + checkGlError("onDrawFrame start"); + st.getTransformMatrix(mSTMatrix); + GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glUseProgram(mProgram); + checkGlError("glUseProgram"); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maPosition"); + GLES20.glEnableVertexAttribArray(maPositionHandle); + checkGlError("glEnableVertexAttribArray maPositionHandle"); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maTextureHandle"); + GLES20.glEnableVertexAttribArray(maTextureHandle); + checkGlError("glEnableVertexAttribArray maTextureHandle"); + Matrix.setIdentityM(mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + checkGlError("glDrawArrays"); + GLES20.glFinish(); + } + /** + * Initializes GL state. Call this after the EGL surface has been created and made current. + */ + public void surfaceCreated() { + mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); + checkGlError("glGetAttribLocation aPosition"); + if (maPositionHandle == -1) { + throw new RuntimeException("Could not get attrib location for aPosition"); + } + maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); + checkGlError("glGetAttribLocation aTextureCoord"); + if (maTextureHandle == -1) { + throw new RuntimeException("Could not get attrib location for aTextureCoord"); + } + muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); + checkGlError("glGetUniformLocation uMVPMatrix"); + if (muMVPMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uMVPMatrix"); + } + muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); + checkGlError("glGetUniformLocation uSTMatrix"); + if (muSTMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uSTMatrix"); + } + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + mTextureID = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + checkGlError("glBindTexture mTextureID"); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE); + checkGlError("glTexParameter"); + } + /** + * Replaces the fragment shader. + */ + public void changeFragmentShader(String fragmentShader) { + throw new UnsupportedOperationException("Not implemented"); + } + private int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + int program = GLES20.glCreateProgram(); + checkGlError("glCreateProgram"); + if (program == 0) { + Log.e(TAG, "Could not create program"); + } + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + return program; + } + public void checkGlError(String op) { + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(TAG, op + ": glError " + error); + throw new RuntimeException(op + ": glError " + error); + } + } + /** + * Saves the current frame to disk as a PNG image. Frame starts from (0,0). + *

+ * Useful for debugging. + */ + public static void saveFrame(String filename, int width, int height) { + throw new UnsupportedOperationException("Not implemented."); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/TrackTranscoder.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/TrackTranscoder.java new file mode 100644 index 000000000..04bff2a40 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/TrackTranscoder.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.engine; + +import android.media.MediaFormat; + +public interface TrackTranscoder { + + void setup(); + + /** + * Get actual MediaFormat which is used to write to muxer. + * To determine you should call {@link #stepPipeline()} several times. + * + * @return Actual output format determined by coder, or {@code null} if not yet determined. + */ + MediaFormat getDeterminedFormat(); + + /** + * Step pipeline if output is available in any step of it. + * It assumes muxer has been started, so you should call muxer.start() first. + * + * @return true if data moved in pipeline. + */ + boolean stepPipeline(); + + /** + * Get presentation time of last sample written to muxer. + * + * @return Presentation time in micro-second. Return value is undefined if finished writing. + */ + long getWrittenPresentationTimeUs(); + + boolean isFinished(); + + void release(); +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/VideoTrackTranscoder.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/VideoTrackTranscoder.java new file mode 100644 index 000000000..a5640dd99 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/engine/VideoTrackTranscoder.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.engine; + +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; + +import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants; + +import java.io.IOException; +import java.nio.ByteBuffer; + +// Refer: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java +public class VideoTrackTranscoder implements TrackTranscoder { + private static final String TAG = "VideoTrackTranscoder"; + private static final int DRAIN_STATE_NONE = 0; + private static final int DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY = 1; + private static final int DRAIN_STATE_CONSUMED = 2; + + private final MediaExtractor mExtractor; + private final int mTrackIndex; + private final MediaFormat mOutputFormat; + private final QueuedMuxer mMuxer; + private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo(); + private MediaCodec mDecoder; + private MediaCodec mEncoder; + private ByteBuffer[] mDecoderInputBuffers; + private ByteBuffer[] mEncoderOutputBuffers; + private MediaFormat mActualOutputFormat; + private OutputSurface mDecoderOutputSurfaceWrapper; + private InputSurface mEncoderInputSurfaceWrapper; + private boolean mIsExtractorEOS; + private boolean mIsDecoderEOS; + private boolean mIsEncoderEOS; + private boolean mDecoderStarted; + private boolean mEncoderStarted; + private long mWrittenPresentationTimeUs; + + public VideoTrackTranscoder(MediaExtractor extractor, int trackIndex, + MediaFormat outputFormat, QueuedMuxer muxer) { + mExtractor = extractor; + mTrackIndex = trackIndex; + mOutputFormat = outputFormat; + mMuxer = muxer; + } + + @Override + public void setup() { + mExtractor.selectTrack(mTrackIndex); + try { + mEncoder = MediaCodec.createEncoderByType(mOutputFormat.getString(MediaFormat.KEY_MIME)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + mEncoder.configure(mOutputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mEncoderInputSurfaceWrapper = new InputSurface(mEncoder.createInputSurface()); + mEncoderInputSurfaceWrapper.makeCurrent(); + mEncoder.start(); + mEncoderStarted = true; + mEncoderOutputBuffers = mEncoder.getOutputBuffers(); + + MediaFormat inputFormat = mExtractor.getTrackFormat(mTrackIndex); + if (inputFormat.containsKey(MediaFormatExtraConstants.KEY_ROTATION_DEGREES)) { + // Decoded video is rotated automatically in Android 5.0 lollipop. + // Turn off here because we don't want to encode rotated one. + // refer: https://android.googlesource.com/platform/frameworks/av/+blame/lollipop-release/media/libstagefright/Utils.cpp + inputFormat.setInteger(MediaFormatExtraConstants.KEY_ROTATION_DEGREES, 0); + } + mDecoderOutputSurfaceWrapper = new OutputSurface(); + try { + mDecoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + mDecoder.configure(inputFormat, mDecoderOutputSurfaceWrapper.getSurface(), null, 0); + mDecoder.start(); + mDecoderStarted = true; + mDecoderInputBuffers = mDecoder.getInputBuffers(); + } + + @Override + public MediaFormat getDeterminedFormat() { + return mActualOutputFormat; + } + + @Override + public boolean stepPipeline() { + boolean busy = false; + + int status; + while (drainEncoder(0) != DRAIN_STATE_NONE) busy = true; + do { + status = drainDecoder(0); + if (status != DRAIN_STATE_NONE) busy = true; + // NOTE: not repeating to keep from deadlock when encoder is full. + } while (status == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY); + while (drainExtractor(0) != DRAIN_STATE_NONE) busy = true; + + return busy; + } + + @Override + public long getWrittenPresentationTimeUs() { + return mWrittenPresentationTimeUs; + } + + @Override + public boolean isFinished() { + return mIsEncoderEOS; + } + + // TODO: CloseGuard + @Override + public void release() { + if (mDecoderOutputSurfaceWrapper != null) { + mDecoderOutputSurfaceWrapper.release(); + mDecoderOutputSurfaceWrapper = null; + } + if (mEncoderInputSurfaceWrapper != null) { + mEncoderInputSurfaceWrapper.release(); + mEncoderInputSurfaceWrapper = null; + } + if (mDecoder != null) { + if (mDecoderStarted) mDecoder.stop(); + mDecoder.release(); + mDecoder = null; + } + if (mEncoder != null) { + if (mEncoderStarted) mEncoder.stop(); + mEncoder.release(); + mEncoder = null; + } + } + + private int drainExtractor(long timeoutUs) { + if (mIsExtractorEOS) return DRAIN_STATE_NONE; + int trackIndex = mExtractor.getSampleTrackIndex(); + if (trackIndex >= 0 && trackIndex != mTrackIndex) { + return DRAIN_STATE_NONE; + } + int result = mDecoder.dequeueInputBuffer(timeoutUs); + if (result < 0) return DRAIN_STATE_NONE; + if (trackIndex < 0) { + mIsExtractorEOS = true; + mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + return DRAIN_STATE_NONE; + } + int sampleSize = mExtractor.readSampleData(mDecoderInputBuffers[result], 0); + boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0; + mDecoder.queueInputBuffer(result, 0, sampleSize, mExtractor.getSampleTime(), isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0); + mExtractor.advance(); + return DRAIN_STATE_CONSUMED; + } + + private int drainDecoder(long timeoutUs) { + if (mIsDecoderEOS) return DRAIN_STATE_NONE; + int result = mDecoder.dequeueOutputBuffer(mBufferInfo, timeoutUs); + switch (result) { + case MediaCodec.INFO_TRY_AGAIN_LATER: + return DRAIN_STATE_NONE; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + } + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + mEncoder.signalEndOfInputStream(); + mIsDecoderEOS = true; + mBufferInfo.size = 0; + } + boolean doRender = (mBufferInfo.size > 0); + // NOTE: doRender will block if buffer (of encoder) is full. + // Refer: http://bigflake.com/mediacodec/CameraToMpegTest.java.txt + mDecoder.releaseOutputBuffer(result, doRender); + if (doRender) { + mDecoderOutputSurfaceWrapper.awaitNewImage(); + mDecoderOutputSurfaceWrapper.drawImage(); + mEncoderInputSurfaceWrapper.setPresentationTime(mBufferInfo.presentationTimeUs * 1000); + mEncoderInputSurfaceWrapper.swapBuffers(); + } + return DRAIN_STATE_CONSUMED; + } + + private int drainEncoder(long timeoutUs) { + if (mIsEncoderEOS) return DRAIN_STATE_NONE; + int result = mEncoder.dequeueOutputBuffer(mBufferInfo, timeoutUs); + switch (result) { + case MediaCodec.INFO_TRY_AGAIN_LATER: + return DRAIN_STATE_NONE; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: + if (mActualOutputFormat != null) + throw new RuntimeException("Video output format changed twice."); + mActualOutputFormat = mEncoder.getOutputFormat(); + mMuxer.setOutputFormat(QueuedMuxer.SampleType.VIDEO, mActualOutputFormat); + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: + mEncoderOutputBuffers = mEncoder.getOutputBuffers(); + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + } + if (mActualOutputFormat == null) { + throw new RuntimeException("Could not determine actual output format."); + } + + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + mIsEncoderEOS = true; + mBufferInfo.set(0, 0, 0, mBufferInfo.flags); + } + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // SPS or PPS, which should be passed by MediaFormat. + mEncoder.releaseOutputBuffer(result, false); + return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY; + } + mMuxer.writeSampleData(QueuedMuxer.SampleType.VIDEO, mEncoderOutputBuffers[result], mBufferInfo); + mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs; + mEncoder.releaseOutputBuffer(result, false); + return DRAIN_STATE_CONSUMED; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android16By9FormatStrategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android16By9FormatStrategy.java new file mode 100644 index 000000000..faf303127 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android16By9FormatStrategy.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaCodecInfo; +import android.media.MediaFormat; +import android.util.Log; + +class Android16By9FormatStrategy implements MediaFormatStrategy { + public static final int AUDIO_BITRATE_AS_IS = -1; + public static final int AUDIO_CHANNELS_AS_IS = -1; + public static final int SCALE_720P = 5; + private static final String TAG = "Android16By9FormatStrategy"; + private final int mScale; + private final int mVideoBitrate; + private final int mAudioBitrate; + private final int mAudioChannels; + + public Android16By9FormatStrategy(int scale, int videoBitrate) { + this(scale, videoBitrate, AUDIO_BITRATE_AS_IS, AUDIO_CHANNELS_AS_IS); + } + + public Android16By9FormatStrategy(int scale, int videoBitrate, int audioBitrate, int audioChannels) { + mScale = scale; + mVideoBitrate = videoBitrate; + mAudioBitrate = audioBitrate; + mAudioChannels = audioChannels; + } + + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); + int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); + int targetLonger = mScale * 16 * 16; + int targetShorter = mScale * 16 * 9; + int longer, shorter, outWidth, outHeight; + if (width >= height) { + longer = width; + shorter = height; + outWidth = targetLonger; + outHeight = targetShorter; + } else { + shorter = width; + longer = height; + outWidth = targetShorter; + outHeight = targetLonger; + } + if (longer * 9 != shorter * 16) { + throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")"); + } + if (shorter <= targetShorter) { + Log.d(TAG, "This video's height is less or equal to " + targetShorter + ", pass-through. (" + width + "x" + height + ")"); + return null; + } + MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight); + // From Nexus 4 Camera in 720p + format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + return format; + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + if (mAudioBitrate == AUDIO_BITRATE_AS_IS || mAudioChannels == AUDIO_CHANNELS_AS_IS) return null; + + // Use original sample rate, as resampling is not supported yet. + final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, + inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); + format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); + format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate); + return format; + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/Android720pFormatStrategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java similarity index 51% rename from src/main/java/eu/siacs/conversations/utils/Android720pFormatStrategy.java rename to libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java index 981866951..9423d4819 100644 --- a/src/main/java/eu/siacs/conversations/utils/Android720pFormatStrategy.java +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java @@ -1,34 +1,49 @@ -package eu.siacs.conversations.utils; +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; import android.media.MediaCodecInfo; import android.media.MediaFormat; -import android.os.Build; -import android.support.annotation.RequiresApi; import android.util.Log; -import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants; -import net.ypresto.androidtranscoder.format.MediaFormatStrategy; -import net.ypresto.androidtranscoder.format.OutputFormatUnavailableException; - -import eu.siacs.conversations.Config; - -public class Android720pFormatStrategy implements MediaFormatStrategy { - +class Android720pFormatStrategy implements MediaFormatStrategy { + public static final int AUDIO_BITRATE_AS_IS = -1; + public static final int AUDIO_CHANNELS_AS_IS = -1; + private static final String TAG = "720pFormatStrategy"; private static final int LONGER_LENGTH = 1280; private static final int SHORTER_LENGTH = 720; - private static final int DEFAULT_VIDEO_BITRATE = 2000 * 1000; - private static final int DEFAULT_AUDIO_BITRATE = 192 * 1000; + private static final int DEFAULT_VIDEO_BITRATE = 8000 * 1000; // From Nexus 4 Camera in 720p private final int mVideoBitrate; private final int mAudioBitrate; private final int mAudioChannels; public Android720pFormatStrategy() { - mVideoBitrate = DEFAULT_VIDEO_BITRATE; - mAudioBitrate = DEFAULT_AUDIO_BITRATE; - mAudioChannels = 2; + this(DEFAULT_VIDEO_BITRATE); + } + + public Android720pFormatStrategy(int videoBitrate) { + this(videoBitrate, AUDIO_BITRATE_AS_IS, AUDIO_CHANNELS_AS_IS); + } + + public Android720pFormatStrategy(int videoBitrate, int audioBitrate, int audioChannels) { + mVideoBitrate = videoBitrate; + mAudioBitrate = audioBitrate; + mAudioChannels = audioChannels; } - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @Override public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); @@ -48,28 +63,28 @@ public class Android720pFormatStrategy implements MediaFormatStrategy { if (longer * 9 != shorter * 16) { throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")"); } - if (shorter <= SHORTER_LENGTH) { - Log.d(Config.LOGTAG, "This video is less or equal to 720p, pass-through. (" + width + "x" + height + ")"); + if (shorter < SHORTER_LENGTH) { + Log.d(TAG, "This video is less to 720p, pass-through. (" + width + "x" + height + ")"); return null; } MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight); + // From Nexus 4 Camera in 720p format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate); format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - format.setInteger(MediaFormat.KEY_PROFILE ,MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); - format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel13); - } return format; } @Override public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { - final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); + if (mAudioBitrate == AUDIO_BITRATE_AS_IS || mAudioChannels == AUDIO_CHANNELS_AS_IS) return null; + + // Use original sample rate, as resampling is not supported yet. + final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, + inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate); return format; } - } diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/AndroidStandardFormatStrategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/AndroidStandardFormatStrategy.java new file mode 100644 index 000000000..6b1990e69 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/AndroidStandardFormatStrategy.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaCodecInfo; +import android.media.MediaFormat; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +class AndroidStandardFormatStrategy implements MediaFormatStrategy { + public static final int AUDIO_BITRATE_AS_IS = -1; + public static final int AUDIO_CHANNELS_AS_IS = -1; + private static final String TAG = "StandardCompression"; + private static final int DEFAULT_VIDEO_BITRATE = 2000 * 1000; + private static int LONGER_LENGTH = 1280; + private static int SHORTER_LENGTH = 720; + private final int mVideoBitrate; + private final int mVideoresolution; + private final int mAudioBitrate; + private final int mAudioChannels; + private float ASPECT_RATIO = LONGER_LENGTH / SHORTER_LENGTH; + + public AndroidStandardFormatStrategy() { + this(DEFAULT_VIDEO_BITRATE, SHORTER_LENGTH); + } + + public AndroidStandardFormatStrategy(int videoBitrate, int SHORTER_LENGTH) { + this(videoBitrate, SHORTER_LENGTH, AUDIO_BITRATE_AS_IS, AUDIO_CHANNELS_AS_IS); + } + + public AndroidStandardFormatStrategy(int videoBitrate, int SHORTER_LENGTH, int audioBitrate, int audioChannels) { + mVideoBitrate = videoBitrate; + mVideoresolution = SHORTER_LENGTH; + mAudioBitrate = audioBitrate; + mAudioChannels = audioChannels; + } + + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); + int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); + ASPECT_RATIO = (float) width / height; + Log.d(TAG, "Input video (" + width + "x" + height + " ratio: " + ASPECT_RATIO); + int shorter, outWidth, outHeight; + if (width >= height) { + shorter = height; + outWidth = Math.round(mVideoresolution * ASPECT_RATIO); + outHeight = mVideoresolution; + } else { + shorter = width; + outWidth = mVideoresolution; + outHeight = Math.round(mVideoresolution * ASPECT_RATIO); + } + if (shorter < mVideoresolution) { + Log.d(TAG, "This video is less to " + mVideoresolution + "p, pass-through. (" + width + "x" + height + ")"); + return null; + } + Log.d(TAG, "Converting video (" + outWidth + "x" + outHeight + " ratio: " + ASPECT_RATIO + ")"); + MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight); + format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + format.setInteger(MediaFormat.KEY_PROFILE ,MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); + format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel13); + } + return format; + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + if (mAudioBitrate == AUDIO_BITRATE_AS_IS || mAudioChannels == AUDIO_CHANNELS_AS_IS) + return null; + + // Use original sample rate, as resampling is not supported yet. + final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC, + inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels); + format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); + format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate); + return format; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/ExportPreset960x540Strategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/ExportPreset960x540Strategy.java new file mode 100644 index 000000000..56d9a7d34 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/ExportPreset960x540Strategy.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaFormat; +import android.util.Log; + +/** +* Created by yuya.tanaka on 2014/11/20. +*/ +class ExportPreset960x540Strategy implements MediaFormatStrategy { + private static final String TAG = "ExportPreset960x540Strategy"; + + @Override + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) { + // TODO: detect non-baseline profile and throw exception + int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH); + int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT); + MediaFormat outputFormat = MediaFormatPresets.getExportPreset960x540(width, height); + int outWidth = outputFormat.getInteger(MediaFormat.KEY_WIDTH); + int outHeight = outputFormat.getInteger(MediaFormat.KEY_HEIGHT); + Log.d(TAG, String.format("inputFormat: %dx%d => outputFormat: %dx%d", width, height, outWidth, outHeight)); + return outputFormat; + } + + @Override + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) { + // TODO + return null; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java new file mode 100644 index 000000000..7f34adc97 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +public class MediaFormatExtraConstants { + // from MediaFormat of API level >= 21, but might be usable in older APIs as native code implementation exists. + // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2621 + // NOTE: native code enforces baseline profile. + // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2638 + /** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCProfile* . */ + public static final String KEY_PROFILE = "profile"; + + // from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2623 + /** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCLevel* . */ + public static final String KEY_LEVEL = "level"; + + // from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/MediaCodec.cpp#2197 + /** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */ + public static final String KEY_AVC_SPS = "csd-0"; + /** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */ + public static final String KEY_AVC_PPS = "csd-1"; + + /** + * For decoder parameter and included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. + * Decoder rotates specified degrees before rendering video to surface. + * NOTE: Only included in track format of API >= 21. + */ + public static final String KEY_ROTATION_DEGREES = "rotation-degrees"; + + // Video formats + // from MediaFormat of API level >= 21 + public static final String MIMETYPE_VIDEO_AVC = "video/avc"; + public static final String MIMETYPE_VIDEO_H263 = "video/3gpp"; + public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8"; + + // Audio formats + // from MediaFormat of API level >= 21 + public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm"; + + private MediaFormatExtraConstants() { + throw new RuntimeException(); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatPresets.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatPresets.java new file mode 100644 index 000000000..086bcd3f3 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatPresets.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaCodecInfo; +import android.media.MediaFormat; + +// Refer for example: https://gist.github.com/wobbals/3990442 +// Refer for preferred parameters: https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html#//apple_ref/doc/uid/TP40008332-CH102-SW8 +// Refer for available keys: (ANDROID ROOT)/media/libstagefright/ACodec.cpp +public class MediaFormatPresets { + private static final int LONGER_LENGTH_960x540 = 960; + + private MediaFormatPresets() { + } + + // preset similar to iOS SDK's AVAssetExportPreset960x540 + @Deprecated + public static MediaFormat getExportPreset960x540() { + MediaFormat format = MediaFormat.createVideoFormat("video/avc", 960, 540); + format.setInteger(MediaFormat.KEY_BIT_RATE, 5500 * 1000); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + return format; + } + + /** + * Preset similar to iOS SDK's AVAssetExportPreset960x540. + * Note that encoding resolutions of this preset are not supported in all devices e.g. Nexus 4. + * On unsupported device encoded video stream will be broken without any exception. + * @param originalWidth Input video width. + * @param originalHeight Input video height. + * @return MediaFormat instance, or null if pass through. + */ + public static MediaFormat getExportPreset960x540(int originalWidth, int originalHeight) { + int longerLength = Math.max(originalWidth, originalHeight); + int shorterLength = Math.min(originalWidth, originalHeight); + + if (longerLength <= LONGER_LENGTH_960x540) return null; // don't upscale + + int residue = LONGER_LENGTH_960x540 * shorterLength % longerLength; + if (residue != 0) { + double ambiguousShorter = (double) LONGER_LENGTH_960x540 * shorterLength / longerLength; + throw new OutputFormatUnavailableException(String.format( + "Could not fit to integer, original: (%d, %d), scaled: (%d, %f)", + longerLength, shorterLength, LONGER_LENGTH_960x540, ambiguousShorter)); + } + + int scaledShorter = LONGER_LENGTH_960x540 * shorterLength / longerLength; + int width, height; + if (originalWidth >= originalHeight) { + width = LONGER_LENGTH_960x540; + height = scaledShorter; + } else { + width = scaledShorter; + height = LONGER_LENGTH_960x540; + } + + MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); + format.setInteger(MediaFormat.KEY_BIT_RATE, 5500 * 1000); + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); + return format; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategy.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategy.java new file mode 100644 index 000000000..1aa8f6dc9 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategy.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +import android.media.MediaFormat; + +public interface MediaFormatStrategy { + + /** + * Returns preferred video format for encoding. + * + * @param inputFormat MediaFormat from MediaExtractor, contains csd-0/csd-1. + * @return null for passthrough. + * @throws OutputFormatUnavailableException if input could not be transcoded because of restrictions. + */ + public MediaFormat createVideoOutputFormat(MediaFormat inputFormat); + + /** + * Caution: this method should return null currently. + * + * @return null for passthrough. + * @throws OutputFormatUnavailableException if input could not be transcoded because of restrictions. + */ + public MediaFormat createAudioOutputFormat(MediaFormat inputFormat); + +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java new file mode 100644 index 000000000..8bf02c181 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +public class MediaFormatStrategyPresets { + public static final int AUDIO_BITRATE_AS_IS = -1; + public static final int AUDIO_CHANNELS_AS_IS = -1; + + /** + * @deprecated Use {@link #createExportPreset960x540Strategy()}. + */ + @Deprecated + public static final MediaFormatStrategy EXPORT_PRESET_960x540 = new ExportPreset960x540Strategy(); + + /** + * Standard preset + * + * @param bitrate Preferred bitrate for video encoding. + */ + public static MediaFormatStrategy createAndroidStandardStrategy(int bitrate, int resolution) { + return new AndroidStandardFormatStrategy(bitrate, resolution); + } + + /** + * Preset based on Nexus 4 camera recording with 720p quality. + * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available). + * Default bitrate is 8Mbps. {@link #createAndroid720pStrategy(int)} to specify bitrate. + */ + public static MediaFormatStrategy createAndroid720pStrategy() { + return new Android720pFormatStrategy(); + } + + /** + * Preset based on Nexus 4 camera recording with 720p quality. + * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available). + * Audio track will be copied as-is. + * + * @param bitrate Preferred bitrate for video encoding. + */ + public static MediaFormatStrategy createAndroid720pStrategy(int bitrate) { + return new Android720pFormatStrategy(bitrate); + } + + /** + * Preset based on Nexus 4 camera recording with 720p quality. + * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available). + *
+ * Note: audio transcoding is experimental feature. + * + * @param bitrate Preferred bitrate for video encoding. + * @param audioBitrate Preferred bitrate for audio encoding. + * @param audioChannels Output audio channels. + */ + public static MediaFormatStrategy createAndroid720pStrategy(int bitrate, int audioBitrate, int audioChannels) { + return new Android720pFormatStrategy(bitrate, audioBitrate, audioChannels); + } + + /** + * Preset similar to iOS SDK's AVAssetExportPreset960x540. + * Note that encoding resolutions of this preset are not supported in all devices e.g. Nexus 4. + * On unsupported device encoded video stream will be broken without any exception. + */ + public static MediaFormatStrategy createExportPreset960x540Strategy() { + return new ExportPreset960x540Strategy(); + } + + private MediaFormatStrategyPresets() { + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/OutputFormatUnavailableException.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/OutputFormatUnavailableException.java new file mode 100644 index 000000000..4fb9b33c9 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/format/OutputFormatUnavailableException.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.format; + +public class OutputFormatUnavailableException extends RuntimeException { + public OutputFormatUnavailableException(String detailMessage) { + super(detailMessage); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java new file mode 100644 index 000000000..049296b9b --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.utils; + +import android.media.MediaFormat; + +import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public class AvcCsdUtils { + // Refer: https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/MediaCodec.cpp#2198 + // Refer: http://stackoverflow.com/a/2861340 + private static final byte[] AVC_START_CODE_3 = {0x00, 0x00, 0x01}; + private static final byte[] AVC_START_CODE_4 = {0x00, 0x00, 0x00, 0x01}; + // Refer: http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/ + private static final byte AVC_SPS_NAL = 103; // 0<<7 + 3<<5 + 7<<0 + // https://tools.ietf.org/html/rfc6184 + private static final byte AVC_SPS_NAL_2 = 39; // 0<<7 + 1<<5 + 7<<0 + private static final byte AVC_SPS_NAL_3 = 71; // 0<<7 + 2<<5 + 7<<0 + + /** + * @return ByteBuffer contains SPS without NAL header. + */ + public static ByteBuffer getSpsBuffer(MediaFormat format) { + ByteBuffer sourceBuffer = format.getByteBuffer(MediaFormatExtraConstants.KEY_AVC_SPS).asReadOnlyBuffer(); // might be direct buffer + ByteBuffer prefixedSpsBuffer = ByteBuffer.allocate(sourceBuffer.limit()).order(sourceBuffer.order()); + prefixedSpsBuffer.put(sourceBuffer); + prefixedSpsBuffer.flip(); + + skipStartCode(prefixedSpsBuffer); + + byte spsNalData = prefixedSpsBuffer.get(); + if (spsNalData != AVC_SPS_NAL && spsNalData != AVC_SPS_NAL_2 && spsNalData != AVC_SPS_NAL_3) { + throw new IllegalStateException("Got non SPS NAL data."); + } + + return prefixedSpsBuffer.slice(); + } + + private static void skipStartCode(ByteBuffer prefixedSpsBuffer) { + byte[] prefix3 = new byte[3]; + prefixedSpsBuffer.get(prefix3); + if (Arrays.equals(prefix3, AVC_START_CODE_3)) return; + + byte[] prefix4 = Arrays.copyOf(prefix3, 4); + prefix4[3] = prefixedSpsBuffer.get(); + if (Arrays.equals(prefix4, AVC_START_CODE_4)) return; + throw new IllegalStateException("AVC NAL start code does not found in csd."); + } + + private AvcCsdUtils() { + throw new RuntimeException(); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/AvcSpsUtils.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/AvcSpsUtils.java new file mode 100644 index 000000000..3eafbed64 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/AvcSpsUtils.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.utils; + +import java.nio.ByteBuffer; + +public class AvcSpsUtils { + public static byte getProfileIdc(ByteBuffer spsBuffer) { + // Refer: http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/ + // First byte after NAL. + return spsBuffer.get(0); + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParser.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParser.java new file mode 100644 index 000000000..273007d3c --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParser.java @@ -0,0 +1,37 @@ +package net.ypresto.androidtranscoder.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ISO6709LocationParser { + private final Pattern pattern; + + public ISO6709LocationParser() { + this.pattern = Pattern.compile("([+\\-][0-9.]+)([+\\-][0-9.]+)"); + } + + /** + * This method parses the given string representing a geographic point location by coordinates in ISO 6709 format + * and returns the latitude and the longitude in float. If location is not in ISO 6709 format, + * this method returns null + * + * @param location a String representing a geographic point location by coordinates in ISO 6709 format + * @return null if the given string is not as expected, an array of floats with size 2, + * where the first element represents latitude and the second represents longitude, otherwise. + */ + public float[] parse(String location) { + if (location == null) return null; + Matcher m = pattern.matcher(location); + if (m.find() && m.groupCount() == 2) { + String latstr = m.group(1); + String lonstr = m.group(2); + try { + float lat = Float.parseFloat(latstr); + float lon = Float.parseFloat(lonstr); + return new float[]{lat, lon}; + } catch (NumberFormatException ignored) { + } + } + return null; + } +} diff --git a/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/MediaExtractorUtils.java b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/MediaExtractorUtils.java new file mode 100644 index 000000000..b973d2f16 --- /dev/null +++ b/libs/android-transcoder/src/main/java/net/ypresto/androidtranscoder/utils/MediaExtractorUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Yuya Tanaka + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.ypresto.androidtranscoder.utils; + +import android.media.MediaExtractor; +import android.media.MediaFormat; + +public class MediaExtractorUtils { + + private MediaExtractorUtils() { + } + + public static class TrackResult { + + private TrackResult() { + } + + public int mVideoTrackIndex; + public String mVideoTrackMime; + public MediaFormat mVideoTrackFormat; + public int mAudioTrackIndex; + public String mAudioTrackMime; + public MediaFormat mAudioTrackFormat; + } + + public static TrackResult getFirstVideoAndAudioTrack(MediaExtractor extractor) { + TrackResult trackResult = new TrackResult(); + trackResult.mVideoTrackIndex = -1; + trackResult.mAudioTrackIndex = -1; + int trackCount = extractor.getTrackCount(); + for (int i = 0; i < trackCount; i++) { + MediaFormat format = extractor.getTrackFormat(i); + String mime = format.getString(MediaFormat.KEY_MIME); + if (trackResult.mVideoTrackIndex < 0 && mime.startsWith("video/")) { + trackResult.mVideoTrackIndex = i; + trackResult.mVideoTrackMime = mime; + trackResult.mVideoTrackFormat = format; + } else if (trackResult.mAudioTrackIndex < 0 && mime.startsWith("audio/")) { + trackResult.mAudioTrackIndex = i; + trackResult.mAudioTrackMime = mime; + trackResult.mAudioTrackFormat = format; + } + if (trackResult.mVideoTrackIndex >= 0 && trackResult.mAudioTrackIndex >= 0) break; + } + if (trackResult.mVideoTrackIndex < 0 || trackResult.mAudioTrackIndex < 0) { + throw new IllegalArgumentException("extractor does not contain video and/or audio tracks."); + } + return trackResult; + } +} diff --git a/libs/gson-1.1/build.gradle b/libs/gson-1.1/build.gradle new file mode 100644 index 000000000..72090a0aa --- /dev/null +++ b/libs/gson-1.1/build.gradle @@ -0,0 +1,2 @@ +configurations.create("default") +artifacts.add("default", file('gson-1.1.jar')) \ No newline at end of file diff --git a/libs/gson-1.1/gson-1.1.jar b/libs/gson-1.1/gson-1.1.jar new file mode 100644 index 000000000..89018e100 Binary files /dev/null and b/libs/gson-1.1/gson-1.1.jar differ diff --git a/libs/xmpp-addr/build.gradle b/libs/xmpp-addr/build.gradle index 2d30752c4..0675efe7d 100644 --- a/libs/xmpp-addr/build.gradle +++ b/libs/xmpp-addr/build.gradle @@ -7,7 +7,7 @@ repositories { } dependencies { - implementation 'rocks.xmpp:precis:1.0.0' + implementation 'rocks.xmpp:precis:1.1.0' } sourceCompatibility = "8" diff --git a/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/FullJid.java b/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/FullJid.java index 24130fd1b..d0eb8e6fe 100644 --- a/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/FullJid.java +++ b/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/FullJid.java @@ -24,10 +24,6 @@ package rocks.xmpp.addr; -import rocks.xmpp.precis.PrecisProfile; -import rocks.xmpp.precis.PrecisProfiles; -import rocks.xmpp.util.cache.LruCache; - import java.net.IDN; import java.nio.charset.Charset; import java.text.Normalizer; @@ -35,6 +31,10 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import rocks.xmpp.precis.PrecisProfile; +import rocks.xmpp.precis.PrecisProfiles; +import rocks.xmpp.util.cache.LruCache; + /** * The implementation of the JID as described in Extensible Messaging and Presence Protocol (XMPP): Address Format. *

diff --git a/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/Jid.java b/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/Jid.java index 4f4b041e7..1211c121c 100644 --- a/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/Jid.java +++ b/libs/xmpp-addr/src/main/java/rocks/xmpp/addr/Jid.java @@ -24,9 +24,10 @@ package rocks.xmpp.addr; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.io.Serializable; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + /** * Represents the JID as described in Extensible Messaging and Presence Protocol (XMPP): Address Format. *

diff --git a/metadata/de/description.txt b/metadata/de/description.txt new file mode 100644 index 000000000..3566d3f11 --- /dev/null +++ b/metadata/de/description.txt @@ -0,0 +1,32 @@ +Pix-Art Messenger ist ein Fork der offiziellen Android-App Conversations mit +einigen Änderungen, insbesondere zur Verbesserung der Benutzerfreundlichkeit, +um den Umstieg von oftmals vorinstallierten Messengern zu erleichtern. + +Verbindungssicherheit und der Schutz persönlicher Daten ist uns sehr wichtig, +deshalb erzwingen wir verschlüsselte Verbindungen zwischen Messenger und Server, +was es nahezu unmöglich macht, deine Nachrichten durch Fremde abzufangen und zu +lesen. +Wem das nicht genug ist, kann auf die integrierte Nachrichtenverschlüsselung von +Ende-zu-Ende, wie OMEMO, OTR oder OpenPGP, zurückgreifen. +Damit lassen sich Nachrichten auf dem Sendegerät verschlüsseln und nur durch das +für den Empfang bestimmte Empfangsgerät entschlüsseln. +Seit Version 1.20.0 wird ein Hinweis direkt im Chat angezeigt, sofern du +unverschlüsselt schreibst und eine Verschlüsselung möglich ist. + +Features: +* Ende-zu-Ende Verschlüsselung entweder mit OMEMO, OTR oder OpenPGP +* Austausch von Bildern sowie anderen Dateien +* Standorte senden und empfangen +* Sprachnachrichten senden und empfangen +* Integration von Profilbildern (Avatare) deiner Kontakte +* Synchronisiere Nachrichtenverlauf mit anderen Clients +* Konferenzen bzw. Gruppenchats +* Adressbuchintegration (ein Austausch deines Adressbuches mit dem Server findet nicht statt) +* sehr geringe Akku-Belastung +* Status Nachrichten +* tägliches Backup der Datenbank auf den lokalen Speicher + +Benötigst du Hilfe? Bitte tritt doch einfach dem Support Gruppenchat support@room.pix-art.de bei. + +Anti-Features: +* Tracking - Das Kartenvorschaubild wird von einem Skript erzeugt, das OSM-Kartenkacheln auf dem Entwickler-Server lädt. Der Server protokolliert: Datum, Uhrzeit, GPS-Koordinaten, Zoomstufe und eine anonymisierte IP. diff --git a/metadata/de/summary.txt b/metadata/de/summary.txt new file mode 100644 index 000000000..139c398ec --- /dev/null +++ b/metadata/de/summary.txt @@ -0,0 +1 @@ +ist ein Open Source XMPP/Jabber Messenger für Android diff --git a/metadata/en-US/description.txt b/metadata/en-US/description.txt new file mode 100644 index 000000000..8af60947d --- /dev/null +++ b/metadata/en-US/description.txt @@ -0,0 +1,29 @@ +Pix-Art Messenger is a fork of Conversations with some changes, +to improve usability. + +Connection security and the protection of personal data are very important for +us, so we enforce encrypted connections between messenger and server, making it +almost impossible to intercept and read your messages by strangers. +If that's not enough for you, end-to-end message encryption such as OMEMO, OTR +or OpenPGP can be used. This allows messages to be encrypted on the sending +device and decrypted only by the receiving device intended for reception. +Since version 1.20.0, a hint is displayed directly in the chat, as long as you +write unencrypted and encryption is possible. + +Features: +* End-to-end encryption with either OMEMO, OTR or OpenPGP +* Send and receive images as well as other files +* Send and receive locations +* Send and receive voice messages +* Integration of profile pictures (avatars) of your contacts +* Synchronize message history with other clients +* Conferences or group chats +* Address book integration (there is no exchange of your address book with the server) +* Very low battery consumption +* Status messages +* Daily backup of database to local storage + +You need help? Please join our support group chat support@room.pix-art.de. + +Anti-Features: +* Tracking - The map preview image is generated by a script that loads OSM map tiles on the developers server. The server will log: date, time, GPS coordinates, zoom level and an anonymised IP. diff --git a/metadata/en-US/featureGraphic.png b/metadata/en-US/featureGraphic.png new file mode 100644 index 000000000..c1b1d69aa Binary files /dev/null and b/metadata/en-US/featureGraphic.png differ diff --git a/metadata/en-US/phoneScreenshots/00.png b/metadata/en-US/phoneScreenshots/00.png new file mode 100644 index 000000000..984d0d55a Binary files /dev/null and b/metadata/en-US/phoneScreenshots/00.png differ diff --git a/metadata/en-US/phoneScreenshots/01.png b/metadata/en-US/phoneScreenshots/01.png new file mode 100644 index 000000000..a1c015ecf Binary files /dev/null and b/metadata/en-US/phoneScreenshots/01.png differ diff --git a/metadata/en-US/phoneScreenshots/02.png b/metadata/en-US/phoneScreenshots/02.png new file mode 100644 index 000000000..5a25c1b5b Binary files /dev/null and b/metadata/en-US/phoneScreenshots/02.png differ diff --git a/metadata/en-US/phoneScreenshots/03.png b/metadata/en-US/phoneScreenshots/03.png new file mode 100644 index 000000000..75fc5f9c9 Binary files /dev/null and b/metadata/en-US/phoneScreenshots/03.png differ diff --git a/metadata/en-US/phoneScreenshots/04.png b/metadata/en-US/phoneScreenshots/04.png new file mode 100644 index 000000000..3c888ee69 Binary files /dev/null and b/metadata/en-US/phoneScreenshots/04.png differ diff --git a/metadata/en-US/phoneScreenshots/05.png b/metadata/en-US/phoneScreenshots/05.png new file mode 100644 index 000000000..fa30cf4f9 Binary files /dev/null and b/metadata/en-US/phoneScreenshots/05.png differ diff --git a/metadata/en-US/phoneScreenshots/06.png b/metadata/en-US/phoneScreenshots/06.png new file mode 100644 index 000000000..f4dbc9cb3 Binary files /dev/null and b/metadata/en-US/phoneScreenshots/06.png differ diff --git a/metadata/en-US/summary.txt b/metadata/en-US/summary.txt new file mode 100644 index 000000000..91e2842b3 --- /dev/null +++ b/metadata/en-US/summary.txt @@ -0,0 +1 @@ +is an Open Source XMPP/Jabber Messenger for Android diff --git a/proguard-rules.pro b/proguard-rules.pro index c8b4089c5..cb241d3fb 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -1,17 +1,20 @@ -dontobfuscate +-keep class de.pixart.messenger.** -keep class eu.siacs.conversations.** - -keep class org.whispersystems.** - -keep class com.kyleduo.switchbutton.Configuration - -keep class com.soundcloud.android.crop.** - -keep class com.google.android.gms.** - -keep class org.openintents.openpgp.* --keep class org.webrtc.** { *; } +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public class * extends com.bumptech.glide.module.AppGlideModule +-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { + **[] $VALUES; + public *; +} +-keep class com.squareup.okhttp.** { *; } +-keep interface com.squareup.okhttp.** { *; } -dontwarn javax.mail.internet.MimeMessage -dontwarn javax.mail.internet.MimeBodyPart @@ -30,6 +33,20 @@ !transient ; } +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform + + + # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and # EnclosingMethod is required to use InnerClasses. -keepattributes Signature, InnerClasses, EnclosingMethod @@ -58,4 +75,4 @@ # With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy # and replaces all potential values with null. Explicitly keeping the interfaces prevents this. -if interface * { @retrofit2.http.* ; } --keep,allowobfuscation interface <1> +-keep,allowobfuscation interface <1> \ No newline at end of file diff --git a/screenshots.png b/screenshots.png deleted file mode 100644 index f742f1f5c..000000000 Binary files a/screenshots.png and /dev/null differ diff --git a/screenshots.xcf b/screenshots.xcf deleted file mode 100644 index 7e3aebc68..000000000 Binary files a/screenshots.xcf and /dev/null differ diff --git a/settings.gradle b/settings.gradle index 746b2c7e3..333bfa6bc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ +include ':libs:android-transcoder' include ':libs:xmpp-addr' -rootProject.name = 'Conversations' +rootProject.name = 'PixArtMessenger' diff --git a/src/compat/java/eu/siacs/conversations/utils/EmojiWrapper.java b/src/compat/java/eu/siacs/conversations/utils/EmojiWrapper.java deleted file mode 100644 index 9466531a3..000000000 --- a/src/compat/java/eu/siacs/conversations/utils/EmojiWrapper.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2017, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package eu.siacs.conversations.utils; - -import android.support.text.emoji.EmojiCompat; - -public class EmojiWrapper { - - public static CharSequence transform(CharSequence input) { - try { - if (EmojiCompat.get().getLoadState() == EmojiCompat.LOAD_STATE_SUCCEEDED) { - return EmojiCompat.get().process(input); - } else { - return input; - } - } catch (IllegalStateException e) { - return input; - } - } -} diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml deleted file mode 100644 index 2100d9719..000000000 --- a/src/conversations/AndroidManifest.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java b/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java deleted file mode 100644 index f84f47d69..000000000 --- a/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java +++ /dev/null @@ -1,104 +0,0 @@ -package eu.siacs.conversations.ui; - -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.databinding.DataBindingUtil; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.MenuItem; - -import java.util.List; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.databinding.ActivityPickServerBinding; -import eu.siacs.conversations.entities.Account; - -public class PickServerActivity extends XmppActivity { - - @Override - protected void refreshUiReal() { - - } - - @Override - void onBackendConnected() { - - } - - @Override - public void onStart() { - super.onStart(); - final int theme = findTheme(); - if (this.mTheme != theme) { - recreate(); - } - } - - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - if (item.getItemId() == android.R.id.home) { - startActivity(new Intent(this, WelcomeActivity.class)); - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onBackPressed() { - startActivity(new Intent(this, WelcomeActivity.class)); - super.onBackPressed(); - } - - @Override - public void onNewIntent(Intent intent) { - if (intent != null) { - setIntent(intent); - } - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - if (getResources().getBoolean(R.bool.portrait_only)) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - super.onCreate(savedInstanceState); - ActivityPickServerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_pick_server); - setSupportActionBar((Toolbar) binding.toolbar); - configureActionBar(getSupportActionBar()); - binding.useCim.setOnClickListener(v -> { - final Intent intent = new Intent(this, MagicCreateActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - addInviteUri(intent); - startActivity(intent); - }); - binding.useOwnProvider.setOnClickListener(v -> { - List accounts = xmppConnectionService.getAccounts(); - Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, true); - if (accounts.size() == 1) { - intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString()); - intent.putExtra("init", true); - } else if (accounts.size() >= 1) { - intent = new Intent(this, ManageAccountActivity.class); - } - addInviteUri(intent); - startActivity(intent); - }); - - } - - public void addInviteUri(Intent intent) { - StartConversationActivity.addInviteUri(intent, getIntent()); - } - - public static void launch(AppCompatActivity activity) { - Intent intent = new Intent(activity, PickServerActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - activity.startActivity(intent); - activity.overridePendingTransition(0, 0); - } - -} diff --git a/src/conversations/new_launcher-web.png b/src/conversations/new_launcher-web.png deleted file mode 100644 index 76057f9fe..000000000 Binary files a/src/conversations/new_launcher-web.png and /dev/null differ diff --git a/src/conversations/res/drawable-hdpi/ic_notification.png b/src/conversations/res/drawable-hdpi/ic_notification.png deleted file mode 100644 index 32325e5f7..000000000 Binary files a/src/conversations/res/drawable-hdpi/ic_notification.png and /dev/null differ diff --git a/src/conversations/res/drawable-hdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-hdpi/ic_unarchive_white_24dp.png deleted file mode 100644 index 18730f12f..000000000 Binary files a/src/conversations/res/drawable-hdpi/ic_unarchive_white_24dp.png and /dev/null differ diff --git a/src/conversations/res/drawable-hdpi/main_logo.png b/src/conversations/res/drawable-hdpi/main_logo.png deleted file mode 100644 index 42d553dd7..000000000 Binary files a/src/conversations/res/drawable-hdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-hdpi/splash_logo.png b/src/conversations/res/drawable-hdpi/splash_logo.png deleted file mode 100644 index d8efc71af..000000000 Binary files a/src/conversations/res/drawable-hdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-mdpi/ic_notification.png b/src/conversations/res/drawable-mdpi/ic_notification.png deleted file mode 100644 index 379720e94..000000000 Binary files a/src/conversations/res/drawable-mdpi/ic_notification.png and /dev/null differ diff --git a/src/conversations/res/drawable-mdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-mdpi/ic_unarchive_white_24dp.png deleted file mode 100644 index 8ec62cd34..000000000 Binary files a/src/conversations/res/drawable-mdpi/ic_unarchive_white_24dp.png and /dev/null differ diff --git a/src/conversations/res/drawable-mdpi/main_logo.png b/src/conversations/res/drawable-mdpi/main_logo.png deleted file mode 100644 index d81b73638..000000000 Binary files a/src/conversations/res/drawable-mdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-mdpi/splash_logo.png b/src/conversations/res/drawable-mdpi/splash_logo.png deleted file mode 100644 index 1b10d1f91..000000000 Binary files a/src/conversations/res/drawable-mdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xhdpi/ic_notification.png b/src/conversations/res/drawable-xhdpi/ic_notification.png deleted file mode 100644 index e14da5dc6..000000000 Binary files a/src/conversations/res/drawable-xhdpi/ic_notification.png and /dev/null differ diff --git a/src/conversations/res/drawable-xhdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-xhdpi/ic_unarchive_white_24dp.png deleted file mode 100644 index a0a1509a1..000000000 Binary files a/src/conversations/res/drawable-xhdpi/ic_unarchive_white_24dp.png and /dev/null differ diff --git a/src/conversations/res/drawable-xhdpi/main_logo.png b/src/conversations/res/drawable-xhdpi/main_logo.png deleted file mode 100644 index 19fbe70b8..000000000 Binary files a/src/conversations/res/drawable-xhdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xhdpi/splash_logo.png b/src/conversations/res/drawable-xhdpi/splash_logo.png deleted file mode 100644 index 9458b791c..000000000 Binary files a/src/conversations/res/drawable-xhdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxhdpi/ic_notification.png b/src/conversations/res/drawable-xxhdpi/ic_notification.png deleted file mode 100644 index 6adbc8f64..000000000 Binary files a/src/conversations/res/drawable-xxhdpi/ic_notification.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxhdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-xxhdpi/ic_unarchive_white_24dp.png deleted file mode 100644 index 20d015751..000000000 Binary files a/src/conversations/res/drawable-xxhdpi/ic_unarchive_white_24dp.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxhdpi/main_logo.png b/src/conversations/res/drawable-xxhdpi/main_logo.png deleted file mode 100644 index cc091c437..000000000 Binary files a/src/conversations/res/drawable-xxhdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxhdpi/splash_logo.png b/src/conversations/res/drawable-xxhdpi/splash_logo.png deleted file mode 100644 index 83c2abe6a..000000000 Binary files a/src/conversations/res/drawable-xxhdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxxhdpi/ic_notification.png b/src/conversations/res/drawable-xxxhdpi/ic_notification.png deleted file mode 100644 index 65d106b8c..000000000 Binary files a/src/conversations/res/drawable-xxxhdpi/ic_notification.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png deleted file mode 100644 index a789520ba..000000000 Binary files a/src/conversations/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxxhdpi/main_logo.png b/src/conversations/res/drawable-xxxhdpi/main_logo.png deleted file mode 100644 index 3e6ddd877..000000000 Binary files a/src/conversations/res/drawable-xxxhdpi/main_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable-xxxhdpi/splash_logo.png b/src/conversations/res/drawable-xxxhdpi/splash_logo.png deleted file mode 100644 index 349070ba2..000000000 Binary files a/src/conversations/res/drawable-xxxhdpi/splash_logo.png and /dev/null differ diff --git a/src/conversations/res/drawable/ic_launcher_foreground.xml b/src/conversations/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 07a7ee7eb..000000000 --- a/src/conversations/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - diff --git a/src/conversations/res/layout/activity_pick_server.xml b/src/conversations/res/layout/activity_pick_server.xml deleted file mode 100644 index 16be52ec4..000000000 --- a/src/conversations/res/layout/activity_pick_server.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - -