This commit is contained in:
Sergei Poljanski 2024-08-06 20:34:01 +03:00
parent 4a819f18eb
commit 9efe732c91
Signed by: Sergei Poljanski
GPG key ID: 4F8851660FA4121B
119 changed files with 0 additions and 3800 deletions

View file

@ -1,70 +0,0 @@
package eu.siacs.conversations.utils;
import android.app.Activity;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import com.android.installreferrer.api.InstallReferrerClient;
import com.android.installreferrer.api.InstallReferrerStateListener;
import com.android.installreferrer.api.ReferrerDetails;
import com.google.common.base.Strings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.ui.WelcomeActivity;
public class InstallReferrerUtils implements InstallReferrerStateListener {
private static final String PROCESSED_INSTALL_REFERRER = "processed_install_referrer";
private final WelcomeActivity welcomeActivity;
private final InstallReferrerClient installReferrerClient;
public InstallReferrerUtils(WelcomeActivity welcomeActivity) {
this.welcomeActivity = welcomeActivity;
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(welcomeActivity);
if (preferences.getBoolean(PROCESSED_INSTALL_REFERRER, false)) {
Log.d(Config.LOGTAG, "install referrer already processed");
this.installReferrerClient = null;
return;
}
this.installReferrerClient = InstallReferrerClient.newBuilder(welcomeActivity).build();
try {
this.installReferrerClient.startConnection(this);
} catch (SecurityException e) {
Log.e(Config.LOGTAG, "unable to start connection to InstallReferrerClient", e);
}
}
public static void markInstallReferrerExecuted(final Activity context) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.edit().putBoolean(PROCESSED_INSTALL_REFERRER, true).apply();
}
@Override
public void onInstallReferrerSetupFinished(int responseCode) {
if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
try {
final ReferrerDetails referrerDetails = installReferrerClient.getInstallReferrer();
final String referrer = referrerDetails.getInstallReferrer();
if (Strings.isNullOrEmpty(referrer)) {
return;
}
welcomeActivity.onInstallReferrerDiscovered(Uri.parse(referrer));
} catch (final RemoteException | IllegalArgumentException e) {
Log.d(Config.LOGTAG, "unable to get install referrer", e);
}
} else {
Log.d(Config.LOGTAG, "unable to setup install referrer client. code=" + responseCode);
}
}
@Override
public void onInstallReferrerServiceDisconnected() {
}
}

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:icon="@mipmap/new_launcher">
<meta-data
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />
<receiver
android:name=".services.MaintenanceReceiver"
android:exported="true"
android:permission="android.permission.CHANGE_CONFIGURATION">
<intent-filter>
<action android:name="eu.siacs.conversations.RENEW_INSTANCE_ID" />
</intent-filter>
</receiver>
<service
android:name=".services.PushMessageReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>

View file

@ -1,10 +0,0 @@
package eu.siacs.conversations.services;
import android.content.Context;
public class EmojiInitializationService {
public static void execute(final Context context) {
}
}

View file

@ -1,30 +0,0 @@
package eu.siacs.conversations.services;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.installations.FirebaseInstallations;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.Compatibility;
public class MaintenanceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(Config.LOGTAG, "received intent in maintenance receiver");
if ("eu.siacs.conversations.RENEW_INSTANCE_ID".equals(intent.getAction())) {
renewInstanceToken(context);
}
}
private void renewInstanceToken(final Context context) {
FirebaseInstallations.getInstance().delete().addOnSuccessListener(unused -> {
final Intent intent = new Intent(context, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_FCM_TOKEN_REFRESH);
Compatibility.startService(context, intent);
});
}
}

View file

@ -1,120 +0,0 @@
package eu.siacs.conversations.services;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailabilityLight;
import com.google.firebase.messaging.FirebaseMessaging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.PhoneHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class PushManagementService {
protected final XmppConnectionService mXmppConnectionService;
PushManagementService(XmppConnectionService service) {
this.mXmppConnectionService = service;
}
private static Data findResponseData(IqPacket response) {
final Element command = response.findChild("command", Namespace.COMMANDS);
final Element x = command == null ? null : command.findChild("x", Namespace.DATA);
return x == null ? null : Data.parse(x);
}
private Jid getAppServer() {
return Jid.of(mXmppConnectionService.getString(R.string.app_server));
}
void registerPushTokenOnServer(final Account account) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support");
retrieveFcmInstanceToken(token -> {
final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService);
final IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(getAppServer(), token, androidId);
mXmppConnectionService.sendIqPacket(account, packet, (a, response) -> {
final Data data = findResponseData(response);
if (response.getType() == IqPacket.TYPE.RESULT && data != null) {
try {
String node = data.getValue("node");
String secret = data.getValue("secret");
Jid jid = Jid.of(data.getValue("jid"));
if (node != null && secret != null) {
enablePushOnServer(a, jid, node, secret);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
} else {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": failed to enable push. invalid response from app server " + response);
}
});
});
}
private void enablePushOnServer(final Account account, final Jid appServer, final String node, final String secret) {
final IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(appServer, node, secret);
mXmppConnectionService.sendIqPacket(account, enable, (a, p) -> {
if (p.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully enabled push on server");
} else if (p.getType() == IqPacket.TYPE.ERROR) {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": enabling push on server failed");
}
});
}
private void retrieveFcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) {
final FirebaseMessaging firebaseMessaging;
try {
firebaseMessaging = FirebaseMessaging.getInstance();
} catch (IllegalStateException e) {
Log.d(Config.LOGTAG, "unable to get firebase instance token ", e);
return;
}
firebaseMessaging.getToken().addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException());
}
final String result;
try {
result = task.getResult();
} catch (Exception e) {
Log.d(Config.LOGTAG, "unable to get Firebase instance token due to bug in library ", e);
return;
}
if (result != null) {
instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result);
}
});
}
public boolean available(Account account) {
final XmppConnection connection = account.getXmppConnection();
return connection != null
&& connection.getFeatures().sm()
&& connection.getFeatures().push()
&& playServicesAvailable();
}
private boolean playServicesAvailable() {
return GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS;
}
public boolean isStub() {
return false;
}
interface OnGcmInstanceTokenRetrieved {
void onGcmInstanceTokenRetrieved(String token);
}
}

View file

@ -1,40 +0,0 @@
package eu.siacs.conversations.services;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import java.util.Map;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.Compatibility;
public class PushMessageReceiver extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage message) {
if (!EventReceiver.hasEnabledAccounts(this)) {
Log.d(Config.LOGTAG,"PushMessageReceiver ignored message because no accounts are enabled");
return;
}
final Map<String, String> data = message.getData();
final Intent intent = new Intent(this, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_FCM_MESSAGE_RECEIVED);
intent.putExtra("account", data.get("account"));
Compatibility.startService(this, intent);
}
@Override
public void onNewToken(String token) {
if (!EventReceiver.hasEnabledAccounts(this)) {
Log.d(Config.LOGTAG,"PushMessageReceiver ignored new token because no accounts are enabled");
return;
}
final Intent intent = new Intent(this, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_FCM_TOKEN_REFRESH);
Compatibility.startService(this, intent);
}
}

View file

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:node="remove" />
<application
android:icon="@mipmap/new_launcher"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:icon">
<activity
android:name=".ui.EnterPhoneNumberActivity"
android:label="@string/verify_your_phone_number"
android:launchMode="singleTop" />
<activity
android:name=".ui.ChooseCountryActivity"
android:label="@string/choose_a_country"
android:launchMode="singleTop" />
<activity
android:name=".ui.VerifyActivity"
android:label="@string/verify_your_phone_number"
android:launchMode="singleTask" />
<activity
android:name=".ui.TosActivity"
android:label="@string/app_name"
android:launchMode="singleTask" />
<activity
android:name=".ui.EnterNameActivity"
android:label="@string/enter_your_name"
android:launchMode="singleTask" />
<receiver
android:name=".services.SMSReceiver"
android:exported="true"
android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
</intent-filter>
</receiver>
</application>
</manifest>

View file

