diff --git a/CHANGELOG.md b/CHANGELOG.md index ff2b7b07b..88e9796fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +### Version 2.6.3 + +* Support for ?register and ?register;preauth XMPP uri parameters + +### Version 2.6.2 +* 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) + ### Version 2.6.1 * fixes for Jingle IBB file transfer * fixes for repeated corrections filling up the database diff --git a/build.gradle b/build.gradle index 703bfec86..9a9bb4a4e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.5.3' } } @@ -22,6 +22,8 @@ configurations { playstoreImplementation compatImplementation conversationsFreeCompatImplementation + conversationsPlaystoreCompatImplementation + conversationsPlaystoreSystemImplementation quicksyFreeCompatImplementation quicksyImplementation } @@ -36,8 +38,10 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } + conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:1.1") + conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:1.1") implementation 'org.sufficientlysecure:openpgp-api:10.0' - implementation ('com.theartofdev.edmodo:android-image-cropper:2.7.+') { + implementation('com.theartofdev.edmodo:android-image-cropper:2.7.+') { exclude group: 'com.android.support', module: 'appcompat-v7' exclude group: 'com.android.support', module: 'exifinterface' } @@ -61,16 +65,18 @@ dependencies { implementation "com.wefika:flowlayout:0.4.1" implementation 'net.ypresto.androidtranscoder:android-transcoder:0.3.0' implementation project(':libs:xmpp-addr') - implementation 'org.osmdroid:osmdroid-android:6.1.0' + implementation 'org.osmdroid:osmdroid-android:6.1.5' implementation 'org.hsluv:hsluv:0.2' implementation 'org.conscrypt:conscrypt-android:2.2.1' implementation 'me.drakeet.support:toastcompat:1.1.0' implementation "com.leinardi.android:speed-dial:2.0.1" - implementation 'com.squareup.retrofit2:retrofit:2.6.1' - implementation 'com.squareup.retrofit2:converter-gson:2.6.1' - implementation 'com.squareup.okhttp3:okhttp:3.12.6' + //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.7' implementation 'com.google.guava:guava:27.1-android' - quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.10.16' + quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.11.1' } ext { @@ -84,8 +90,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 25 - versionCode 351 - versionName "2.6.1" + versionCode 360 + versionName "2.6.3" archivesBaseName += "-$versionName" applicationId "eu.sum7.conversations" resValue "string", "applicationId", applicationId @@ -141,12 +147,12 @@ android { sourceSets { quicksyFreeCompat { java { - srcDirs 'src/freeCompat/java' + srcDir 'src/freeCompat/java' } } quicksyPlaystoreCompat { java { - srcDirs 'src/playstoreCompat/java' + srcDir 'src/playstoreCompat/java' } res { srcDir 'src/playstoreCompat/res' @@ -160,12 +166,19 @@ android { } conversationsFreeCompat { java { - srcDirs 'src/freeCompat/java' + srcDir 'src/freeCompat/java' + srcDir 'src/conversationsFree/java' + } + } + conversationsFreeSystem { + java { + srcDir 'src/conversationsFree/java' } } conversationsPlaystoreCompat { java { - srcDirs 'src/playstoreCompat/java' + srcDir 'src/playstoreCompat/java' + srcDir 'src/conversationsPlaystore/java' } res { srcDir 'src/playstoreCompat/res' @@ -173,6 +186,9 @@ android { } } conversationsPlaystoreSystem { + java { + srcDir 'src/conversationsPlaystore/java' + } res { srcDir 'src/conversationsPlaystore/res' } @@ -193,7 +209,6 @@ android { } - if (new File("signing.properties").exists()) { Properties props = new Properties() props.load(new FileInputStream(file("signing.properties"))) diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml index a91f4c129..2100d9719 100644 --- a/src/conversations/AndroidManifest.xml +++ b/src/conversations/AndroidManifest.xml @@ -11,32 +11,33 @@ + android:launchMode="singleTask" /> + android:launchMode="singleTask" /> + android:launchMode="singleTask" /> - + + - + + - diff --git a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java index 52c067258..405bb53b6 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java @@ -2,115 +2,163 @@ 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.widget.Toolbar; import android.text.Editable; import android.text.TextWatcher; import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import java.security.SecureRandom; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.databinding.MagicCreateBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.InstallReferrerUtils; import rocks.xmpp.addr.Jid; public class MagicCreateActivity extends XmppActivity implements TextWatcher { - private TextView mFullJidDisplay; - private EditText mUsername; + public static final String EXTRA_DOMAIN = "domain"; + public static final String EXTRA_PRE_AUTH = "pre_auth"; + public static final String EXTRA_USERNAME = "username"; - @Override - protected void refreshUiReal() { + private MagicCreateBinding binding; + private String domain; + private String username; + private String preAuth; - } + @Override + protected void refreshUiReal() { - @Override - void onBackendConnected() { + } - } + @Override + void onBackendConnected() { - @Override - public void onStart() { - super.onStart(); - final int theme = findTheme(); - if (this.mTheme != theme) { - recreate(); - } - } + } - @Override - protected void onCreate(final Bundle savedInstanceState) { - if (getResources().getBoolean(R.bool.portrait_only)) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - super.onCreate(savedInstanceState); - setContentView(R.layout.magic_create); - setSupportActionBar(findViewById(R.id.toolbar)); - configureActionBar(getSupportActionBar()); - mFullJidDisplay = findViewById(R.id.full_jid); - mUsername = findViewById(R.id.username); - Button next = findViewById(R.id.create_account); - next.setOnClickListener(v -> { - try { - String username = mUsername.getText().toString(); - Jid jid = Jid.of(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); - if (!jid.getEscapedLocal().equals(jid.getLocal())|| username.length() < 3) { - mUsername.setError(getString(R.string.invalid_username)); - mUsername.requestFocus(); - } else { - mUsername.setError(null); - Account account = xmppConnectionService.findAccountByJid(jid); - if (account == null) { - account = new Account(jid, CryptoHelper.createPassword(new SecureRandom())); - account.setOption(Account.OPTION_REGISTER, true); - account.setOption(Account.OPTION_DISABLED, true); - account.setOption(Account.OPTION_MAGIC_CREATE, true); - xmppConnectionService.createAccount(account); - } - Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toString()); - intent.putExtra("init", true); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show(); - StartConversationActivity.addInviteUri(intent, getIntent()); - startActivity(intent); - } - } catch (IllegalArgumentException e) { - mUsername.setError(getString(R.string.invalid_username)); - mUsername.requestFocus(); - } - }); - mUsername.addTextChangedListener(this); - } + @Override + public void onStart() { + super.onStart(); + final int theme = findTheme(); + if (this.mTheme != theme) { + recreate(); + } + } - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + @Override + protected void onCreate(final Bundle savedInstanceState) { + final Intent data = getIntent(); + this.domain = data == null ? null : data.getStringExtra(EXTRA_DOMAIN); + this.preAuth = data == null ? null : data.getStringExtra(EXTRA_PRE_AUTH); + this.username = data == null ? null : data.getStringExtra(EXTRA_USERNAME); + if (getResources().getBoolean(R.bool.portrait_only)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + super.onCreate(savedInstanceState); + this.binding = DataBindingUtil.setContentView(this, R.layout.magic_create); + setSupportActionBar((Toolbar) this.binding.toolbar); + configureActionBar(getSupportActionBar(), this.domain == null); + if (username != null && domain != null) { + binding.title.setText(R.string.your_server_invitation); + binding.instructions.setText(getString(R.string.magic_create_text_fixed, domain)); + binding.finePrint.setVisibility(View.INVISIBLE); + binding.username.setEnabled(false); + binding.username.setText(this.username); + updateFullJidInformation(this.username); + } else if (domain != null) { + binding.instructions.setText(getString(R.string.magic_create_text_on_x, domain)); + binding.finePrint.setVisibility(View.INVISIBLE); + } + binding.createAccount.setOnClickListener(v -> { + try { + final String username = binding.username.getText().toString(); + final Jid jid; + final boolean fixedUsername; + if (this.domain != null && this.username != null) { + fixedUsername = true; + jid = Jid.ofLocalAndDomain(this.username, this.domain); + } else if (this.domain != null) { + fixedUsername = false; + jid = Jid.ofLocalAndDomain(username, this.domain); + } else { + fixedUsername = false; + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); + } + if (!jid.getEscapedLocal().equals(jid.getLocal()) || (this.username == null && username.length() < 3)) { + binding.username.setError(getString(R.string.invalid_username)); + binding.username.requestFocus(); + } else { + binding.username.setError(null); + Account account = xmppConnectionService.findAccountByJid(jid); + if (account == null) { + account = new Account(jid, CryptoHelper.createPassword(new SecureRandom())); + account.setOption(Account.OPTION_REGISTER, true); + account.setOption(Account.OPTION_DISABLED, true); + account.setOption(Account.OPTION_MAGIC_CREATE, true); + account.setOption(Account.OPTION_FIXED_USERNAME, fixedUsername); + if (this.preAuth != null) { + account.setKey(Account.PRE_AUTH_REGISTRATION_TOKEN, this.preAuth); + } + xmppConnectionService.createAccount(account); + } + Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().asBareJid().toString()); + intent.putExtra("init", true); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show(); + StartConversationActivity.addInviteUri(intent, getIntent()); + startActivity(intent); + } + } catch (IllegalArgumentException e) { + binding.username.setError(getString(R.string.invalid_username)); + binding.username.requestFocus(); + } + }); + binding.username.addTextChangedListener(this); + } - } + @Override + public void onDestroy() { + InstallReferrerUtils.markInstallReferrerExecuted(this); + super.onDestroy(); + } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + } - @Override - public void afterTextChanged(Editable s) { - if (s.toString().trim().length() > 0) { - try { - mFullJidDisplay.setVisibility(View.VISIBLE); - Jid jid = Jid.of(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null); - mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString())); - } catch (IllegalArgumentException e) { - mFullJidDisplay.setVisibility(View.INVISIBLE); - } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { - } else { - mFullJidDisplay.setVisibility(View.INVISIBLE); - } - } + } + + @Override + public void afterTextChanged(final Editable s) { + updateFullJidInformation(s.toString()); + } + + private void updateFullJidInformation(final String username) { + if (username.trim().isEmpty()) { + binding.fullJid.setVisibility(View.INVISIBLE); + } else { + try { + binding.fullJid.setVisibility(View.VISIBLE); + final Jid jid; + if (this.domain == null) { + jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN); + } else { + jid = Jid.ofLocalAndDomain(username, this.domain); + } + binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString())); + } catch (IllegalArgumentException e) { + binding.fullJid.setVisibility(View.INVISIBLE); + } + } + } } diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index 02613f368..df90dfb26 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -1,21 +1,30 @@ package eu.siacs.conversations.ui; +import android.Manifest; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.databinding.DataBindingUtil; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; +import java.util.Arrays; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityWelcomeBinding; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.utils.InstallReferrerUtils; +import eu.siacs.conversations.utils.SignupUtils; +import eu.siacs.conversations.utils.XmppUri; +import rocks.xmpp.addr.Jid; import static eu.siacs.conversations.utils.PermissionUtils.allGranted; import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; @@ -24,6 +33,46 @@ public class WelcomeActivity extends XmppActivity { private static final int REQUEST_IMPORT_BACKUP = 0x63fb; + private XmppUri inviteUri; + + public static void launch(AppCompatActivity activity) { + Intent intent = new Intent(activity, WelcomeActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + activity.startActivity(intent); + activity.overridePendingTransition(0, 0); + } + + public void onInstallReferrerDiscovered(final String referrer) { + Log.d(Config.LOGTAG, "welcome activity: on install referrer discovered " + referrer); + if (referrer != null) { + final XmppUri xmppUri = new XmppUri(referrer); + runOnUiThread(() -> processXmppUri(xmppUri)); + } + } + + private boolean processXmppUri(final XmppUri xmppUri) { + if (xmppUri.isValidJid()) { + final String preauth = xmppUri.getParamater("preauth"); + final Jid jid = xmppUri.getJid(); + final Intent intent; + if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { + intent = SignupUtils.getTokenRegistrationIntent(this, jid, preauth); + } else if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParamater("ibr"))) { + intent = SignupUtils.getTokenRegistrationIntent(this, Jid.ofDomain(jid.getDomain()), preauth); + intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); + } else { + intent = null; + } + if (intent != null) { + startActivity(intent); + finish(); + return true; + } + this.inviteUri = xmppUri; + } + return false; + } + @Override protected void refreshUiReal() { @@ -41,6 +90,12 @@ public class WelcomeActivity extends XmppActivity { if (this.mTheme != theme) { recreate(); } + new InstallReferrerUtils(this); + } + + @Override + public void onStop() { + super.onStop(); } @Override @@ -56,7 +111,7 @@ public class WelcomeActivity extends XmppActivity { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } super.onCreate(savedInstanceState); - ActivityWelcomeBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_welcome); + ActivityWelcomeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_welcome); setSupportActionBar((Toolbar) binding.toolbar); configureActionBar(getSupportActionBar(), false); binding.registerNewAccount.setOnClickListener(v -> { @@ -65,9 +120,9 @@ public class WelcomeActivity extends XmppActivity { startActivity(intent); }); binding.useExisting.setOnClickListener(v -> { - List accounts = xmppConnectionService.getAccounts(); + final List accounts = xmppConnectionService.getAccounts(); Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class); - intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER,false); + intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, false); if (accounts.size() == 1) { intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString()); intent.putExtra("init", true); @@ -83,22 +138,29 @@ public class WelcomeActivity extends XmppActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.welcome_menu, menu); + final MenuItem scan = menu.findItem(R.id.action_scan_qr_code); + scan.setVisible(getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.action_import_backup) { - if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) { - startActivity(new Intent(this, ImportBackupActivity.class)); - } - return true; + switch (item.getItemId()) { + case R.id.action_import_backup: + if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) { + startActivity(new Intent(this, ImportBackupActivity.class)); + } + break; + case R.id.action_scan_qr_code: + UriHandlerActivity.scan(this); + break; } return super.onOptionsItemSelected(item); } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults); if (grantResults.length > 0) { if (allGranted(grantResults)) { switch (requestCode) { @@ -106,7 +168,7 @@ public class WelcomeActivity extends XmppActivity { startActivity(new Intent(this, ImportBackupActivity.class)); break; } - } else { + } else if (Arrays.asList(permissions).contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); } } @@ -117,15 +179,15 @@ public class WelcomeActivity extends XmppActivity { } } - public void addInviteUri(Intent intent) { - StartConversationActivity.addInviteUri(intent, getIntent()); - } - - public static void launch(AppCompatActivity activity) { - Intent intent = new Intent(activity, WelcomeActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - activity.startActivity(intent); - activity.overridePendingTransition(0, 0); + public void addInviteUri(Intent to) { + final Intent from = getIntent(); + if (from != null && from.hasExtra(StartConversationActivity.EXTRA_INVITE_URI)) { + final String invite = from.getStringExtra(StartConversationActivity.EXTRA_INVITE_URI); + to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, invite); + } else if (this.inviteUri != null) { + Log.d(Config.LOGTAG, "injecting referrer uri into on-boarding flow"); + to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, this.inviteUri.toString()); + } } } diff --git a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java index fc5d874d3..a47dfbca8 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java @@ -8,13 +8,31 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.EditAccountActivity; +import eu.siacs.conversations.ui.MagicCreateActivity; import eu.siacs.conversations.ui.ManageAccountActivity; import eu.siacs.conversations.ui.PickServerActivity; import eu.siacs.conversations.ui.StartConversationActivity; import eu.siacs.conversations.ui.WelcomeActivity; +import rocks.xmpp.addr.Jid; public class SignupUtils { + public static boolean isSupportTokenRegistry() { + return true; + } + + public static Intent getTokenRegistrationIntent(final Activity activity, Jid jid, String preAuth) { + final Intent intent = new Intent(activity, MagicCreateActivity.class); + if (jid.isDomainJid()) { + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain()); + } else { + intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain()); + intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getEscapedLocal()); + } + intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preAuth); + return intent; + } + public static Intent getSignUpIntent(final Activity activity) { return getSignUpIntent(activity, false); } diff --git a/src/conversations/res/layout/magic_create.xml b/src/conversations/res/layout/magic_create.xml index 240aa5b3f..cc0337062 100644 --- a/src/conversations/res/layout/magic_create.xml +++ b/src/conversations/res/layout/magic_create.xml @@ -1,86 +1,114 @@ - + - + - + + + + + android:fillViewport="true"> - - - - - - -