~singpolyma/cheogram-android

66731f3f38b1eed5f5faf5f2f4823f513f6b940e — Stephen Paul Weber 10 months ago 14ca6d5 + afe2575
Merge branch 'fastlane'

* fastlane:
  Screenshot add contact options speeddial
  startConversation screenshot
  Draft new full description
  Generate a screenshot with fastlane
A Gemfile => Gemfile +6 -0
@@ 0,0 1,6 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "fastlane"
gem "screengrab"

A Gemfile.lock => Gemfile.lock +221 -0
@@ 0,0 1,221 @@
GEM
  remote: https://rubygems.org/
  specs:
    CFPropertyList (3.0.5)
      rexml
    addressable (2.8.0)
      public_suffix (>= 2.0.2, < 5.0)
    artifactory (3.0.15)
    atomos (0.1.3)
    aws-eventstream (1.2.0)
    aws-partitions (1.566.0)
    aws-sdk-core (3.130.0)
      aws-eventstream (~> 1, >= 1.0.2)
      aws-partitions (~> 1, >= 1.525.0)
      aws-sigv4 (~> 1.1)
      jmespath (~> 1.0)
    aws-sdk-kms (1.55.0)
      aws-sdk-core (~> 3, >= 3.127.0)
      aws-sigv4 (~> 1.1)
    aws-sdk-s3 (1.113.0)
      aws-sdk-core (~> 3, >= 3.127.0)
      aws-sdk-kms (~> 1)
      aws-sigv4 (~> 1.4)
    aws-sigv4 (1.4.0)
      aws-eventstream (~> 1, >= 1.0.2)
    babosa (1.0.4)
    claide (1.1.0)
    colored (1.2)
    colored2 (3.1.2)
    commander (4.6.0)
      highline (~> 2.0.0)
    declarative (0.0.20)
    digest-crc (0.6.4)
      rake (>= 12.0.0, < 14.0.0)
    domain_name (0.5.20190701)
      unf (>= 0.0.5, < 1.0.0)
    dotenv (2.7.6)
    emoji_regex (3.2.3)
    excon (0.91.0)
    faraday (1.10.0)
      faraday-em_http (~> 1.0)
      faraday-em_synchrony (~> 1.0)
      faraday-excon (~> 1.1)
      faraday-httpclient (~> 1.0)
      faraday-multipart (~> 1.0)
      faraday-net_http (~> 1.0)
      faraday-net_http_persistent (~> 1.0)
      faraday-patron (~> 1.0)
      faraday-rack (~> 1.0)
      faraday-retry (~> 1.0)
      ruby2_keywords (>= 0.0.4)
    faraday-cookie_jar (0.0.7)
      faraday (>= 0.8.0)
      http-cookie (~> 1.0.0)
    faraday-em_http (1.0.0)
    faraday-em_synchrony (1.0.0)
    faraday-excon (1.1.0)
    faraday-httpclient (1.0.1)
    faraday-multipart (1.0.3)
      multipart-post (>= 1.2, < 3)
    faraday-net_http (1.0.1)
    faraday-net_http_persistent (1.2.0)
    faraday-patron (1.0.0)
    faraday-rack (1.0.0)
    faraday-retry (1.0.3)
    faraday_middleware (1.2.0)
      faraday (~> 1.0)
    fastimage (2.2.6)
    fastlane (2.204.3)
      CFPropertyList (>= 2.3, < 4.0.0)
      addressable (>= 2.8, < 3.0.0)
      artifactory (~> 3.0)
      aws-sdk-s3 (~> 1.0)
      babosa (>= 1.0.3, < 2.0.0)
      bundler (>= 1.12.0, < 3.0.0)
      colored
      commander (~> 4.6)
      dotenv (>= 2.1.1, < 3.0.0)
      emoji_regex (>= 0.1, < 4.0)
      excon (>= 0.71.0, < 1.0.0)
      faraday (~> 1.0)
      faraday-cookie_jar (~> 0.0.6)
      faraday_middleware (~> 1.0)
      fastimage (>= 2.1.0, < 3.0.0)
      gh_inspector (>= 1.1.2, < 2.0.0)
      google-apis-androidpublisher_v3 (~> 0.3)
      google-apis-playcustomapp_v1 (~> 0.1)
      google-cloud-storage (~> 1.31)
      highline (~> 2.0)
      json (< 3.0.0)
      jwt (>= 2.1.0, < 3)
      mini_magick (>= 4.9.4, < 5.0.0)
      multipart-post (~> 2.0.0)
      naturally (~> 2.2)
      optparse (~> 0.1.1)
      plist (>= 3.1.0, < 4.0.0)
      rubyzip (>= 2.0.0, < 3.0.0)
      security (= 0.1.3)
      simctl (~> 1.6.3)
      terminal-notifier (>= 2.0.0, < 3.0.0)
      terminal-table (>= 1.4.5, < 2.0.0)
      tty-screen (>= 0.6.3, < 1.0.0)
      tty-spinner (>= 0.8.0, < 1.0.0)
      word_wrap (~> 1.0.0)
      xcodeproj (>= 1.13.0, < 2.0.0)
      xcpretty (~> 0.3.0)
      xcpretty-travis-formatter (>= 0.0.3)
    gh_inspector (1.1.3)
    google-apis-androidpublisher_v3 (0.16.0)
      google-apis-core (>= 0.4, < 2.a)
    google-apis-core (0.4.2)
      addressable (~> 2.5, >= 2.5.1)
      googleauth (>= 0.16.2, < 2.a)
      httpclient (>= 2.8.1, < 3.a)
      mini_mime (~> 1.0)
      representable (~> 3.0)
      retriable (>= 2.0, < 4.a)
      rexml
      webrick
    google-apis-iamcredentials_v1 (0.10.0)
      google-apis-core (>= 0.4, < 2.a)
    google-apis-playcustomapp_v1 (0.7.0)
      google-apis-core (>= 0.4, < 2.a)
    google-apis-storage_v1 (0.11.0)
      google-apis-core (>= 0.4, < 2.a)
    google-cloud-core (1.6.0)
      google-cloud-env (~> 1.0)
      google-cloud-errors (~> 1.0)
    google-cloud-env (1.5.0)
      faraday (>= 0.17.3, < 2.0)
    google-cloud-errors (1.2.0)
    google-cloud-storage (1.36.1)
      addressable (~> 2.8)
      digest-crc (~> 0.4)
      google-apis-iamcredentials_v1 (~> 0.1)
      google-apis-storage_v1 (~> 0.1)
      google-cloud-core (~> 1.6)
      googleauth (>= 0.16.2, < 2.a)
      mini_mime (~> 1.0)
    googleauth (1.1.2)
      faraday (>= 0.17.3, < 3.a)
      jwt (>= 1.4, < 3.0)
      memoist (~> 0.16)
      multi_json (~> 1.11)
      os (>= 0.9, < 2.0)
      signet (>= 0.16, < 2.a)
    highline (2.0.3)
    http-cookie (1.0.4)
      domain_name (~> 0.5)
    httpclient (2.8.3)
    jmespath (1.6.1)
    json (2.6.1)
    jwt (2.3.0)
    memoist (0.16.2)
    mini_magick (4.11.0)
    mini_mime (1.1.2)
    multi_json (1.15.0)
    multipart-post (2.0.0)
    nanaimo (0.3.0)
    naturally (2.2.1)
    optparse (0.1.1)
    os (1.1.4)
    plist (3.6.0)
    public_suffix (4.0.6)
    rake (13.0.6)
    representable (3.1.1)
      declarative (< 0.1.0)
      trailblazer-option (>= 0.1.1, < 0.2.0)
      uber (< 0.2.0)
    retriable (3.1.2)
    rexml (3.2.5)
    rouge (2.0.7)
    ruby2_keywords (0.0.5)
    rubyzip (2.3.2)
    screengrab (1.0.0)
      fastlane (>= 2.0.0, < 3.0.0)
    security (0.1.3)
    signet (0.16.1)
      addressable (~> 2.8)
      faraday (>= 0.17.5, < 3.0)
      jwt (>= 1.5, < 3.0)
      multi_json (~> 1.10)
    simctl (1.6.8)
      CFPropertyList
      naturally
    terminal-notifier (2.0.0)
    terminal-table (1.8.0)
      unicode-display_width (~> 1.1, >= 1.1.1)
    trailblazer-option (0.1.2)
    tty-cursor (0.7.1)
    tty-screen (0.8.1)
    tty-spinner (0.9.3)
      tty-cursor (~> 0.7)
    uber (0.1.0)
    unf (0.1.4)
      unf_ext
    unf_ext (0.0.8)
    unicode-display_width (1.8.0)
    webrick (1.7.0)
    word_wrap (1.0.0)
    xcodeproj (1.21.0)
      CFPropertyList (>= 2.3.3, < 4.0)
      atomos (~> 0.1.3)
      claide (>= 1.0.2, < 2.0)
      colored2 (~> 3.1)
      nanaimo (~> 0.3.0)
      rexml (~> 3.2.4)
    xcpretty (0.3.0)
      rouge (~> 2.0.7)
    xcpretty-travis-formatter (1.0.1)
      xcpretty (~> 0.2, >= 0.0.7)