@ -1,14 +0,0 @@
Quicksy ist ein Ableger des beliebten Jabber/XMPP-Clients Conversations mit automatischer Kontaktsuche.
Du meldest dich mit deiner Telefonnummer an und Quicksy schlägt dir automatisch — basierend auf den Telefonnummern in deinem Adressbuch — mögliche Kontakte vor.
Unter der Oberfläche ist Quicksy ein vollwertiger Jabber-Client, mit dem du mit jedem Benutzer auf jedem öffentlich zugänglichen Server kommunizieren kannst. Ebenso können Benutzer auf Quicksy von außen kontaktiert werden, indem du einfach +telefonnummer@quicksy.im zu deiner Kontaktliste hinzufügst.
Abgesehen von der Kontaktsynchronisation ist die Benutzeroberfläche bewusst so nah wie möglich an Conversations gehalten. Dies ermöglicht es den Nutzern, von Quicksy zu Conversations zu wechseln, ohne die Funktionsweise der App neu erlernen zu müssen.
Die vorgeschlagenen Kontakte bestehen aus anderen Quicksy-Benutzern und normalen Jabber/XMPP-Benutzern, die ihre Jabber-ID in das Quicksy-Verzeichnis (https://quicksy.im/#get-listed) eingegeben haben.
HINWEIS: Für den Eintrag (https://quicksy.im/enter/) deiner Jabber-ID in das Quicksy-
Verzeichnis einzutragen, ist eine einmalige Registrierungsgebühr erforderlich.
Lies die Datenschutzrichtlinien (https://quicksy.im/#privacy) für weitere Informationen.

View file

@ -1 +0,0 @@
Jabber/XMPP mit einfacher Anmeldung und leichter Erkennung

View file

@ -1,14 +0,0 @@
Quicksy is a spin off of the popular Jabber/XMPP client Conversations with automatic contact discovery.
You sign up with your phone number and Quicksy will automatically—based on the phone numbers in your address book—suggest possible contacts to you.
Under the hood Quicksy is a full-fledged Jabber client that lets you communicate with any user on any publicly federating server. Likewise users on Quicksy can be contacted from the outside simply by adding +phonenumber@quicksy.im to your contact list.
Aside from the contact sync the user interface is deliberately as close to Conversations as possible. This allows users to eventually migrate from Quicksy to Conversations without having to relearn how the app works.
Suggested contacts consists of other Quicksy users and regular Jabber/XMPP users who have entered their Jabber ID into the Quicksy Directory (https://quicksy.im/#get-listed).
NOTE: To enter (https://quicksy.im/enter/) your Jabber ID in the Quicksy
Directory an one time registration fee is required.
Read the Privacy Policy (https://quicksy.im/#privacy) for more info.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1 +0,0 @@
Jabber/XMPP with Easy Entry and Easy Discovery

View file

@ -1 +0,0 @@
Jabber/XMPP kun Facila Eniro kaj Facila Malkovro

View file

@ -1,14 +0,0 @@
Quicksy é unha aplicación derivada do coñecido cliente Conversations para Jabber/XMPP co engadido do descubremento automático dos contactos.
Accedes co teu número de móbil e Quicksy suxerirá automáticamente posibles contactos en función dos números da libreta de enderezos.
Por debaixo estarás desfrutando dun completo cliente Jabber que che permite comunicarte con calquera usuaria doutros servidores federados. Do mesmo xeito, as persoas que usan Quicksy poden ser contactadas simplemente engadindo +numerodemobil@quicksy.im á túa lista de contactos.
Fóra da sincronización de contactos o resto da interface é o máis semellante posible a Conversations. Deste xeito as usuarias poden migrar de Quicksy a Conversations sen maiores dificultades e sen ter que volver a aprender a usar a aplicación.
Os contactos suxeridos proveñen doutras usuarias de Quicksy e usuarias regulares de Jabber/XMPP que engadiron o seu ID de Jabber ao Directorio Quicksy (https://quicksy.im/#get-listed).
NOTA: Para engadir (https://quicksy.im/enter/) o teu ID de Jabber ao Directorio
Quicksy requírese facer unha pequena aportación só unha vez.
Le a Política de Privacidade (https://quicksy.im/#privacy) para ter máis información.

View file

@ -1 +0,0 @@
Jabber/XMPP Fácil de usar e Atopar os teus contactos

View file

@ -1,14 +0,0 @@
Quicksy è uno spin off del popolare client Jabber/XMPP Conversations con ricerca automatica dei contatti.
Ti registri con il numero di telefono e Quicksy ti proporrà automaticamente, in base ai numeri di telefono nella tua rubrica, i possibili contatti.
Sotto il cofano Quicksy è un vero e proprio client Jabber che ti consente di comunicare con qualsiasi utente su qualsiasi server federato pubblicamente. Allo stesso modo gli utenti su Quicksy possono essere contattati dall'esterno semplicemente aggiungendo +numeroditelefono@quicksy.im al tuo elenco di contatti.
A parte la sincronizzazione dei contatti, l'interfaccia utente è deliberatamente il più possibile simile a quella di Conversations. Ciò permette agli utenti eventualmente di migrare da Quicksy a Conversations senza il bisogno di imparare di nuovo come funziona l'app.
I contatti proposti consistono in altri utenti di Quicksy e utenti regolari di Jabber/XMPP che hanno inserito il loro ID Jabber nella Directory di Quicksy (https://quicksy.im/#get-listed).
NOTA: per inserire (https://quicksy.im/enter/) il tuo ID Jabber nella Directory
di Quicksy è richiesto un pagamento una tantum per la registrazione.
Leggi l'informativa sulla privacy (https://quicksy.im/#privacy) per maggiori informazioni.

View file

@ -1 +0,0 @@
Jabber/XMPP con Easy Entry e Easy Discovery

View file

@ -1,14 +0,0 @@
Quicksy este un derivat al popularului client Jabber/XMPP Conversations cu descoperire automată a contactelor.
Vă înscrieți cu numărul de telefon, iar Quicksy vă va sugera automat, pe baza numerelor de telefon din agenda dvs., posibile contacte.
Sub capota Quicksy este un client Jabber complet care vă permite să comunicați cu orice utilizator de pe orice server public federat. De asemenea, utilizatorii de pe Quicksy pot fi contactați din exterior prin simpla adăugare a +numărdetelefon@quicksy.im la lista dvs. de contacte.
În afară de sincronizarea contactelor, interfața utilizatorului este în mod deliberat cât mai apropiată de Conversations. Acest lucru permite utilizatorilor să migreze în cele din urmă de la Quicksy la Conversations fără a fi nevoiți să învețe din nou cum funcționează aplicația.
Contactele sugerate constau din alți utilizatori Quicksy și utilizatori obișnuiți de Jabber/XMPP care și-au introdus adresa XMPP în Directorul Quicksy (https://quicksy.im/#get-listed).
NOTĂ: Pentru a vă introduce (https://quicksy.im/enter/) adresa XMPP în Directorul
Quicksy este necesară o taxă de înregistrare unică.
Citiți Politica de confidențialitate (https://quicksy.im/#privacy) pentru mai multe informații.

View file

@ -1 +0,0 @@
Jabber/XMPP cu acces și descoperire facilă

View file

@ -1,14 +0,0 @@
Quicksy є відгалуженням Conversations — популярного клієнта Jabber/XMPP, з автоматичним пошуком контактів.
Реєструйтеся за допомогою свого номера телефону, і Quicksy автоматично — за номерами телефонів у Вашій адресній книзі — запропонує Вам можливі контакти.
Під капотом Quicksy — це повноцінний клієнт Jabber, який дозволяє вам спілкуватися з будь-яким користувачем на будь-якому загальнодоступному сервері. Так само з користувачами Quicksy можна зв’язатися ззовні, просто додавши +phonenumber@quicksy.im до свого списку контактів.
Окрім синхронізації контактів, інтерфейс користувача навмисно максимально наближений до Conversations. Це дозволяє користувачам зрештою перейти з Quicksy на Conversations без необхідності перевчатися.
Пропоновані контакти складаються з інших користувачів Quicksy і звичайних користувачів Jabber/XMPP, які ввели свій Jabber ID у каталог Quicksy (https://quicksy.im/#get-listed).
ПРИМІТКА. Щоб ввести (https://quicksy.im/enter/) свій Jabber ID у каталог Quicksy,
потрібно сплатити одноразовий реєстраційний внесок.
Для отримання додаткової інформації прочитайте Політику конфіденційності (https://quicksy.im/#privacy).

View file

@ -1 +0,0 @@
Jabber/XMPP із простим входом і легким пошуком

View file

@ -1,13 +0,0 @@
Quicksy 是流行的 Jabber/XMPP 客户端 Conversations 的衍生品,具有自动发现联系人的功能。
您只需用电话号码注册Quicksy 就会自动—根据您通讯录中的电话号码—向您推荐可能的联系人。
从本质上讲Quicksy 是成熟的 Jabber 客户端,可让您与任何公共联合服务器上的任何用户进行交流。同样,只需将 +phonenumber@quicksy.im 添加到您的联系人列表中,即可从外部联系 Quicksy 上的用户。
除了联系人同步之外,用户界面尽可能地接近 Conversations。让用户最终可以从 Quicksy 迁移到 Conversations而无需重新了解应用程序的工作方式。
建议的联系人包括其他 Quicksy 用户和在 Quicksy 目录https://quicksy.im/#get-listed中输入 Jabber ID 的普通 Jabber/XMPP 用户。
注意:要在 Quicksy 目录中输入https://quicksy.im/enter/)您的 Jabber ID 需要缴纳一次性注册费。
请阅读隐私政策https://quicksy.im/#privacy了解更多信息。

View file

@ -1 +0,0 @@
Jabber/XMPP 轻松进入和易于发现

View file

@ -1,90 +0,0 @@
package eu.siacs.conversations.android;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.ContactsContract;
import android.util.Log;
import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import io.michaelrocks.libphonenumber.android.NumberParseException;
public class PhoneNumberContact extends AbstractPhoneContact {
private final String phoneNumber;
public String getPhoneNumber() {
return phoneNumber;
}
private PhoneNumberContact(Context context, Cursor cursor) throws IllegalArgumentException {
super(cursor);
try {
this.phoneNumber = PhoneNumberUtilWrapper.normalize(context, cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
} catch (NumberParseException | NullPointerException e) {
throw new IllegalArgumentException(e);
}
}
public static ImmutableMap<String, PhoneNumberContact> load(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
return ImmutableMap.of();
}
final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.PHOTO_URI,
ContactsContract.Data.LOOKUP_KEY,
ContactsContract.CommonDataKinds.Phone.NUMBER};
final HashMap<String, PhoneNumberContact> contacts = new HashMap<>();
try (final Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null)){
while (cursor != null && cursor.moveToNext()) {
try {
final PhoneNumberContact contact = new PhoneNumberContact(context, cursor);
final PhoneNumberContact preexisting = contacts.get(contact.getPhoneNumber());
if (preexisting == null || preexisting.rating() < contact.rating()) {
contacts.put(contact.getPhoneNumber(), contact);
}
} catch (final IllegalArgumentException ignored) {
}
}
} catch (final Exception e) {
return ImmutableMap.of();
}
return ImmutableMap.copyOf(contacts);
}
public static PhoneNumberContact findByUriOrNumber(Collection<PhoneNumberContact> haystack, Uri uri, String number) {
final PhoneNumberContact byUri = findByUri(haystack, uri);
return byUri != null || number == null ? byUri : findByNumber(haystack, number);
}
public static PhoneNumberContact findByUri(Collection<PhoneNumberContact> haystack, Uri needle) {
for (PhoneNumberContact contact : haystack) {
if (needle.equals(contact.getLookupUri())) {
return contact;
}
}
return null;
}
private static PhoneNumberContact findByNumber(Collection<PhoneNumberContact> haystack, String needle) {
for (PhoneNumberContact contact : haystack) {
if (needle.equals(contact.getPhoneNumber())) {
return contact;
}
}
return null;
}
}

View file

@ -1,109 +0,0 @@
package eu.siacs.conversations.entities;
import android.util.Base64;
import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import eu.siacs.conversations.android.PhoneNumberContact;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.Jid;
public class Entry implements Comparable<Entry> {
private final List<Jid> jids;
private final String number;
private Entry(String number, List<Jid> jids) {
this.number = number;
this.jids = jids;
}
public static Entry of(Element element) {
final String number = element.getAttribute("number");
final List<Jid> jids = new ArrayList<>();
for (Element jidElement : element.getChildren()) {
String content = jidElement.getContent();
if (content != null) {
jids.add(Jid.of(content));
}
}
return new Entry(number, jids);
}
public static List<Entry> ofPhoneBook(Element phoneBook) {
List<Entry> entries = new ArrayList<>();
for (Element entry : phoneBook.getChildren()) {
if ("entry".equals(entry.getName())) {
entries.add(of(entry));
}
}
return entries;
}
public static String statusQuo(final Collection<PhoneNumberContact> phoneNumberContacts, Collection<Contact> systemContacts) {
return statusQuo(ofPhoneNumberContactsAndContacts(phoneNumberContacts, systemContacts));
}
private static String statusQuo(final List<Entry> entries) {
Collections.sort(entries);
StringBuilder builder = new StringBuilder();
for(Entry entry : entries) {
if (builder.length() != 0) {
builder.append('\u001d');
}
builder.append(entry.getNumber());
List<Jid> jids = entry.getJids();
Collections.sort(jids);
for(Jid jid : jids) {
builder.append('\u001e');
builder.append(jid.asBareJid().toEscapedString());
}
}
@SuppressWarnings("deprecation")
final byte[] sha1 = Hashing.sha1().hashString(builder.toString(), Charsets.UTF_8).asBytes();
return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
}
private static List<Entry> ofPhoneNumberContactsAndContacts(final Collection<PhoneNumberContact> phoneNumberContacts, Collection<Contact> systemContacts) {
final ArrayList<Entry> entries = new ArrayList<>();
for(Contact contact : systemContacts) {
final PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount());
if (phoneNumberContact != null && phoneNumberContact.getPhoneNumber() != null) {
Entry entry = findOrCreateByPhoneNumber(entries, phoneNumberContact.getPhoneNumber());
entry.jids.add(contact.getJid().asBareJid());
}
}
return entries;
}
private static Entry findOrCreateByPhoneNumber(final List<Entry> entries, String number) {
for(Entry entry : entries) {
if (entry.number.equals(number)) {
return entry;
}
}
Entry entry = new Entry(number, new ArrayList<>());
entries.add(entry);
return entry;
}
public List<Jid> getJids() {
return jids;
}
public String getNumber() {
return number;
}
@Override
public int compareTo(Entry o) {
return number.compareTo(o.number);
}
}

View file

@ -1,510 +0,0 @@
package eu.siacs.conversations.services;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.common.collect.ImmutableMap;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.android.PhoneNumberContact;
import eu.siacs.conversations.crypto.sasl.Plain;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Entry;
import eu.siacs.conversations.http.HttpConnectionManager;
import eu.siacs.conversations.utils.AccountUtils;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.utils.SmsRetrieverWrapper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import io.michaelrocks.libphonenumber.android.Phonenumber;
public class QuickConversationsService extends AbstractQuickConversationsService {
public static final int API_ERROR_OTHER = -1;
public static final int API_ERROR_UNKNOWN_HOST = -2;
public static final int API_ERROR_CONNECT = -3;
public static final int API_ERROR_SSL_HANDSHAKE = -4;
public static final int API_ERROR_AIRPLANE_MODE = -5;
public static final int API_ERROR_SSL_CERTIFICATE = -6;
public static final int API_ERROR_SSL_GENERAL = -7;
public static final int API_ERROR_TIMEOUT = -8;
private static final String API_DOMAIN = "api." + Config.QUICKSY_DOMAIN;
private static final String BASE_URL = "https://" + API_DOMAIN;
private static final String INSTALLATION_ID = "eu.siacs.conversations.installation-id";
private final Set<OnVerificationRequested> mOnVerificationRequested = Collections.newSetFromMap(new WeakHashMap<>());
private final Set<OnVerification> mOnVerification = Collections.newSetFromMap(new WeakHashMap<>());
private final AtomicBoolean mVerificationInProgress = new AtomicBoolean(false);
private final AtomicBoolean mVerificationRequestInProgress = new AtomicBoolean(false);
private final AtomicInteger mRunningSyncJobs = new AtomicInteger(0);
private CountDownLatch awaitingAccountStateChange;
private Attempt mLastSyncAttempt = Attempt.NULL;
private final SerialSingleThreadExecutor mSerialSingleThreadExecutor = new SerialSingleThreadExecutor(QuickConversationsService.class.getSimpleName());
QuickConversationsService(XmppConnectionService xmppConnectionService) {
super(xmppConnectionService);
}
private static long retryAfter(HttpURLConnection connection) {
try {
return SystemClock.elapsedRealtime() + (Long.parseLong(connection.getHeaderField("Retry-After")) * 1000L);
} catch (Exception e) {
return 0;
}
}
public void addOnVerificationRequestedListener(OnVerificationRequested onVerificationRequested) {
synchronized (mOnVerificationRequested) {
mOnVerificationRequested.add(onVerificationRequested);
}
}
public void removeOnVerificationRequestedListener(OnVerificationRequested onVerificationRequested) {
synchronized (mOnVerificationRequested) {
mOnVerificationRequested.remove(onVerificationRequested);
}
}
public void addOnVerificationListener(OnVerification onVerification) {
synchronized (mOnVerification) {
mOnVerification.add(onVerification);
}
}
public void removeOnVerificationListener(OnVerification onVerification) {
synchronized (mOnVerification) {
mOnVerification.remove(onVerification);
}
}
public void requestVerification(Phonenumber.PhoneNumber phoneNumber) {
final String e164 = PhoneNumberUtilWrapper.normalize(service, phoneNumber);
if (mVerificationRequestInProgress.compareAndSet(false, true)) {
SmsRetrieverWrapper.start(service);
new Thread(() -> {
try {
final URL url = new URL(BASE_URL + "/authentication/" + e164);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000);
setHeader(connection);
final int code = connection.getResponseCode();
if (code == 200) {
createAccountAndWait(phoneNumber, 0L);
} else if (code == 429) {
createAccountAndWait(phoneNumber, retryAfter(connection));
} else {
synchronized (mOnVerificationRequested) {
for (OnVerificationRequested onVerificationRequested : mOnVerificationRequested) {
onVerificationRequested.onVerificationRequestFailed(code);
}
}
}
} catch (IOException e) {
final int code = getApiErrorCode(e);
synchronized (mOnVerificationRequested) {
for (OnVerificationRequested onVerificationRequested : mOnVerificationRequested) {
onVerificationRequested.onVerificationRequestFailed(code);
}
}
} finally {
mVerificationRequestInProgress.set(false);
}
}).start();
}
}
public void signalAccountStateChange() {
if (awaitingAccountStateChange != null && awaitingAccountStateChange.getCount() > 0) {
Log.d(Config.LOGTAG, "signaled state change");
awaitingAccountStateChange.countDown();
}
}
private void createAccountAndWait(Phonenumber.PhoneNumber phoneNumber, final long timestamp) {
String local = PhoneNumberUtilWrapper.normalize(service, phoneNumber);
Log.d(Config.LOGTAG, "requesting verification for " + PhoneNumberUtilWrapper.normalize(service, phoneNumber));
Jid jid = Jid.of(local, Config.QUICKSY_DOMAIN, null);
Account account = AccountUtils.getFirst(service);
if (account == null || !account.getJid().asBareJid().equals(jid.asBareJid())) {
if (account != null) {
service.deleteAccount(account);
}
account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
account.setOption(Account.OPTION_DISABLED, true);
account.setOption(Account.OPTION_MAGIC_CREATE, true);
account.setOption(Account.OPTION_UNVERIFIED, true);
service.createAccount(account);
}
synchronized (mOnVerificationRequested) {
for (OnVerificationRequested onVerificationRequested : mOnVerificationRequested) {
if (timestamp <= 0) {
onVerificationRequested.onVerificationRequested();
} else {
onVerificationRequested.onVerificationRequestedRetryAt(timestamp);
}
}
}
}
public void verify(final Account account, String pin) {
if (mVerificationInProgress.compareAndSet(false, true)) {
new Thread(() -> {
try {
final URL url = new URL(BASE_URL + "/password");
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000);
connection.setRequestMethod("POST");
connection.setRequestProperty("Authorization", Plain.getMessage(account.getUsername(), pin));
setHeader(connection);
final OutputStream os = connection.getOutputStream();
final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
writer.write(account.getPassword());
writer.flush();
writer.close();
os.close();
connection.connect();
final int code = connection.getResponseCode();
if (code == 200 || code == 201) {
account.setOption(Account.OPTION_UNVERIFIED, false);
account.setOption(Account.OPTION_DISABLED, false);
awaitingAccountStateChange = new CountDownLatch(1);
service.updateAccount(account);
try {
awaitingAccountStateChange.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": timer expired while waiting for account to connect");
}
synchronized (mOnVerification) {
for (OnVerification onVerification : mOnVerification) {
onVerification.onVerificationSucceeded();
}
}
} else if (code == 429) {
final long retryAfter = retryAfter(connection);
synchronized (mOnVerification) {
for (OnVerification onVerification : mOnVerification) {
onVerification.onVerificationRetryAt(retryAfter);
}
}
} else {
synchronized (mOnVerification) {
for (OnVerification onVerification : mOnVerification) {
onVerification.onVerificationFailed(code);
}
}
}
} catch (IOException e) {
final int code = getApiErrorCode(e);
synchronized (mOnVerification) {
for (OnVerification onVerification : mOnVerification) {
onVerification.onVerificationFailed(code);
}
}
} finally {
mVerificationInProgress.set(false);
}
}).start();
}
}
private void setHeader(HttpURLConnection connection) {
connection.setRequestProperty("User-Agent", HttpConnectionManager.getUserAgent());
connection.setRequestProperty("Installation-Id", getInstallationId());
connection.setRequestProperty("Accept-Language", Locale.getDefault().getLanguage());
}
private String getInstallationId() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(service);
String id = preferences.getString(INSTALLATION_ID, null);
if (id != null) {
return id;
} else {
id = UUID.randomUUID().toString();
preferences.edit().putString(INSTALLATION_ID, id).apply();
return id;
}
}
private int getApiErrorCode(final Exception e) {
if (!service.hasInternetConnection()) {
return API_ERROR_AIRPLANE_MODE;
} else if (e instanceof UnknownHostException) {
return API_ERROR_UNKNOWN_HOST;
} else if (e instanceof ConnectException) {
return API_ERROR_CONNECT;
} else if (e instanceof SSLHandshakeException) {
return API_ERROR_SSL_HANDSHAKE;
} else if (e instanceof SSLPeerUnverifiedException || e instanceof CertificateException) {
return API_ERROR_SSL_CERTIFICATE;
} else if (e instanceof SSLException || e instanceof GeneralSecurityException) {
return API_ERROR_SSL_GENERAL;
} else if (e instanceof SocketTimeoutException) {
return API_ERROR_TIMEOUT;
} else {
Log.d(Config.LOGTAG, e.getClass().getName());
return API_ERROR_OTHER;
}
}
public boolean isVerifying() {
return mVerificationInProgress.get();
}
public boolean isRequestingVerification() {
return mVerificationRequestInProgress.get();
}
@Override
public boolean isSynchronizing() {
return mRunningSyncJobs.get() > 0;
}
@Override
public void considerSync() {
considerSync(false);
}
@Override
public void considerSyncBackground(final boolean forced) {
mRunningSyncJobs.incrementAndGet();
mSerialSingleThreadExecutor.execute(() -> {
considerSync(forced);
if (mRunningSyncJobs.decrementAndGet() == 0) {
service.updateRosterUi();
}
});
}
@Override
public void handleSmsReceived(final Intent intent) {
final Bundle extras = intent.getExtras();
final String pin = SmsRetrieverWrapper.extractPin(extras);
if (pin == null) {
Log.d(Config.LOGTAG, "unable to extract Pin from received SMS");
return;
}
final Account account = AccountUtils.getFirst(service);
if (account == null) {
Log.d(Config.LOGTAG, "no account configured to process PIN received by SMS");
return;
}
verify(account, pin);
synchronized (mOnVerification) {
for (OnVerification onVerification : mOnVerification) {
onVerification.startBackgroundVerification(pin);
}
}
}
private void considerSync(boolean forced) {
final ImmutableMap<String, PhoneNumberContact> allContacts = PhoneNumberContact.load(service);
for (final Account account : service.getAccounts()) {
final Map<String, PhoneNumberContact> contacts = filtered(allContacts, account.getJid().getLocal());
if (contacts.size() < allContacts.size()) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": found own phone number in address book. ignoring...");
}
refresh(account, contacts.values());
if (!considerSync(account, contacts, forced)) {
service.syncRoster(account);
}
}
}
@SafeVarargs
private static <A, B> Map<A, B> filtered(final Map<A, B> input, final A... filters) {
final HashMap<A, B> result = new HashMap<>(input);
for (final A filtered : filters) {
result.remove(filtered);
}
return result;
}
private void refresh(Account account, Collection<PhoneNumberContact> contacts) {
for (Contact contact : account.getRoster().getWithSystemAccounts(PhoneNumberContact.class)) {
final Uri uri = contact.getSystemAccount();
if (uri == null) {
continue;
}
final String number = getNumber(contact);
final PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUriOrNumber(contacts, uri, number);
final boolean needsCacheClean;
if (phoneNumberContact != null) {
if (!uri.equals(phoneNumberContact.getLookupUri())) {
Log.d(Config.LOGTAG, "lookupUri has changed from " + uri + " to " + phoneNumberContact.getLookupUri());
}
needsCacheClean = contact.setPhoneContact(phoneNumberContact);
} else {
needsCacheClean = contact.unsetPhoneContact(PhoneNumberContact.class);
Log.d(Config.LOGTAG, uri.toString() + " vanished from address book");
}
if (needsCacheClean) {
service.getAvatarService().clear(contact);
}
}
}
private static String getNumber(final Contact contact) {
final Jid jid = contact.getJid();
if (jid.getLocal() != null && Config.QUICKSY_DOMAIN.equals(jid.getDomain())) {
return jid.getLocal();
}
return null;
}
private boolean considerSync(final Account account, final Map<String, PhoneNumberContact> contacts, final boolean forced) {
final int hash = contacts.keySet().hashCode();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": consider sync of " + hash);
if (!mLastSyncAttempt.retry(hash) && !forced) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not attempt sync");
return false;
}
mRunningSyncJobs.incrementAndGet();
final Jid syncServer = Jid.of(API_DOMAIN);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending phone list to " + syncServer);
final List<Element> entries = new ArrayList<>();
for (final PhoneNumberContact c : contacts.values()) {
entries.add(new Element("entry").setAttribute("number", c.getPhoneNumber()));
}
final IqPacket query = new IqPacket(IqPacket.TYPE.GET);
query.setTo(syncServer);
final Element book = new Element("phone-book", Namespace.SYNCHRONIZATION).setChildren(entries);
final String statusQuo = Entry.statusQuo(contacts.values(), account.getRoster().getWithSystemAccounts(PhoneNumberContact.class));
book.setAttribute("ver", statusQuo);
query.addChild(book);
mLastSyncAttempt = Attempt.create(hash);
service.sendIqPacket(account, query, (a, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
final Element phoneBook = response.findChild("phone-book", Namespace.SYNCHRONIZATION);
if (phoneBook != null) {
final List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class);
for (Entry entry : Entry.ofPhoneBook(phoneBook)) {
final PhoneNumberContact phoneContact = contacts.get(entry.getNumber());
if (phoneContact == null) {
continue;
}
for (final Jid jid : entry.getJids()) {
final Contact contact = account.getRoster().getContact(jid);
final boolean needsCacheClean = contact.setPhoneContact(phoneContact);
if (needsCacheClean) {
service.getAvatarService().clear(contact);
}
withSystemAccounts.remove(contact);
}
}
for (final Contact contact : withSystemAccounts) {
final boolean needsCacheClean = contact.unsetPhoneContact(PhoneNumberContact.class);
if (needsCacheClean) {
service.getAvatarService().clear(contact);
}
}
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": phone number contact list remains unchanged");
}
} else if (response.getType() == IqPacket.TYPE.TIMEOUT) {
mLastSyncAttempt = Attempt.NULL;
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": failed to sync contact list with api server");
}
mRunningSyncJobs.decrementAndGet();
service.syncRoster(account);
service.updateRosterUi();
});
return true;
}
public interface OnVerificationRequested {
void onVerificationRequestFailed(int code);
void onVerificationRequested();
void onVerificationRequestedRetryAt(long timestamp);
}
public interface OnVerification {
void onVerificationFailed(int code);
void onVerificationSucceeded();
void onVerificationRetryAt(long timestamp);
void startBackgroundVerification(String pin);
}
private static class Attempt {
private final long timestamp;
private final int hash;
private static final Attempt NULL = new Attempt(0, 0);
private Attempt(long timestamp, int hash) {
this.timestamp = timestamp;
this.hash = hash;
}
public static Attempt create(int hash) {
return new Attempt(SystemClock.elapsedRealtime(), hash);
}
public boolean retry(int hash) {
return hash != this.hash || SystemClock.elapsedRealtime() - timestamp >= Config.CONTACT_SYNC_RETRY_INTERVAL;
}
}
}

View file

@ -1,22 +0,0 @@
package eu.siacs.conversations.services;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.common.base.Strings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.Compatibility;
public class SMSReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
intent.setClass(context, XmppConnectionService.class);
Compatibility.startService(context, intent);
}
}