PLATFORMS
  x86_64-linux

DEPENDENCIES
  fastlane
  screengrab

BUNDLED WITH
   2.2.5

M build.gradle => build.gradle +1 -1
@@ 121,12 121,12 @@ android {
        targetSdkVersion 29
        versionCode 42024 + grgit.tag.list().size()
        versionName grgit.describe(tags: true, always: true)
        archivesBaseName += "-$versionName"
        applicationId "eu.siacs.conversations"
        resValue "string", "applicationId", applicationId
        def appName = "Conversations"
        resValue "string", "app_name", appName
        buildConfigField "String", "APP_NAME", "\"$appName\"";
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }



A fastlane/Appfile => fastlane/Appfile +2 -0
@@ 0,0 1,2 @@
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("com.cheogram.android") # e.g. com.krausefx.app

A fastlane/Fastfile => fastlane/Fastfile +38 -0
@@ 0,0 1,38 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#		 https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#		 https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:android)

platform :android do
	desc "Build debug and test APK for screenshots"
	lane :build_for_screengrab do
		build_android_app(
			task: 'assemble',
			flavor: 'CheogramFree',
			build_type: 'Debug'
		)
		build_android_app(
			task: 'assemble',
			flavor: 'CheogramFree',
			build_type: 'DebugAndroidTest'
		)
	end

	desc "Build and take screenshots"
	lane :build_and_screengrab do
		build_for_screengrab
		capture_android_screenshots
	end
end

A fastlane/Screengrabfile => fastlane/Screengrabfile +5 -0
@@ 0,0 1,5 @@
locales ['en-US']
clear_previous_screenshots true
tests_apk_path 'build/outputs/apk/androidTest/cheogramFree/debug/Conversations-cheogram-free-debug-androidTest.apk'
app_apk_path 'build/outputs/apk/cheogramFree/debug/Conversations-cheogram-free-debug.apk'
test_instrumentation_runner 'androidx.test.runner.AndroidJUnitRunner'
\ No newline at end of file

M fastlane/metadata/android/en_US/full_description.txt => fastlane/metadata/android/en_US/full_description.txt +10 -33
@@ 1,39 1,16 @@
Easy to use, reliable, battery friendly. With built-in support for images, group chats and e2e encryption.
The Cheogram Android app allows you to join a worldwide communication network.  It especially focuses on features useful to users who want to contact those on other networks as well, such as SMS-enabled phone numbers.

Design principles:
Unique Features:

* 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 <a href="http://conversations.im/omemo/">OMEMO</a> or <a href="http://openpgp.org/about/">OpenPGP</a>
* Sending and receiving images
* Encrypted audio and video calls (DTLS-SRTP)
* Intuitive UI that follows Android Design guidelines
* Pictures / Avatars for your Contacts
* Syncs with desktop client
* Conferences (with support for bookmarks)
* Messages with both media and text, including animated media
* Unobtrusive display of subject lines, where present
* Links to known contacts are shown with their name
* Integrates with gateways add contact flows
* When using a gateway to the phone network, integrate with the native Android Phone app
* 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.
Where to get service:

These XEPs are - as of now:
Cheogram Android requires you have an account with a Jabber service.  You can run your own service, or use one provided by someone else, for example: https://snikket.org/hosting/