View file

@ -1,138 +0,0 @@
package eu.siacs.conversations.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.widget.Toolbar;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityChooseCountryBinding;
import eu.siacs.conversations.ui.adapter.CountryAdapter;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import eu.siacs.conversations.utils.ThemeHelper;
public class ChooseCountryActivity extends ActionBarActivity implements CountryAdapter.OnCountryClicked {
private ActivityChooseCountryBinding binding;
private List<PhoneNumberUtilWrapper.Country> countries = new ArrayList<>();
private CountryAdapter countryAdapter = new CountryAdapter(countries);
private final TextWatcher mSearchTextWatcher = new TextWatcher() {
@Override
public void afterTextChanged(final Editable editable) {
filterCountries(editable.toString());
}
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
}
};
private EditText mSearchEditText;
private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(final MenuItem item) {
mSearchEditText.post(() -> {
mSearchEditText.requestFocus();
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
});
return true;
}
@Override
public boolean onMenuItemActionCollapse(final MenuItem item) {
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
mSearchEditText.setText("");
filterCountries(null);
return true;
}
};
private TextView.OnEditorActionListener mSearchDone = (v, actionId, event) -> {
if (countries.size() == 1) {
onCountryClicked(countries.get(0));
}
return true;
};
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(ThemeHelper.find(this));
Integer override = ThemeHelper.findThemeOverrideStyle(this);
if (override != null) {
getTheme().applyStyle(R.style.OverlayPrimaryColorRed, true);
}
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_choose_country);
setSupportActionBar((Toolbar) this.binding.toolbar);
configureActionBar(getSupportActionBar());
this.countries.addAll(PhoneNumberUtilWrapper.getCountries(this));
Collections.sort(this.countries);
this.binding.countries.setAdapter(countryAdapter);
this.binding.countries.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
countryAdapter.setOnCountryClicked(this);
countryAdapter.notifyDataSetChanged();
}
@Override
public void onCountryClicked(PhoneNumberUtilWrapper.Country country) {
Intent data = new Intent();
data.putExtra("region", country.getRegion());
setResult(RESULT_OK, data);
finish();
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.choose_country, menu);
final MenuItem menuSearchView = menu.findItem(R.id.action_search);
final View mSearchView = menuSearchView.getActionView();
mSearchEditText = mSearchView.findViewById(R.id.search_field);
mSearchEditText.addTextChangedListener(mSearchTextWatcher);
mSearchEditText.setHint(R.string.search_countries);
mSearchEditText.setOnEditorActionListener(mSearchDone);
menuSearchView.setOnActionExpandListener(mOnActionExpandListener);
return true;
}
private void filterCountries(String needle) {
List<PhoneNumberUtilWrapper.Country> countries = PhoneNumberUtilWrapper.getCountries(this);
Iterator<PhoneNumberUtilWrapper.Country> iterator = countries.iterator();
while (iterator.hasNext()) {
final PhoneNumberUtilWrapper.Country country = iterator.next();
if (needle != null && !country.getName().toLowerCase(Locale.getDefault()).contains(needle.toLowerCase(Locale.getDefault()))) {
iterator.remove();
}
}
this.countries.clear();
this.countries.addAll(countries);
this.countryAdapter.notifyDataSetChanged();
}
}