* 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.
Art in screenshots is from https://www.peppercarrot.com by David Revoy, CC-BY. Artwork has been modified to crop out sections for avatars and photos, and in some cases add transparency. Use of this artwork does not imply endoresement of this project by the artist.

M fastlane/metadata/android/en_US/images/phoneScreenshots/conversation.png => fastlane/metadata/android/en_US/images/phoneScreenshots/conversation.png +0 -0
A fastlane/metadata/android/en_US/images/phoneScreenshots/startConversation.png => fastlane/metadata/android/en_US/images/phoneScreenshots/startConversation.png +0 -0
A fastlane/metadata/android/en_US/images/phoneScreenshots/startConversationOptions.png => fastlane/metadata/android/en_US/images/phoneScreenshots/startConversationOptions.png +0 -0
A src/androidTest/java/com/cheogram/android/test/ScreenshotTest.java => src/androidTest/java/com/cheogram/android/test/ScreenshotTest.java +169 -0
@@ 0,0 1,169 @@
package com.cheogram.android.test;

import java.util.concurrent.TimeoutException;
import java.lang.Thread;
import java.util.Arrays;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ServiceTestRule;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

import tools.fastlane.screengrab.Screengrab;
import tools.fastlane.screengrab.cleanstatusbar.CleanStatusBar;
import tools.fastlane.screengrab.locale.LocaleTestRule;

import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.test.R;
import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.StartConversationActivity;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;

@RunWith(AndroidJUnit4.class)
public class ScreenshotTest {

	static String pkg = InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
	static XmppConnectionService xmppConnectionService;
	static Account account;

	@ClassRule
	public static final LocaleTestRule localeTestRule = new LocaleTestRule();

	@ClassRule
	public static final ServiceTestRule xmppServiceRule = new ServiceTestRule();

	@BeforeClass
	public static void setup() throws TimeoutException {
		CleanStatusBar.enableWithDefaults();

		Intent intent = new Intent(ApplicationProvider.getApplicationContext(), XmppConnectionService.class);
		intent.setAction("ui");
		xmppConnectionService = ((XmppConnectionBinder) xmppServiceRule.bindService(intent)).getService();
		account = xmppConnectionService.findAccountByJid(Jid.of("carrot@chaosah.hereva"));
		if (account == null) {
			account = new Account(
				Jid.of("carrot@chaosah.hereva"),
				"orangeandfurry"
			);
			xmppConnectionService.createAccount(account);
		}

		Uri avatarUri = Uri.parse("android.resource://" + pkg + "/" + String.valueOf(R.drawable.carrot));
		final Avatar avatar = xmppConnectionService.getFileBackend().getPepAvatar(avatarUri, 192, Bitmap.CompressFormat.WEBP);
		xmppConnectionService.getFileBackend().save(avatar);
		account.setAvatar(avatar.getFilename());

		Contact cheogram = account.getRoster().getContact(Jid.of("cheogram.com"));
		cheogram.setOption(Contact.Options.IN_ROSTER);
		cheogram.setPhotoUri("android.resource://" + pkg + "/" + String.valueOf(R.drawable.cheogram));
		Presence cheogramPresence = Presence.parse(null, null, "");
		IqPacket discoPacket = new IqPacket(IqPacket.TYPE.RESULT);
		Element query = discoPacket.addChild("query", "http://jabber.org/protocol/disco#info");
		Element identity = query.addChild("identity");
		identity.setAttribute("category", "gateway");
		identity.setAttribute("type", "pstn");
		cheogramPresence.setServiceDiscoveryResult(new ServiceDiscoveryResult(discoPacket));
		cheogram.updatePresence("gw", cheogramPresence);
	}

	@AfterClass
	public static void teardown() {
		CleanStatusBar.disable();
	}

	@Test
	public void testConversation() throws FileBackend.FileCopyException, InterruptedException {
		Conversation conversation = xmppConnectionService.findOrCreateConversation(account, Jid.of("+15550737737@cheogram.com"), false, false);
		conversation.getContact().setOption(Contact.Options.IN_ROSTER);
		conversation.getContact().setSystemName("Pepper");
		conversation.getContact().setPhotoUri("android.resource://" + pkg + "/" + String.valueOf(R.drawable.pepper));

		Message voicemail = new Message(conversation, "", 0, Message.STATUS_RECEIVED);
		voicemail.setOob("https://example.com/thing.mp3");
		voicemail.setFileParams(new Message.FileParams("https://example.com/thing.mp3|5000|0|0|10000"));
		voicemail.setType(Message.TYPE_FILE);
		voicemail.setSubject("Voicemail Recording");

		Message transcript = new Message(conversation, "Where are you?", 0, Message.STATUS_RECEIVED);
		transcript.setSubject("Voicemail Transcription");

		Message picture = new Message(conversation, "", 0, Message.STATUS_SEND_RECEIVED);
		picture.setOob("https://example.com/thing.webp");
		picture.setType(Message.TYPE_FILE);
		xmppConnectionService.getFileBackend().copyFileToPrivateStorage(
			picture,
			Uri.parse("android.resource://" + pkg + "/" + String.valueOf(R.drawable.komona)),
			"image/webp"
		);
		xmppConnectionService.getFileBackend().updateFileParams(picture);

		conversation.addAll(0, Arrays.asList(
			voicemail,
			transcript,
			new Message(conversation, "Meow", 0, Message.STATUS_SEND_RECEIVED),
			picture,
			new Message(conversation, "👍", 0, Message.STATUS_RECEIVED)
		));

		ActivityScenario scenario = ActivityScenario.launch(ConversationsActivity.class);
		scenario.onActivity((Activity activity) -> {
			((ConversationsActivity) activity).switchToConversation(conversation);
		});
		InstrumentationRegistry.getInstrumentation().waitForIdleSync();
		Thread.sleep(100); // ImageView not paited yet after waitForIdleSync
		Screengrab.screenshot("conversation");
	}

	@Test
	public void testStartConversation() throws InterruptedException {
		ActivityScenario scenario = ActivityScenario.launch(StartConversationActivity.class);
		InstrumentationRegistry.getInstrumentation().waitForIdleSync();
		Thread.sleep(100); // ImageView not paited yet after waitForIdleSync
		Screengrab.screenshot("startConversation");
	}

	@Test
	public void testAddContact() throws InterruptedException {
		ActivityScenario scenario = ActivityScenario.launch(StartConversationActivity.class);
		onView(withId(eu.siacs.conversations.R.id.speed_dial)).perform(click());
		Screengrab.screenshot("startConversationOptions");

		// Not actually online, so can't screenshot the gateway selector yet
		/*onView(withId(eu.siacs.conversations.R.id.create_contact)).perform(click());
		InstrumentationRegistry.getInstrumentation().waitForIdleSync();
		Thread.sleep(10000); // ImageView not paited yet after waitForIdleSync
		Screengrab.screenshot("addContact");*/
	}
}

A src/androidTest/res/drawable/carrot.webp => src/androidTest/res/drawable/carrot.webp +0 -0
A src/androidTest/res/drawable/cheogram.png => src/androidTest/res/drawable/cheogram.png +0 -0
A src/androidTest/res/drawable/komona.webp => src/androidTest/res/drawable/komona.webp +0 -0
A src/androidTest/res/drawable/pepper.webp => src/androidTest/res/drawable/pepper.webp +0 -0
M src/main/java/eu/siacs/conversations/utils/MimeUtils.java => src/main/java/eu/siacs/conversations/utils/MimeUtils.java +5 -8
@@ 528,15 528,12 @@ public final class MimeUtils {

    public static String guessMimeTypeFromUriAndMime(final Context context, final Uri uri, final String mime) {
        Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime " + uri + " and mime=" + mime);
        if (mime == null || mime.equals("application/octet-stream")) {
            final String guess = guessMimeTypeFromUri(context, uri);
            if (guess != null) {
                return guess;
            } else {
                return mime;
            }
        final String guess = guessMimeTypeFromUri(context, uri);
        if (guess != null) {
            return guess;
        } else {
            return mime;
        }
        return guessMimeTypeFromUri(context, uri);
    }

    public static String guessMimeTypeFromUri(Context context, Uri uri) {