View file

@ -1,9 +0,0 @@
package eu.siacs.conversations.ui;
import eu.siacs.conversations.entities.Account;
public class EasyOnboardingInviteActivity {
public static void launch(Account account, XmppActivity activity) {
throw new IllegalArgumentException("Easy Onboarding is not implemented for Quicksy");
}
}

View file

@ -1,81 +0,0 @@
package eu.siacs.conversations.ui;
import android.content.Intent;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import androidx.appcompat.widget.Toolbar;
import android.view.View;
import java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityEnterNameBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.AccountUtils;
public class EnterNameActivity extends XmppActivity implements XmppConnectionService.OnAccountUpdate {
private ActivityEnterNameBinding binding;
private Account account;
private final AtomicBoolean setNick = new AtomicBoolean(false);
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_enter_name);
setSupportActionBar((Toolbar) this.binding.toolbar);
this.binding.next.setOnClickListener(this::next);
this.setNick.set(savedInstanceState != null && savedInstanceState.getBoolean("set_nick",false));
}
private void next(View view) {
if (account != null) {
String name = this.binding.name.getText().toString().trim();
account.setDisplayName(name);
xmppConnectionService.publishDisplayName(account);
Intent intent = new Intent(this, PublishProfilePictureActivity.class);
intent.putExtra(PublishProfilePictureActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
intent.putExtra("setup", true);
startActivity(intent);
}
finish();
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean("set_nick", this.setNick.get());
super.onSaveInstanceState(savedInstanceState);
}
@Override
protected void refreshUiReal() {
checkSuggestPreviousNick();
}
@Override
void onBackendConnected() {
this.account = AccountUtils.getFirst(xmppConnectionService);
checkSuggestPreviousNick();
}
private void checkSuggestPreviousNick() {
String displayName = this.account == null ? null : this.account.getDisplayName();
if (displayName != null) {
if (setNick.compareAndSet(false, true) && this.binding.name.getText().length() == 0) {
this.binding.name.getText().append(displayName);
}
}
}
@Override
public void onAccountUpdate() {
refreshUi();
}
}

View file

@ -1,241 +0,0 @@
package eu.siacs.conversations.ui;
import android.app.AlertDialog;
import android.content.Intent;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import androidx.appcompat.widget.Toolbar;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityEnterNumberBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.ui.drawable.TextDrawable;
import eu.siacs.conversations.ui.util.ApiDialogHelper;
import eu.siacs.conversations.utils.AccountUtils;
import eu.siacs.conversations.utils.LocationProvider;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import io.michaelrocks.libphonenumber.android.NumberParseException;
import io.michaelrocks.libphonenumber.android.PhoneNumberUtil;
import io.michaelrocks.libphonenumber.android.Phonenumber;
public class EnterPhoneNumberActivity extends XmppActivity implements QuickConversationsService.OnVerificationRequested {
private static final int REQUEST_CHOOSE_COUNTRY = 0x1234;
private ActivityEnterNumberBinding binding;
private final AtomicBoolean redirectInProgress = new AtomicBoolean(false);
private String region = null;
private final TextWatcher countryCodeTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable editable) {
final String text = editable.toString();
try {
final int oldCode = region != null ? PhoneNumberUtilWrapper.getInstance(EnterPhoneNumberActivity.this).getCountryCodeForRegion(region) : 0;
final int code = Integer.parseInt(text);
if (oldCode != code) {
region = PhoneNumberUtilWrapper.getInstance(EnterPhoneNumberActivity.this).getRegionCodeForCountryCode(code);
}
if ("ZZ".equals(region)) {
binding.country.setText(TextUtils.isEmpty(text) ? R.string.choose_a_country : R.string.invalid_country_code);
} else {
binding.number.requestFocus();
binding.country.setText(PhoneNumberUtilWrapper.getCountryForCode(region));
}
} catch (NumberFormatException e) {
binding.country.setText(TextUtils.isEmpty(text) ? R.string.choose_a_country : R.string.invalid_country_code);
}
}
};
private boolean requestingVerification = false;
@Override
protected void refreshUiReal() {
}
@Override
void onBackendConnected() {
xmppConnectionService.getQuickConversationsService().addOnVerificationRequestedListener(this);
final Account account = AccountUtils.getFirst(xmppConnectionService);
if (account != null) {
runOnUiThread(this::performRedirectToVerificationActivity);
}
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String region = savedInstanceState != null ? savedInstanceState.getString("region") : null;
boolean requestingVerification = savedInstanceState != null && savedInstanceState.getBoolean("requesting_verification", false);
if (region != null) {
this.region = region;
} else {
this.region = LocationProvider.getUserCountry(this);
}
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_enter_number);
this.binding.countryCode.setCompoundDrawables(new TextDrawable(this.binding.countryCode, "+"), null, null, null);
this.binding.country.setOnClickListener(this::onSelectCountryClick);
this.binding.next.setOnClickListener(this::onNextClick);
setSupportActionBar((Toolbar) this.binding.toolbar);
this.binding.countryCode.addTextChangedListener(this.countryCodeTextWatcher);
this.binding.countryCode.setText(String.valueOf(PhoneNumberUtilWrapper.getInstance(this).getCountryCodeForRegion(this.region)));
this.binding.number.setOnKeyListener((v, keyCode, event) -> {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
final EditText editText = (EditText) v;
final boolean cursorAtZero = editText.getSelectionEnd() == 0 && editText.getSelectionStart() == 0;
if (keyCode == KeyEvent.KEYCODE_DEL && (cursorAtZero || editText.getText().length() == 0)) {
final Editable countryCode = this.binding.countryCode.getText();
if (countryCode.length() > 0) {
countryCode.delete(countryCode.length() - 1, countryCode.length());
this.binding.countryCode.setSelection(countryCode.length());
}
this.binding.countryCode.requestFocus();
return true;
}
return false;
});
setRequestingVerificationState(requestingVerification);
}
@Override
public void onSaveInstanceState(@NotNull Bundle savedInstanceState) {
if (this.region != null) {
savedInstanceState.putString("region", this.region);
}
savedInstanceState.putBoolean("requesting_verification", this.requestingVerification);
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onStop() {
if (xmppConnectionService != null) {
xmppConnectionService.getQuickConversationsService().removeOnVerificationRequestedListener(this);
}
super.onStop();
}
private void onNextClick(View v) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
try {
final Editable number = this.binding.number.getText();
final String input = number.toString();
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtilWrapper.getInstance(this).parse(input, region);
this.binding.countryCode.setText(String.valueOf(phoneNumber.getCountryCode()));
number.clear();
number.append(String.valueOf(phoneNumber.getNationalNumber()));
final String formattedPhoneNumber = PhoneNumberUtilWrapper.getInstance(this).format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL).replace(' ','\u202F');
if (PhoneNumberUtilWrapper.getInstance(this).isValidNumber(phoneNumber)) {
builder.setMessage(Html.fromHtml(getString(R.string.we_will_be_verifying, formattedPhoneNumber)));
builder.setNegativeButton(R.string.edit, null);
builder.setPositiveButton(R.string.ok, (dialog, which) -> onPhoneNumberEntered(phoneNumber));
} else {
builder.setMessage(getString(R.string.not_a_valid_phone_number, formattedPhoneNumber));
builder.setPositiveButton(R.string.ok, null);
}
Log.d(Config.LOGTAG, phoneNumber.toString());
} catch (NumberParseException e) {
builder.setMessage(R.string.please_enter_your_phone_number);
builder.setPositiveButton(R.string.ok, null);
}
builder.create().show();
}
private void onSelectCountryClick(View view) {
final Intent intent = new Intent(this, ChooseCountryActivity.class);
startActivityForResult(intent, REQUEST_CHOOSE_COUNTRY);
}
private void onPhoneNumberEntered(Phonenumber.PhoneNumber phoneNumber) {
setRequestingVerificationState(true);
xmppConnectionService.getQuickConversationsService().requestVerification(phoneNumber);
}
private void setRequestingVerificationState(boolean requesting) {
this.requestingVerification = requesting;
this.binding.countryCode.setEnabled(!requesting);
this.binding.country.setEnabled(!requesting);
this.binding.number.setEnabled(!requesting);
this.binding.next.setEnabled(!requesting);
this.binding.next.setText(requesting ? R.string.requesting_sms : R.string.next);
this.binding.progressBar.setVisibility(requesting ? View.VISIBLE : View.GONE);
this.binding.progressBar.setIndeterminate(requesting);
}
@Override
public void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == REQUEST_CHOOSE_COUNTRY) {
final String region = data.getStringExtra("region");
if (region != null) {
this.region = region;
final int countryCode = PhoneNumberUtilWrapper.getInstance(this).getCountryCodeForRegion(region);
this.binding.countryCode.setText(String.valueOf(countryCode));
}
}
}
private void performRedirectToVerificationActivity(long timestamp) {
if (redirectInProgress.compareAndSet(false, true)) {
Intent intent = new Intent(this, VerifyActivity.class);
intent.putExtra(VerifyActivity.EXTRA_RETRY_SMS_AFTER, timestamp);
startActivity(intent);
finish();
}
}
private void performRedirectToVerificationActivity() {
if (redirectInProgress.compareAndSet(false, true)) {
startActivity(new Intent(this, VerifyActivity.class));
finish();
}
}
@Override
public void onVerificationRequestFailed(int code) {
runOnUiThread(() -> {
setRequestingVerificationState(false);
ApiDialogHelper.createError(this, code).show();
});
}
@Override
public void onVerificationRequested() {
runOnUiThread(this::performRedirectToVerificationActivity);
}
@Override
public void onVerificationRequestedRetryAt(long timestamp) {
runOnUiThread(() -> performRedirectToVerificationActivity(timestamp));
}
}

View file

@ -1,77 +0,0 @@
package eu.siacs.conversations.ui;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.ActionBar;
import eu.siacs.conversations.R;
public class TosActivity 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 void onNewIntent(Intent intent) {
super.onNewIntent(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);
setContentView(R.layout.activity_tos);
setSupportActionBar(findViewById(R.id.toolbar));
final ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayShowHomeEnabled(false);
ab.setDisplayHomeAsUpEnabled(false);
}
final Button agreeButton = findViewById(R.id.agree);
final TextView welcomeText = findViewById(R.id.welcome_text);
agreeButton.setOnClickListener(v -> {
final Intent intent = new Intent(this, EnterPhoneNumberActivity.class);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
preferences.edit().putBoolean("tos", true).apply();
addInviteUri(intent);
startActivity(intent);
finish();
});
welcomeText.setText(Html.fromHtml(getString(R.string.welcome_text_quicksy_static)));
welcomeText.setMovementMethod(LinkMovementMethod.getInstance());
}
public void addInviteUri(Intent intent) {
StartConversationActivity.addInviteUri(intent, getIntent());
}
}

View file

@ -1,357 +0,0 @@
package eu.siacs.conversations.ui;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.widget.Toolbar;
import android.text.Html;
import android.view.View;
import java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityVerifyBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.ui.util.ApiDialogHelper;
import eu.siacs.conversations.ui.util.PinEntryWrapper;
import eu.siacs.conversations.utils.AccountUtils;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import eu.siacs.conversations.utils.TimeFrameUtils;
import io.michaelrocks.libphonenumber.android.NumberParseException;
import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
public class VerifyActivity extends XmppActivity implements ClipboardManager.OnPrimaryClipChangedListener, QuickConversationsService.OnVerification, QuickConversationsService.OnVerificationRequested {
public static final String EXTRA_RETRY_SMS_AFTER = "retry_sms_after";
private static final String EXTRA_RETRY_VERIFICATION_AFTER = "retry_verification_after";
private final Handler mHandler = new Handler();
private ActivityVerifyBinding binding;
private Account account;
private PinEntryWrapper pinEntryWrapper;
private ClipboardManager clipboardManager;
private String pasted = null;
private boolean verifying = false;
private boolean requestingVerification = false;
private long retrySmsAfter = 0;
private final Runnable SMS_TIMEOUT_UPDATER = new Runnable() {
@Override
public void run() {
if (setTimeoutLabelInResendButton()) {
mHandler.postDelayed(this, 300);
}
}
};
private long retryVerificationAfter = 0;
private final Runnable VERIFICATION_TIMEOUT_UPDATER = new Runnable() {
@Override
public void run() {
if (setTimeoutLabelInNextButton()) {
mHandler.postDelayed(this, 300);
}
}
};
private final AtomicBoolean redirectInProgress = new AtomicBoolean(false);
private boolean setTimeoutLabelInResendButton() {
if (retrySmsAfter != 0) {
long remaining = retrySmsAfter - SystemClock.elapsedRealtime();
if (remaining >= 0) {
binding.resendSms.setEnabled(false);
binding.resendSms.setText(getString(R.string.resend_sms_in, TimeFrameUtils.resolve(VerifyActivity.this, remaining)));
return true;
}
}
binding.resendSms.setEnabled(true);
binding.resendSms.setText(R.string.resend_sms);
return false;
}
private boolean setTimeoutLabelInNextButton() {
if (retryVerificationAfter != 0) {
long remaining = retryVerificationAfter - SystemClock.elapsedRealtime();
if (remaining >= 0) {
binding.next.setEnabled(false);
binding.next.setText(getString(R.string.wait_x, TimeFrameUtils.resolve(VerifyActivity.this, remaining)));
return true;
}
}
this.binding.next.setEnabled(!verifying);
this.binding.next.setText(verifying ? R.string.verifying : R.string.next);
return false;
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String pin = savedInstanceState != null ? savedInstanceState.getString("pin") : null;
boolean verifying = savedInstanceState != null && savedInstanceState.getBoolean("verifying");
boolean requestingVerification = savedInstanceState != null && savedInstanceState.getBoolean("requesting_verification", false);
this.pasted = savedInstanceState != null ? savedInstanceState.getString("pasted") : null;
this.retrySmsAfter = savedInstanceState != null ? savedInstanceState.getLong(EXTRA_RETRY_SMS_AFTER, 0L) : 0L;
this.retryVerificationAfter = savedInstanceState != null ? savedInstanceState.getLong(EXTRA_RETRY_VERIFICATION_AFTER, 0L) : 0L;
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_verify);
setSupportActionBar((Toolbar) this.binding.toolbar);
this.pinEntryWrapper = new PinEntryWrapper(binding.pinBox);
if (pin != null) {
this.pinEntryWrapper.setPin(pin);
}
binding.back.setOnClickListener(this::onBackButton);
binding.next.setOnClickListener(this::onNextButton);
binding.resendSms.setOnClickListener(this::onResendSmsButton);
clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
setVerifyingState(verifying);
setRequestingVerificationState(requestingVerification);
}
private void onBackButton(View view) {
if (this.verifying) {
setVerifyingState(false);
return;
}
final Intent intent = new Intent(this, EnterPhoneNumberActivity.class);
if (this.account != null) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.abort_registration_procedure);
builder.setPositiveButton(R.string.yes, (dialog, which) -> {
xmppConnectionService.deleteAccount(account);
startActivity(intent);
finish();
});
builder.setNegativeButton(R.string.no, null);
builder.create().show();
} else {
startActivity(intent);
finish();
}
}
private void onNextButton(View view) {
final String pin = pinEntryWrapper.getPin();
if (PinEntryWrapper.isValidPin(pin)) {
if (account != null && xmppConnectionService != null) {
setVerifyingState(true);
xmppConnectionService.getQuickConversationsService().verify(account, pin);
}
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.please_enter_pin);
builder.setPositiveButton(R.string.ok, null);
builder.create().show();
}
}
private void onResendSmsButton(View view) {
try {
xmppConnectionService.getQuickConversationsService().requestVerification(PhoneNumberUtilWrapper.toPhoneNumber(this, account.getJid()));
setRequestingVerificationState(true);
} catch (NumberParseException e) {
}
}
private void setVerifyingState(boolean verifying) {
this.verifying = verifying;
this.binding.back.setText(verifying ? R.string.cancel : R.string.back);
this.binding.next.setEnabled(!verifying);
this.binding.next.setText(verifying ? R.string.verifying : R.string.next);
this.binding.resendSms.setVisibility(verifying ? View.GONE : View.VISIBLE);
pinEntryWrapper.setEnabled(!verifying);
this.binding.progressBar.setVisibility(verifying ? View.VISIBLE : View.GONE);
this.binding.progressBar.setIndeterminate(verifying);
}
private void setRequestingVerificationState(boolean requesting) {
this.requestingVerification = requesting;
if (requesting) {
this.binding.resendSms.setEnabled(false);
this.binding.resendSms.setText(R.string.requesting_sms);
} else {
setTimeoutLabelInResendButton();
}
}
@Override
protected void refreshUiReal() {
}
@Override
void onBackendConnected() {
xmppConnectionService.getQuickConversationsService().addOnVerificationListener(this);
xmppConnectionService.getQuickConversationsService().addOnVerificationRequestedListener(this);
this.account = AccountUtils.getFirst(xmppConnectionService);
if (this.account == null) {
return;
}
if (!account.isOptionSet(Account.OPTION_UNVERIFIED) && !account.isOptionSet(Account.OPTION_DISABLED)) {
runOnUiThread(this::performPostVerificationRedirect);
return;
}
this.binding.weHaveSent.setText(Html.fromHtml(getString(R.string.we_have_sent_you_an_sms_to_x, PhoneNumberUtilWrapper.toFormattedPhoneNumber(this, this.account.getJid()))));
setVerifyingState(xmppConnectionService.getQuickConversationsService().isVerifying());
setRequestingVerificationState(xmppConnectionService.getQuickConversationsService().isRequestingVerification());
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("pin", this.pinEntryWrapper.getPin());
savedInstanceState.putBoolean("verifying", this.verifying);
savedInstanceState.putBoolean("requesting_verification", this.requestingVerification);
savedInstanceState.putLong(EXTRA_RETRY_SMS_AFTER, this.retrySmsAfter);
savedInstanceState.putLong(EXTRA_RETRY_VERIFICATION_AFTER, this.retryVerificationAfter);
if (this.pasted != null) {
savedInstanceState.putString("pasted", this.pasted);
}
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
clipboardManager.addPrimaryClipChangedListener(this);
final Intent intent = getIntent();
this.retrySmsAfter = intent != null ? intent.getLongExtra(EXTRA_RETRY_SMS_AFTER, this.retrySmsAfter) : this.retrySmsAfter;
if (this.retrySmsAfter > 0) {
mHandler.post(SMS_TIMEOUT_UPDATER);
}
if (this.retryVerificationAfter > 0) {
mHandler.post(VERIFICATION_TIMEOUT_UPDATER);
}
}
@Override
public void onStop() {
super.onStop();
mHandler.removeCallbacks(SMS_TIMEOUT_UPDATER);
mHandler.removeCallbacks(VERIFICATION_TIMEOUT_UPDATER);
clipboardManager.removePrimaryClipChangedListener(this);
if (xmppConnectionService != null) {
xmppConnectionService.getQuickConversationsService().removeOnVerificationListener(this);
xmppConnectionService.getQuickConversationsService().removeOnVerificationRequestedListener(this);
}
}
@Override
public void onResume() {
super.onResume();
if (pinEntryWrapper.isEmpty()) {
//starting with Android P we need input focus
pinEntryWrapper.requestFocus();
pastePinFromClipboard();
}
}
private void pastePinFromClipboard() {
final ClipDescription description = clipboardManager != null ? clipboardManager.getPrimaryClipDescription() : null;
if (description != null && description.hasMimeType(MIMETYPE_TEXT_PLAIN)) {
final ClipData primaryClip = clipboardManager.getPrimaryClip();
if (primaryClip != null && primaryClip.getItemCount() > 0) {
final CharSequence clip = primaryClip.getItemAt(0).getText();
if (PinEntryWrapper.isValidPin(clip) && !clip.toString().equals(this.pasted)) {
this.pasted = clip.toString();
pinEntryWrapper.setPin(clip.toString());
final Snackbar snackbar = Snackbar.make(binding.coordinator, R.string.possible_pin, Snackbar.LENGTH_LONG);
snackbar.setAction(R.string.undo, v -> pinEntryWrapper.clear());
snackbar.show();
}
}
}
}
private void performPostVerificationRedirect() {
if (redirectInProgress.compareAndSet(false, true)) {
Intent intent = new Intent(this, EnterNameActivity.class);
startActivity(intent);
finish();
}
}
@Override
public void onPrimaryClipChanged() {
this.pasted = null;
if (pinEntryWrapper.isEmpty()) {
pastePinFromClipboard();
}
}
@Override
public void onVerificationFailed(final int code) {
runOnUiThread(() -> {
setVerifyingState(false);
if (code == 401 || code == 404) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(code == 404 ? R.string.pin_expired : R.string.incorrect_pin);
builder.setPositiveButton(R.string.ok, null);
builder.create().show();
} else {
ApiDialogHelper.createError(this, code).show();
}
});
}
@Override
public void onVerificationSucceeded() {
runOnUiThread(this::performPostVerificationRedirect);
}
@Override
public void onVerificationRetryAt(long timestamp) {
this.retryVerificationAfter = timestamp;
runOnUiThread(() -> {
ApiDialogHelper.createTooManyAttempts(this).show();
setVerifyingState(false);
});
mHandler.removeCallbacks(VERIFICATION_TIMEOUT_UPDATER);
runOnUiThread(VERIFICATION_TIMEOUT_UPDATER);
}
@Override
public void startBackgroundVerification(String pin) {
pinEntryWrapper.setPin(pin);
setVerifyingState(true);
}
//send sms again button callback
@Override
public void onVerificationRequestFailed(int code) {
runOnUiThread(() -> {
setRequestingVerificationState(false);
ApiDialogHelper.createError(this, code).show();
});
}
//send sms again button callback
@Override
public void onVerificationRequested() {
runOnUiThread(() -> {
pinEntryWrapper.clear();
setRequestingVerificationState(false);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.we_have_sent_you_another_sms);
builder.setPositiveButton(R.string.ok, null);
builder.create().show();
});
}
@Override
public void onVerificationRequestedRetryAt(long timestamp) {
this.retrySmsAfter = timestamp;
runOnUiThread(() -> {
ApiDialogHelper.createRateLimited(this, timestamp).show();
setRequestingVerificationState(false);
});
mHandler.removeCallbacks(SMS_TIMEOUT_UPDATER);
runOnUiThread(SMS_TIMEOUT_UPDATER);
}
}

View file

@ -1,70 +0,0 @@
package eu.siacs.conversations.ui.adapter;
import androidx.databinding.DataBindingUtil;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import java.util.List;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.CountryItemBinding;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
public class CountryAdapter extends RecyclerView.Adapter<CountryAdapter.CountryViewHolder> {
private final List<PhoneNumberUtilWrapper.Country> countries;
private OnCountryClicked onCountryClicked;
public CountryAdapter(List<PhoneNumberUtilWrapper.Country> countries) {
this.countries = countries;
}
@NonNull
@Override
public CountryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
CountryItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.country_item, parent, false);
return new CountryViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull CountryViewHolder holder, int position) {
final PhoneNumberUtilWrapper.Country county = countries.get(position);
holder.binding.country.setText(county.getName());
holder.binding.countryCode.setText(county.getCode());
holder.itemView.setOnClickListener(v -> {
if (onCountryClicked != null) {
onCountryClicked.onCountryClicked(county);
}
});
}
public void setOnCountryClicked(OnCountryClicked listener) {
this.onCountryClicked = listener;
}
@Override
public int getItemCount() {
return countries.size();
}
class CountryViewHolder extends RecyclerView.ViewHolder {
private final CountryItemBinding binding;
CountryViewHolder(CountryItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
public interface OnCountryClicked {
void onCountryClicked(PhoneNumberUtilWrapper.Country country);
}
}

View file

@ -1,243 +0,0 @@
package eu.siacs.conversations.ui.drawable; /**
* Copyright 2016 Ali Muzaffar
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import eu.siacs.conversations.ui.util.StyledAttributes;
public class TextDrawable extends Drawable implements TextWatcher {
private WeakReference<TextView> ref;
private String mText;
private Paint mPaint;
private Rect mHeightBounds;
private boolean mBindToViewPaint = false;
private float mPrevTextSize = 0;
private boolean mInitFitText = false;
private boolean mFitTextEnabled = false;
/**
* Create a TextDrawable using the given paint object and string
*
* @param paint
* @param s
*/
public TextDrawable(Paint paint, String s) {
mText = s;
mPaint = new Paint(paint);
mHeightBounds = new Rect();
init();
}
/**
* Create a TextDrawable. This uses the given TextView to initialize paint and has initial text
* that will be drawn. Initial text can also be useful for reserving space that may otherwise
* not be available when setting compound drawables.
*
* @param tv The TextView / EditText using to initialize this drawable
* @param initialText Optional initial text to display
* @param bindToViewsText Should this drawable mirror the text in the TextView
* @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc.
* Note, this will override any changes made using setColorFilter or setAlpha.
*/
public TextDrawable(TextView tv, String initialText, boolean bindToViewsText, boolean bindToViewsPaint) {
this(tv.getPaint(), initialText);
mPaint.setColor(StyledAttributes.getColor(tv.getContext(), android.R.attr.textColorPrimary));
ref = new WeakReference<>(tv);
if (bindToViewsText || bindToViewsPaint) {
if (bindToViewsText) {
tv.addTextChangedListener(this);
}
mBindToViewPaint = bindToViewsPaint;
}
}
/**
* Create a TextDrawable. This uses the given TextView to initialize paint and the text that
* will be drawn.
*
* @param tv The TextView / EditText using to initialize this drawable
* @param bindToViewsText Should this drawable mirror the text in the TextView
* @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc.
* Note, this will override any changes made using setColorFilter or setAlpha.
*/
public TextDrawable(TextView tv, boolean bindToViewsText, boolean bindToViewsPaint) {
this(tv, tv.getText().toString(), false, false);
}
/**
* Use the provided TextView/EditText to initialize the drawable.
* The Drawable will copy the Text and the Paint properties, however it will from that
* point on be independant of the TextView.
*
* @param tv a TextView or EditText or any of their children.
*/
public TextDrawable(TextView tv) {
this(tv, false, false);
}
/**
* Use the provided TextView/EditText to initialize the drawable.
* The Drawable will copy the Paint properties, and use the provided text to initialise itself.
*
* @param tv a TextView or EditText or any of their children.
* @param s The String to draw
*/
public TextDrawable(TextView tv, String s) {
this(tv, s, false, false);
}
@Override
public void draw(Canvas canvas) {
if (mBindToViewPaint && ref.get() != null) {
Paint p = ref.get().getPaint();
canvas.drawText(mText, 0, getBounds().height(), p);
} else {
if (mInitFitText) {
fitTextAndInit();
}
canvas.drawText(mText, 0, getBounds().height(), mPaint);
}
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
int alpha = mPaint.getAlpha();
if (alpha == 0) {
return PixelFormat.TRANSPARENT;
} else if (alpha == 255) {
return PixelFormat.OPAQUE;
} else {
return PixelFormat.TRANSLUCENT;
}
}
private void init() {
Rect bounds = getBounds();
//We want to use some character to determine the max height of the text.
//Otherwise if we draw something like "..." they will appear centered
//Here I'm just going to use the entire alphabet to determine max height.
mPaint.getTextBounds("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+", 0, 1, mHeightBounds);
//This doesn't account for leading or training white spaces.
//mPaint.getTextBounds(mText, 0, mText.length(), bounds);
float width = mPaint.measureText(mText);
bounds.top = mHeightBounds.top;
bounds.bottom = mHeightBounds.bottom;
bounds.right = (int) width;
bounds.left = 0;
setBounds(bounds);
}
public Paint getPaint() {
return mPaint;
}
public void setPaint(Paint paint) {
mPaint = new Paint(paint);
//Since this can change the font used, we need to recalculate bounds.
if (mFitTextEnabled && !mInitFitText) {
fitTextAndInit();
} else {
init();
}
invalidateSelf();
}
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
//Since this can change the bounds of the text, we need to recalculate.
if (mFitTextEnabled && !mInitFitText) {
fitTextAndInit();
} else {
init();
}
invalidateSelf();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
setText(s.toString());
}
/**
* Make the TextDrawable match the width of the View it's associated with.
* <p/>
* Note: While this option will not work if bindToViewPaint is true.
*
* @param fitText
*/
public void setFillText(boolean fitText) {
mFitTextEnabled = fitText;
if (fitText) {
mPrevTextSize = mPaint.getTextSize();
if (ref.get() != null) {
if (ref.get().getWidth() > 0) {
fitTextAndInit();
} else {
mInitFitText = true;
}
}
} else {
if (mPrevTextSize > 0) {
mPaint.setTextSize(mPrevTextSize);
}
init();
}
}
private void fitTextAndInit() {
float fitWidth = ref.get().getWidth();
float textWidth = mPaint.measureText(mText);
float multi = fitWidth / textWidth;
mPaint.setTextSize(mPaint.getTextSize() * multi);
mInitFitText = false;
init();
}
}

View file

@ -1,101 +0,0 @@
package eu.siacs.conversations.ui.util;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.SystemClock;
import androidx.annotation.StringRes;
import eu.siacs.conversations.R;
import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.utils.TimeFrameUtils;
public class ApiDialogHelper {
public static Dialog createError(final Context context, final int code) {
@StringRes final int res;
switch (code) {
case QuickConversationsService.API_ERROR_AIRPLANE_MODE:
res = R.string.no_network_connection;
break;
case QuickConversationsService.API_ERROR_OTHER:
res = R.string.unknown_api_error_network;
break;
case QuickConversationsService.API_ERROR_CONNECT:
res = R.string.unable_to_connect_to_server;
break;
case QuickConversationsService.API_ERROR_SSL_HANDSHAKE:
res = R.string.unable_to_establish_secure_connection;
break;
case QuickConversationsService.API_ERROR_UNKNOWN_HOST:
res = R.string.unable_to_find_server;
break;
case QuickConversationsService.API_ERROR_SSL_CERTIFICATE:
res = R.string.unable_to_verify_server_identity;
break;
case QuickConversationsService.API_ERROR_SSL_GENERAL:
res = R.string.unknown_security_error;
break;
case QuickConversationsService.API_ERROR_TIMEOUT:
res = R.string.timeout_while_connecting_to_server;
break;
case 400:
res = R.string.invalid_user_input;
break;
case 403:
res = R.string.the_app_is_out_of_date;
break;
case 409:
res = R.string.logged_in_with_another_device;
break;
case 451:
res = R.string.not_available_in_your_country;
break;
case 500:
res = R.string.something_went_wrong_processing_your_request;
break;
case 502:
case 503:
case 504:
res = R.string.temporarily_unavailable;
break;
default:
res = R.string.unknown_api_error_response;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(res);
if (code == 403 && resolvable(context, getMarketViewIntent(context))) {
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.update, (dialog, which) -> context.startActivity(getMarketViewIntent(context)));
} else {
builder.setPositiveButton(R.string.ok, null);
}
return builder.create();
}
public static Dialog createRateLimited(final Context context, final long timestamp) {
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.rate_limited);
builder.setMessage(context.getString(R.string.try_again_in_x, TimeFrameUtils.resolve(context, timestamp - SystemClock.elapsedRealtime())));
builder.setPositiveButton(R.string.ok, null);
return builder.create();
}
public static Dialog createTooManyAttempts(final Context context) {
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(R.string.too_many_attempts);
builder.setPositiveButton(R.string.ok, null);
return builder.create();
}
private static Intent getMarketViewIntent(Context context) {
return new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + context.getPackageName()));
}
private static boolean resolvable(Context context, Intent intent) {
return context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
}
}

View file

@ -1,159 +0,0 @@
package eu.siacs.conversations.ui.util;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class PinEntryWrapper {
private static final Pattern PIN_STRING_PATTERN = Pattern.compile("^[0-9]{6}$");
private final List<EditText> digits = new ArrayList<>();
private final TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
int current = -1;
for (int i = 0; i < digits.size(); ++i) {
if (s.hashCode() == digits.get(i).getText().hashCode()) {
current = i;
}
}
if (current == -1) {
return;
}
if (s.length() > 0) {
if (current < digits.size() - 1) {
digits.get(current + 1).requestFocus();
}
} else {
int focusOn = current;
for (int i = current - 1; i >= 0; --i) {
if (digits.get(i).getText().length() == 0) {
focusOn = i;
} else {
break;
}
}
digits.get(focusOn).requestFocus();
}
}
};
private final View.OnKeyListener keyListener = (v, keyCode, event) -> {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
if (v instanceof EditText) {
final EditText editText = (EditText) v;
final boolean cursorAtZero = editText.getSelectionEnd() == 0 && editText.getSelectionStart() == 0;
if (keyCode == KeyEvent.KEYCODE_DEL && (cursorAtZero || editText.getText().length() == 0)) {
final int current = digits.indexOf(editText);
for (int i = current - 1; i >= 0; --i) {
if (digits.get(i).getText().length() > 0) {
digits.get(i).getText().clear();
return true;
}
}
if (current != 0) {
digits.get(0).requestFocus();
return true;
}
}
}
return false;
};
public PinEntryWrapper(LinearLayout linearLayout) {
for (int i = 0; i < linearLayout.getChildCount(); ++i) {
View view = linearLayout.getChildAt(i);
if (view instanceof EditText) {
this.digits.add((EditText) view);
}
}
registerListeners();
}
private void registerListeners() {
for (EditText editText : digits) {
editText.addTextChangedListener(textWatcher);
editText.setOnKeyListener(keyListener);
}
}
public String getPin() {
char[] chars = new char[digits.size()];
for (int i = 0; i < chars.length; ++i) {
final String input = digits.get(i).getText().toString();
chars[i] = input.length() != 1 ? ' ' : input.charAt(0);
}
return String.valueOf(chars);
}
public void setPin(String pin) {
char[] chars = pin.toCharArray();
for (int i = 0; i < digits.size(); ++i) {
if (i < chars.length) {
final Editable editable = digits.get(i).getText();
editable.clear();
editable.append(Character.isDigit(chars[i]) ? String.valueOf(chars[i]) : "");
}
}
}
public void setEnabled(final boolean enabled) {
for (EditText digit : digits) {
digit.setEnabled(enabled);
digit.setCursorVisible(enabled);
digit.setFocusable(enabled);
digit.setFocusableInTouchMode(enabled);
}
if (enabled) {
final EditText last = digits.get(digits.size() - 1);
if (last.getEditableText().length() > 0) {
last.requestFocus();
}
}
}
public boolean isEmpty() {
for (EditText digit : digits) {
if (digit.getText().length() > 0) {
return false;
}
}
return true;
}
public static boolean isValidPin(CharSequence pin) {
return pin != null && PIN_STRING_PATTERN.matcher(pin).matches();
}
public void clear() {
for (int i = digits.size() - 1; i >= 0; --i) {
digits.get(i).getText().clear();
}
}
public void requestFocus() {
digits.get(0).requestFocus();
}
}

View file

@ -1,101 +0,0 @@
package eu.siacs.conversations.utils;
import android.content.Context;
import android.telephony.TelephonyManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import eu.siacs.conversations.xmpp.Jid;
import io.michaelrocks.libphonenumber.android.NumberParseException;
import io.michaelrocks.libphonenumber.android.PhoneNumberUtil;
import io.michaelrocks.libphonenumber.android.Phonenumber;
public class PhoneNumberUtilWrapper {
private static volatile PhoneNumberUtil instance;
public static String getCountryForCode(String code) {
Locale locale = new Locale("", code);
return locale.getDisplayCountry();
}
public static String toFormattedPhoneNumber(Context context, Jid jid) {
try {
return getInstance(context).format(toPhoneNumber(context, jid), PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL).replace(' ','\u202F');
} catch (Exception e) {
return jid.getEscapedLocal();
}
}
public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid) throws NumberParseException {
return getInstance(context).parse(jid.getEscapedLocal(), "de");
}
public static String normalize(Context context, String input) throws IllegalArgumentException, NumberParseException {
final Phonenumber.PhoneNumber number = getInstance(context).parse(input, LocationProvider.getUserCountry(context));
if (!getInstance(context).isValidNumber(number)) {
throw new IllegalArgumentException(String.format("%s is not a valid phone number", input));
}
return normalize(context, number);
}
public static String normalize(Context context, Phonenumber.PhoneNumber phoneNumber) {
return getInstance(context).format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
}
public static PhoneNumberUtil getInstance(final Context context) {
PhoneNumberUtil localInstance = instance;
if (localInstance == null) {
synchronized (PhoneNumberUtilWrapper.class) {
localInstance = instance;
if (localInstance == null) {
instance = localInstance = PhoneNumberUtil.createInstance(context);
}
}
}
return localInstance;
}
public static List<Country> getCountries(final Context context) {
List<Country> countries = new ArrayList<>();
for (String region : getInstance(context).getSupportedRegions()) {
countries.add(new Country(region, getInstance(context).getCountryCodeForRegion(region)));
}
return countries;
}
public static class Country implements Comparable<Country> {
private final String name;
private final String region;
private final int code;
Country(String region, int code) {
this.name = getCountryForCode(region);
this.region = region;
this.code = code;
}
public String getName() {
return name;
}
public String getRegion() {
return region;
}
public String getCode() {
return '+' + String.valueOf(code);
}
@Override
public int compareTo(Country o) {
return name.compareTo(o.name);
}
}
}

View file

@ -1,9 +0,0 @@
package eu.siacs.conversations.utils;
import eu.siacs.conversations.ui.UriHandlerActivity;
public class ProvisioningUtils {
public static void provision(UriHandlerActivity uriHandlerActivity, String result) {
throw new IllegalStateException("Quicksy does not support provisioning");
}
}

View file

@ -1,58 +0,0 @@
package eu.siacs.conversations.utils;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.EditAccountActivity;
import eu.siacs.conversations.ui.EnterPhoneNumberActivity;
import eu.siacs.conversations.ui.StartConversationActivity;
import eu.siacs.conversations.ui.TosActivity;
import eu.siacs.conversations.ui.VerifyActivity;
import eu.siacs.conversations.xmpp.Jid;
public class SignupUtils {
public static Intent getSignUpIntent(Activity activity, boolean ignored) {
return getSignUpIntent(activity);
}
public static Intent getSignUpIntent(Activity activity) {
return new Intent(activity, EnterPhoneNumberActivity.class);
}
public static Intent getRedirectionIntent(ConversationsActivity activity) {
final Intent intent;
final Account account = AccountUtils.getFirst(activity.xmppConnectionService);
if (account != null) {
if (account.isOptionSet(Account.OPTION_UNVERIFIED)) {
intent = new Intent(activity, VerifyActivity.class);
} else {
intent = new Intent(activity, StartConversationActivity.class);
}
} else {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
if (preferences.getBoolean("tos",false)) {
intent = getSignUpIntent(activity);
} else {
intent = new Intent(activity, TosActivity.class);
}
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return intent;
}
public static boolean isSupportTokenRegistry() {
return false;
}
public static Intent getTokenRegistrationIntent(Activity activity, Jid preset, String key) {
return null;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View file

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="49.41353"
android:viewportHeight="49.413532">
<group
android:translateX="13.888052"
android:translateY="13.888054">
<path
android:fillColor="#ffffff"
android:pathData="m10.8073,0.5a9.9285,9.9285 0,0 0,-9.9284 9.9284,9.9285 9.9285,0 0,0 9.9284,9.9286 9.9285,9.9285 0,0 0,4.4216 -1.0435l0.0133,0.0045 4.2237,1.6392c0.337,0.1308 0.7803,0.3154 1.0825,0.0284 0.2934,-0.2788 0.2244,-0.5614 0.1229,-0.9745l-1.1748,-4.7803a9.9285,9.9285 0,0 0,1.2395 -4.7992,9.9285 9.9285,0 0,0 -9.9286,-9.9285zM5.7004,8.1173a1.1946,1.1946 135,0 1,1.1944 1.1946,1.1946 1.1946,0 0,1 -1.1944,1.1944 1.1946,1.1946 135,0 1,-1.1946 -1.1944,1.1946 1.1946,0 0,1 1.1946,-1.1946zM10.9304,8.1173a1.1946,1.1946 135,0 1,1.1946 1.1946,1.1946 1.1946,0 0,1 -1.1946,1.1944 1.1946,1.1946 0,0 1,-1.1944 -1.1944,1.1946 1.1946,135 0,1 1.1944,-1.1946zM16.1818,8.1173a1.1946,1.1946 135,0 1,1.1946 1.1946,1.1946 1.1946,0 0,1 -1.1946,1.1944 1.1946,1.1946 135,0 1,-1.1946 -1.1944,1.1946 1.1946,0 0,1 1.1946,-1.1946zM4.6246,11.2577c0.8992,0 1.6282,0.8652 1.6282,1.9323 0.0672,3.9098 -1.7698,-0.1304 -3.0862,-0.0963 -0.0481,-1.2839 0.9335,-1.836 1.458,-1.836zM17.2571,11.2577c0.5245,0 1.5061,0.5521 1.458,1.836 -1.3164,-0.034 -3.1534,4.0061 -3.0862,0.0963 0,-1.0671 0.729,-1.9323 1.6282,-1.9323zM10.937,11.5584c0.3358,0.0051 0.6757,0.0485 0.9853,0.1786 0.3096,0.1301 0.5881,0.354 0.7381,0.6545 0.1078,0.216 0.1453,0.462 0.1401,0.7034 -0.0051,0.2414 -0.0514,0.48 -0.1037,0.7157 -0.0523,0.2358 -0.1111,0.4705 -0.1418,0.71 -0.0307,0.2395 -0.033,0.4856 0.0271,0.7195 0.1124,0.4392 0.454,0.8129 0.8814,0.9641 0.4274,0.1512 0.9279,0.0754 1.2915,-0.1955 -1.0722,0.9392 -2.496,1.467 -3.9213,1.4536 -1.3779,-0.013 -2.7453,-0.5302 -3.7869,-1.4323 0.3657,0.2622 0.8612,0.3326 1.2854,0.1826 0.4242,-0.15 0.7655,-0.5162 0.885,-0.9501 0.066,-0.2392 0.0673,-0.4923 0.039,-0.7388 -0.0284,-0.2465 -0.086,-0.4885 -0.1365,-0.7315 -0.0504,-0.243 -0.0942,-0.489 -0.0946,-0.7371 0,-0.2482 0.045,-0.5003 0.1629,-0.7185 0.1598,-0.2954 0.4453,-0.5101 0.7589,-0.6304 0.3136,-0.1203 0.6545,-0.1529 0.9903,-0.1477z" />
</group>
</vector>

View file

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="49.41353"
android:viewportHeight="49.413532">
<group
android:translateX="13.888052"
android:translateY="13.888054">
<path
android:fillColor="#000000"
android:pathData="m10.8073,0.5a9.9285,9.9285 0,0 0,-9.9284 9.9284,9.9285 9.9285,0 0,0 9.9284,9.9286 9.9285,9.9285 0,0 0,4.4216 -1.0435l0.0133,0.0045 4.2237,1.6392c0.337,0.1308 0.7803,0.3154 1.0825,0.0284 0.2934,-0.2788 0.2244,-0.5614 0.1229,-0.9745l-1.1748,-4.7803a9.9285,9.9285 0,0 0,1.2395 -4.7992,9.9285 9.9285,0 0,0 -9.9286,-9.9285zM5.7004,8.1173a1.1946,1.1946 135,0 1,1.1944 1.1946,1.1946 1.1946,0 0,1 -1.1944,1.1944 1.1946,1.1946 135,0 1,-1.1946 -1.1944,1.1946 1.1946,0 0,1 1.1946,-1.1946zM10.9304,8.1173a1.1946,1.1946 135,0 1,1.1946 1.1946,1.1946 1.1946,0 0,1 -1.1946,1.1944 1.1946,1.1946 0,0 1,-1.1944 -1.1944,1.1946 1.1946,135 0,1 1.1944,-1.1946zM16.1818,8.1173a1.1946,1.1946 135,0 1,1.1946 1.1946,1.1946 1.1946,0 0,1 -1.1946,1.1944 1.1946,1.1946 135,0 1,-1.1946 -1.1944,1.1946 1.1946,0 0,1 1.1946,-1.1946zM4.6246,11.2577c0.8992,0 1.6282,0.8652 1.6282,1.9323 0.0672,3.9098 -1.7698,-0.1304 -3.0862,-0.0963 -0.0481,-1.2839 0.9335,-1.836 1.458,-1.836zM17.2571,11.2577c0.5245,0 1.5061,0.5521 1.458,1.836 -1.3164,-0.034 -3.1534,4.0061 -3.0862,0.0963 0,-1.0671 0.729,-1.9323 1.6282,-1.9323zM10.937,11.5584c0.3358,0.0051 0.6757,0.0485 0.9853,0.1786 0.3096,0.1301 0.5881,0.354 0.7381,0.6545 0.1078,0.216 0.1453,0.462 0.1401,0.7034 -0.0051,0.2414 -0.0514,0.48 -0.1037,0.7157 -0.0523,0.2358 -0.1111,0.4705 -0.1418,0.71 -0.0307,0.2395 -0.033,0.4856 0.0271,0.7195 0.1124,0.4392 0.454,0.8129 0.8814,0.9641 0.4274,0.1512 0.9279,0.0754 1.2915,-0.1955 -1.0722,0.9392 -2.496,1.467 -3.9213,1.4536 -1.3779,-0.013 -2.7453,-0.5302 -3.7869,-1.4323 0.3657,0.2622 0.8612,0.3326 1.2854,0.1826 0.4242,-0.15 0.7655,-0.5162 0.885,-0.9501 0.066,-0.2392 0.0673,-0.4923 0.039,-0.7388 -0.0284,-0.2465 -0.086,-0.4885 -0.1365,-0.7315 -0.0504,-0.243 -0.0942,-0.489 -0.0946,-0.7371 0,-0.2482 0.045,-0.5003 0.1629,-0.7185 0.1598,-0.2954 0.4453,-0.5101 0.7589,-0.6304 0.3136,-0.1203 0.6545,-0.1529 0.9903,-0.1477z" />
</group>
</vector>

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/color_background_primary"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/countries"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipToPadding="false"
android:padding="2dp"
android:scrollbars="vertical"/>
</LinearLayout>
</layout>

View file

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/instructions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="16dp"
android:text="@string/enter_your_name_instructions"
android:textAppearance="@style/TextAppearance.Conversations.Body1" />
<LinearLayout
android:id="@+id/name_box"
android:layout_width="256dp"
android:layout_height="wrap_content"
android:layout_above="@+id/next"
android:layout_below="@+id/instructions"
android:layout_centerHorizontal="true"
android:orientation="vertical">
<EditText
android:id="@+id/name"
style="@style/Widget.Conversations.EditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/your_name"
android:imeOptions="flagNoExtractUi"
android:longClickable="false" />
</LinearLayout>
<Button
android:id="@+id/next"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:text="@string/next"
android:textColor="?colorAccent" />
</RelativeLayout>
</ScrollView>
</LinearLayout>
</layout>

View file

@ -1,114 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/instructions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="16dp"
android:text="@string/enter_country_code_and_phone_number"
android:textAppearance="@style/TextAppearance.Conversations.Body1" />
<LinearLayout
android:id="@+id/phone_number_box"
android:layout_width="256dp"
android:layout_height="wrap_content"
android:layout_above="@+id/next"
android:layout_below="@+id/instructions"
android:layout_centerHorizontal="true"
android:orientation="vertical">
<EditText
android:id="@+id/country"
style="@style/Widget.Conversations.EditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="false"
android:drawableEnd="@drawable/ic_arrow_drop_down_black_18dp"
app:drawableTint="?android:attr/textColorPrimary"
android:focusable="false"
android:gravity="bottom|center_horizontal"
android:imeOptions="flagNoExtractUi"
android:inputType="textNoSuggestions"
android:longClickable="false" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/country_code"
style="@style/Widget.Conversations.EditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="bottom|center_horizontal"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:longClickable="false"
android:maxLength="3"
android:maxLines="1" />
<EditText
android:id="@+id/number"
style="@style/Widget.Conversations.EditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:gravity="bottom|start"
android:hint="@string/phone_number"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:longClickable="false"
android:maxLines="1" />
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
<Button
android:id="@+id/next"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:text="@string/next"
android:textColor="?colorAccent" />
</RelativeLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</layout>

View file

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<ScrollView android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/color_background_primary">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:minHeight="256dp"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingBottom="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/welcome_header_quicksy"
android:textAppearance="@style/TextAppearance.Conversations.Title"/>
<TextView
android:id="@+id/welcome_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/welcome_text_quicksy_static"
android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
</LinearLayout>
<Button
android:id="@+id/agree"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="@string/agree_and_continue"
android:textColor="?colorAccent"/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/linearLayout"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:padding="8dp"
android:src="@drawable/main_logo"/>
</RelativeLayout>
</RelativeLayout>
</ScrollView>
</LinearLayout>

View file

@ -1,193 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/color_background_primary"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/instructions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/we_have_sent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/we_have_sent_you_an_sms_to_x"
android:textAppearance="@style/TextAppearance.Conversations.Subhead" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/please_enter_pin_below"
android:textAppearance="@style/TextAppearance.Conversations.Body1" />
</LinearLayout>
<LinearLayout
android:id="@+id/pin_box"
android:layout_width="230sp"
android:layout_height="wrap_content"
android:layout_below="@+id/instructions"
android:layout_centerHorizontal="true">
<EditText
android:layout_width="35sp"
android:layout_height="wrap_content"
android:digits="1234567890"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:maxLength="1"
android:textIsSelectable="false"
android:textSize="40sp" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:layout_width="35sp"
android:layout_height="wrap_content"
android:digits="1234567890"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:maxLength="1"
android:textIsSelectable="false"
android:textSize="40sp" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:layout_width="35sp"
android:layout_height="wrap_content"
android:digits="1234567890"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:maxLength="1"
android:textIsSelectable="false"
android:textSize="40sp" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:layout_width="35sp"
android:layout_height="wrap_content"
android:digits="1234567890"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:maxLength="1"
android:textIsSelectable="false"
android:textSize="40sp" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:layout_width="35sp"
android:layout_height="wrap_content"
android:digits="1234567890"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:maxLength="1"
android:textIsSelectable="false"
android:textSize="40sp" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:layout_width="35sp"
android:layout_height="wrap_content"
android:digits="1234567890"
android:gravity="center"
android:imeOptions="flagNoExtractUi"
android:inputType="number"
android:maxLength="1"
android:textIsSelectable="false"
android:textSize="40sp" />
</LinearLayout>
<Button
android:id="@+id/next"
style="@style/Widget.Conversations.Button.Borderless.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:text="@string/next" />
<Button
android:id="@+id/back"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:text="@string/back"
android:textColor="?android:textColorSecondary" />
<Button
android:id="@+id/resend_sms"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/pin_box"
android:layout_centerHorizontal="true"
android:text="@string/resend_sms"
android:textColor="?android:textColorSecondary" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/pin_box"
android:layout_centerHorizontal="true" />
</RelativeLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</layout>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:padding="16dp">
<TextView
android:id="@+id/country"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:text="Germany"
android:textAppearance="@style/TextAppearance.Conversations.Subhead.Bold" />
<TextView
android:id="@+id/country_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="+49"
android:textAppearance="@style/TextAppearance.Conversations.Subhead.Bold.Secondary" />
</RelativeLayout>
</layout>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
app:actionLayout="@layout/actionview_search"
android:icon="?attr/icon_search"
app:showAsAction="collapseActionView|always"
android:title="@string/search"/>
</menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">مدى الوقت الذي يظل فيه Quicksy هادئًا بعد رؤية نشاط على جهاز آخر</string>
<string name="pref_never_send_crash_summary">عبر إرسال الأخطاء انت تقوم بالمساعدة في تطوير برمجة Quicksy</string>
<string name="pref_broadcast_last_activity_summary">إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي</string>
<string name="huawei_protected_apps_summary">للمواصلة في إستقبال التنبيهات، حتى والشاشة مغلقة، يجب عليك أن تضيف Quicksy إلى قائمة التطبيقات المحميّة.</string>
<string name="set_profile_picture">صورة حساب Quicksy</string>
<string name="not_available_in_your_country">إن كويكسي Quicksy غير متوفر في بلدكم.</string>
<string name="unable_to_verify_server_identity">لا يمكن التأكد من خادم الهويّة.</string>
<string name="unknown_security_error">خطأ أمني مجهول.</string>
<string name="timeout_while_connecting_to_server">تجاوز الوقت أثناء الإتصال بالخادم.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Времето, през което Quicksy няма да прави нищо, след като забележи дейност на друго устройство</string>
<string name="pref_never_send_crash_summary">Изпращайки проследявания на стека, Вие помагате за непрекъснатото развитие на Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Така всичките Ви контакти ще знаят кога използвате Quicksy</string>
<string name="huawei_protected_apps_summary">Ако искате да продължите да получавате известия дори когато екранът е заключен, трябва да добавите Quicksy към списъка със защитени приложения.</string>
<string name="set_profile_picture">Профилна снимка за Quicksy</string>
<string name="not_available_in_your_country">Quicksy не може да се използва във Вашата страна.</string>
<string name="unable_to_verify_server_identity">Идентичността на сървъра не може да бъде потвърдена.</string>
<string name="unknown_security_error">Неизвестна грешка в сигурността.</string>
<string name="timeout_while_connecting_to_server">Времето за изчакване на сървъра изтече.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">El temps que Quicksy roman en silenci després de veure activitat en un altre dispositiu.</string>
<string name="pref_never_send_crash_summary">En enviar les traces de les piles, vostè està ajudant al desenvolupament continu de Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Avisi a tots els seus contactes quan utilitzi Quicksy</string>
<string name="huawei_protected_apps_summary">Per seguir rebent notificacions, fins i tot quan la pantalla està desactivada, és necessari afegir Quicksy a la llista d\'aplicacions protegides.</string>
<string name="set_profile_picture">Imatge de perfil en Quicksy</string>
<string name="not_available_in_your_country">Quicksy no està disponible al teu país.</string>
<string name="unable_to_verify_server_identity">No es pot verificar la identitat del servidor.</string>
<string name="unknown_security_error">Error de seguretat desconegut.</string>
<string name="timeout_while_connecting_to_server">Temps d\'espera mentre es connecta al servidor.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Hvor lang tid Quicksy er stille efter at have set aktivitet på en anden enhed</string>
<string name="pref_never_send_crash_summary">Ved at indsende stakspor hjælper du den løbende udvikling af Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Lad alle dine kontakter vide når du bruger Quicksy </string>
<string name="huawei_protected_apps_summary">For at modtage notifikationer, selv når skærmen er slukket, skal du tilføje Quicksy til listen over beskyttede apps.</string>
<string name="set_profile_picture">Quicksy profilbillede</string>
<string name="not_available_in_your_country">Quicksy er ikke tilgængelig i dit land.</string>
<string name="unable_to_verify_server_identity">Kan ikke bekræfte server identitet.</string>
<string name="unknown_security_error">Ukendt sikkerhedsfejl.</string>
<string name="timeout_while_connecting_to_server">Timeout under tilslutning til serveren.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Zeitspanne, in der Quicksy still bleibt, nachdem es Aktivitäten auf einem anderen Gerät erkannt hat</string>
<string name="pref_never_send_crash_summary">Mit dem Einsenden von Absturzberichten hilfst du bei der Weiterentwicklung von Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Informiere deine Kontakte, wann du Quicksy nutzt</string>
<string name="huawei_protected_apps_summary">Um weiterhin Benachrichtigungen zu erhalten, auch wenn der Bildschirm ausgeschaltet ist, musst du Quicksy zur Liste der geschützten Apps hinzufügen.</string>
<string name="set_profile_picture">Quicksy Profilbild</string>
<string name="not_available_in_your_country">Quicksy ist in deinem Land nicht verfügbar.</string>
<string name="unable_to_verify_server_identity">Überprüfung der Serveridentität ist nicht möglich.</string>
<string name="unknown_security_error">Unbekannter Sicherheitsfehler.</string>
<string name="timeout_while_connecting_to_server">Zeitüberschreitung bei der Verbindung zum Server.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Ο χρόνος σίγασης ειδοποιήσεων του Quicksy αφότου ανιχνευθεί δραστηριότητα σε μια από τις άλλες συσκευές σας.</string>
<string name="pref_never_send_crash_summary">Στέλνοντας ίχνη στοίβας προωθείτε την συνεχόμενη ανάπτυξη του Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Επιτρέψτε στις επαφές σας να γνωρίζουν πότε χρησιμοποιείτε το Quicksy</string>
<string name="huawei_protected_apps_summary">Για να συνεχίσετε να λαμβάνετε ειδοποιήσεις, ακόμα κι όταν η οθόνη είναι σβηστή, χρειάζεται να προσθέσετε το Quicksy στον κατάλογο με τις προστατευμένες εφαρμογές.</string>
<string name="set_profile_picture">Φωτογραφία προφίλ του Quicksy</string>
<string name="not_available_in_your_country">Το Quicksy δεν είναι διαθέσιμο στην χώρα σας.</string>
<string name="unable_to_verify_server_identity">Αδυναμία επαλήθευσης της ταυτότητας του διακομιστή.</string>
<string name="unknown_security_error">Άγνωστο σφάλμα ασφάλειας.</string>
<string name="timeout_while_connecting_to_server">Λήξη χρονικού ορίου κατά τη σύνδεση στον διακομιστή.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="huawei_protected_apps_summary">Por daŭre ricevi sciigojn, eĉ kiam la ekrano estas malŝaltita, vi devas aldoni Quicksy al la listo de protektitaj programoj.</string>
<string name="pref_never_send_crash_summary">Sendante stakspurojn vi helpas la daŭran disvolviĝon de Quicksy</string>
<string name="unable_to_verify_server_identity">Ne eblas kontroli servilan identecon.</string>
<string name="unknown_security_error">Nekonata sekureca eraro.</string>
<string name="timeout_while_connecting_to_server">Eltempiĝo dum konektante al servilo.</string>
<string name="not_available_in_your_country">Quicksy ne haveblas en via lando.</string>
<string name="set_profile_picture">Quicksy profilbildo</string>
<string name="pref_notification_grace_period_summary">La tempodaŭro Quicksy silentas post vidado de agado sur alia aparato</string>
<string name="pref_broadcast_last_activity_summary">Sciigi ĉiujn viajn kontaktojn kiam vi uzas Quicksy</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">El tiempo que Quicksy silencia las notificaciones tras detectar actividad en otro de tus dispositivos</string>
<string name="pref_never_send_crash_summary">Al enviar informes de fallos, ayudará a desarrollar Quicksy aún más</string>
<string name="pref_broadcast_last_activity_summary">Informar a tus contactos cuando usas Quicksy</string>
<string name="huawei_protected_apps_summary">Para continuar recibiendo notificaciones incluso cuando la pantalla está apagada, debe agregar Quicksy a la lista de aplicaciones protegidas.</string>
<string name="set_profile_picture">Foto de perfil de Quicksy</string>
<string name="not_available_in_your_country">Quicksy no está disponible en tu país.</string>
<string name="unable_to_verify_server_identity">No se ha podido verificar la identidad del servidor.</string>
<string name="unknown_security_error">Error de seguridad desconocido.</string>
<string name="timeout_while_connecting_to_server">Se ha superado el tiempo máximo de espera conectando al servidor.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Kuinka kauan Quicksy pysyy hiljaa nähtyään toisella laitteellasi toimintaa</string>
<string name="pref_never_send_crash_summary">Lähettämällä virheenkorjaustietoja autat Quicksyn kehittäjiä</string>
<string name="pref_broadcast_last_activity_summary">Kerro kaikille yhteystiedoillesi kun käytät Quicksya</string>
<string name="huawei_protected_apps_summary">Saadaksesi ilmoituksia silloinkin kun näyttö on sammutettu, Quicksy pitää lisätä suojattujen sovellusten luetteloon.</string>
<string name="set_profile_picture">Quicksy-profiilikuva</string>
<string name="not_available_in_your_country">Quicksy ei ole saatavilla maassasi.</string>
<string name="unable_to_verify_server_identity">Palvelimen identiteetin varmennus epäonnistui.</string>
<string name="unknown_security_error">Tuntematon turvallisuusvirhe.</string>
<string name="timeout_while_connecting_to_server">Palvelimeen yhdistäminen aikakatkaistiin.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Durée dinactivité de Quicksy après avoir repéré un changement sur un autre appareil</string>
<string name="pref_never_send_crash_summary">En envoyant des traces dappels, vous aidez le développement de Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Faites savoir à tous vos contacts quand vous utilisez Quicksy</string>
<string name="huawei_protected_apps_summary">Pour continuer à recevoir des notifications, même lorsque lécran est éteint, vous devez ajouter Quicksy à la liste des applications protégées.</string>
<string name="set_profile_picture">Photo de profil Quicksy</string>
<string name="not_available_in_your_country">Quicksy nest pas disponible dans votre pays.</string>
<string name="unable_to_verify_server_identity">Impossible de vérifier lidentité du serveur.</string>
<string name="unknown_security_error">Erreur de sécurité inconnue.</string>
<string name="timeout_while_connecting_to_server">Délai expiré lors de la connexion au serveur.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">O período de tempo que Quicksy permanece acalado tras ver actividade noutro dispositivo</string>
<string name="pref_never_send_crash_summary">Enviando trazas do rexistro estás axudando ao desenvolvemento de Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Permitir a todos os teus contactos saber cando estás a utilizar Quicksy</string>
<string name="huawei_protected_apps_summary">Para seguir recibindo notificacións, mesmo coa pantalla apagada, tes que engadir a Quicksy á lista de apps protexidas.</string>
<string name="set_profile_picture">Imaxe de perfil Quicksy</string>
<string name="not_available_in_your_country">Quicksy non está dispoñible no teu país.</string>
<string name="unable_to_verify_server_identity">Non se puido verificar a identidade do servidor.</string>
<string name="unknown_security_error">Fallo de seguridade descoñecido.</string>
<string name="timeout_while_connecting_to_server">Caducou a conexión mentras conectaba co servidor.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Duljina vremena u kojem Quicksy šuti nakon što vidi aktivnost na drugom uređaju</string>
<string name="pref_never_send_crash_summary">Slanjem tragova hrpe pomažete tekući razvoj Quicksyja</string>
<string name="pref_broadcast_last_activity_summary">Obavijestite sve svoje kontakte kada koristite Quicksy</string>
<string name="huawei_protected_apps_summary">Kako biste nastavili primati obavijesti, čak i kada je ekran isključen, trebate dodati Quicksy na popis zaštićenih aplikacija.</string>
<string name="set_profile_picture">Quicksy profilna slika</string>
<string name="not_available_in_your_country">Quicksy nije dostupan u vašoj zemlji.</string>
<string name="unable_to_verify_server_identity">Nije moguće potvrditi identitet poslužitelja.</string>
<string name="unknown_security_error">Nepoznata sigurnosna pogreška.</string>
<string name="timeout_while_connecting_to_server">Istek vremena tijekom povezivanja s poslužiteljem.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">A Quicksy csendben marad ennyi ideig, miután aktivitást észlelt egy másik eszközön</string>
<string name="pref_never_send_crash_summary">A veremkiíratások elküldésével segíti a Quicksy alkalmazás folyamatos fejlesztését</string>
<string name="pref_broadcast_last_activity_summary">Tudassa az összes partnerével, hogy a Quicksy alkalmazást használja</string>
<string name="huawei_protected_apps_summary">Ha akkor is szeretne értesítéseket kapni, amikor a kijelző ki van kapcsolva, hozzá kell adnia a Quicksy alkalmazást a védett alkalmazások listájához.</string>
<string name="set_profile_picture">Quicksy profilkép</string>
<string name="not_available_in_your_country">A Quicksy nem érhető el az Ön országában.</string>
<string name="unable_to_verify_server_identity">Nem sikerült ellenőrizni a kiszolgáló személyazonosságát.</string>
<string name="unknown_security_error">Ismeretlen biztonsági hiba.</string>
<string name="timeout_while_connecting_to_server">Időtúllépés a kiszolgálóhoz való csatlakozáskor.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Durasi Quicksy tetap diam setelah melihat aktivitas di perangkat lain</string>
<string name="pref_never_send_crash_summary">Dengan mengirimkan data log, Anda sedang membantu pengembangan Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Ijinkan kontak anda mengetahui kapan anda menggunakan Quicksy</string>
<string name="huawei_protected_apps_summary">Untuk tetap menerima notifikasi, bahkan saat layar mati, Anda perlu menambahkan Quicksy ke daftar aplikasi yang dilindungi.</string>
<string name="set_profile_picture">Gambar profil Quicksy</string>
<string name="not_available_in_your_country">Quicksy tidak tersedia di negara anda.</string>
<string name="unable_to_verify_server_identity">Gagal memverifikasi identitas server</string>
<string name="unknown_security_error">Error keamanan tidak dikenal.</string>
<string name="timeout_while_connecting_to_server">Waktu habis saat menghubungi server.</string>
</resources>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_notification_grace_period_summary">Il periodo di tempo in cui Quicksy resta silenzioso quando vede attività su un altro dispositivo</string>
<string name="pref_never_send_crash_summary">Se scegli di inviare una segnalazione dellerrore aiuterai lo sviluppo di Quicksy</string>
<string name="pref_broadcast_last_activity_summary">Fai sapere ai tuoi contatti quando usi Quicksy</string>
<string name="huawei_protected_apps_summary">Per ricevere notifiche anche quando lo schermo è spento, devi aggiungere Quicksy all\'elenco delle app protette.</string>
<string name="set_profile_picture">Immagine profilo di Quicksy</string>
<string name="not_available_in_your_country">Quicksy non è disponibile nella tua nazione.</string>
<string name="unable_to_verify_server_identity">Impossibile verificare l\'identità del server.</string>
<string name="unknown_security_error">Errore di sicurezza sconosciuto.</string>
<string name="timeout_while_connecting_to_server">Tentativo di connessione al server scaduto.</string>
</resources>

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