1package eu.siacs.conversations.ui;
2
3import android.Manifest;
4import android.content.ActivityNotFoundException;
5import android.content.Intent;
6import android.content.pm.ActivityInfo;
7import android.net.Uri;
8import android.os.Bundle;
9import android.security.KeyChain;
10import android.security.KeyChainAliasCallback;
11import android.util.Log;
12import android.view.Menu;
13import android.view.MenuItem;
14import android.widget.Toast;
15
16import androidx.annotation.NonNull;
17import androidx.appcompat.app.AppCompatActivity;
18import androidx.databinding.DataBindingUtil;
19
20import java.security.SecureRandom;
21import java.util.Arrays;
22import java.util.List;
23import java.util.HashSet;
24import java.util.UUID;
25
26import eu.siacs.conversations.Config;
27import eu.siacs.conversations.R;
28import eu.siacs.conversations.databinding.ActivityWelcomeBinding;
29import eu.siacs.conversations.entities.Account;
30import eu.siacs.conversations.services.XmppConnectionService;
31import eu.siacs.conversations.utils.CryptoHelper;
32import eu.siacs.conversations.utils.Compatibility;
33import eu.siacs.conversations.utils.InstallReferrerUtils;
34import eu.siacs.conversations.utils.SignupUtils;
35import eu.siacs.conversations.utils.XmppUri;
36import eu.siacs.conversations.xmpp.Jid;
37
38import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
39import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
40
41public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, XmppConnectionService.OnAccountUpdate, KeyChainAliasCallback {
42
43 private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
44
45 private XmppUri inviteUri;
46 private Account onboardingAccount = null;
47
48 public static void launch(AppCompatActivity activity) {
49 Intent intent = new Intent(activity, WelcomeActivity.class);
50 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
51 activity.startActivity(intent);
52 activity.overridePendingTransition(0, 0);
53 }
54
55 public void onInstallReferrerDiscovered(final Uri referrer) {
56 Log.d(Config.LOGTAG, "welcome activity: on install referrer discovered " + referrer);
57 if ("xmpp".equalsIgnoreCase(referrer.getScheme())) {
58 final XmppUri xmppUri = new XmppUri(referrer);
59 runOnUiThread(() -> processXmppUri(xmppUri));
60 } else {
61 Log.i(Config.LOGTAG, "install referrer was not an XMPP uri");
62 }
63 }
64
65 private void processXmppUri(final XmppUri xmppUri) {
66 if (!xmppUri.isValidJid()) {
67 return;
68 }
69 final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH);
70 final Jid jid = xmppUri.getJid();
71 final Intent intent;
72 if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
73 intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth);
74 } else if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) {
75 intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth);
76 intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
77 } else {
78 intent = null;
79 }
80 if (intent != null) {
81 startActivity(intent);
82 finish();
83 return;
84 }
85 this.inviteUri = xmppUri;
86 }
87
88 @Override
89 protected synchronized void refreshUiReal() {
90 if (onboardingAccount == null) return;
91 if (onboardingAccount.getStatus() != Account.State.ONLINE) return;
92
93 Intent intent = new Intent(this, StartConversationActivity.class);
94 intent.putExtra("init", true);
95 intent.putExtra(EXTRA_ACCOUNT, onboardingAccount.getJid().asBareJid().toEscapedString());
96 onboardingAccount = null;
97 startActivity(intent);
98 finish();
99 }
100
101 @Override
102 public void onAccountUpdate() {
103 refreshUi();
104 }
105
106 @Override
107 void onBackendConnected() {
108
109 }
110
111 @Override
112 public void onStart() {
113 super.onStart();
114 final int theme = findTheme();
115 if (this.mTheme != theme) {
116 recreate();
117 }
118 new InstallReferrerUtils(this);
119 }
120
121 @Override
122 public void onStop() {
123 super.onStop();
124 }
125
126 @Override
127 public void onNewIntent(Intent intent) {
128 if (intent != null) {
129 setIntent(intent);
130 }
131 }
132
133 @Override
134 protected void onCreate(final Bundle savedInstanceState) {
135 if (getResources().getBoolean(R.bool.portrait_only)) {
136 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
137 }
138 super.onCreate(savedInstanceState);
139 getPreferences().edit().putStringSet("pstn_gateways", new HashSet<>()).apply();
140 ActivityWelcomeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_welcome);
141 setSupportActionBar(binding.toolbar);
142 configureActionBar(getSupportActionBar(), false);
143 binding.registerNewAccount.setOnClickListener(v -> {
144 if (hasInviteUri()) {
145 final Intent intent = new Intent(this, MagicCreateActivity.class);
146 addInviteUri(intent);
147 startActivity(intent);
148 } else {
149 binding.registerNewAccount.setText("Working...");
150 binding.registerNewAccount.setEnabled(false);
151 onboardingAccount = new Account(Jid.ofLocalAndDomain(UUID.randomUUID().toString(), Config.ONBOARDING_DOMAIN.toEscapedString()), CryptoHelper.createPassword(new SecureRandom()));
152 onboardingAccount.setOption(Account.OPTION_REGISTER, true);
153 onboardingAccount.setOption(Account.OPTION_FIXED_USERNAME, true);
154 xmppConnectionService.createAccount(onboardingAccount);
155 }
156 });
157 binding.useExisting.setOnClickListener(v -> {
158 final List<Account> accounts = xmppConnectionService.getAccounts();
159 Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class);
160 intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, false);
161 if (accounts.size() == 1) {
162 intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
163 intent.putExtra("init", true);
164 } else if (accounts.size() >= 1) {
165 intent = new Intent(WelcomeActivity.this, ManageAccountActivity.class);
166 }
167 addInviteUri(intent);
168 startActivity(intent);
169 });
170 binding.useSnikket.setOnClickListener(v -> {
171 final List<Account> accounts = xmppConnectionService.getAccounts();
172 Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class);
173 intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, false);
174 intent.putExtra("snikket", true);
175 if (accounts.size() == 1) {
176 intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
177 intent.putExtra("init", true);
178 } else if (accounts.size() >= 1) {
179 intent = new Intent(WelcomeActivity.this, ManageAccountActivity.class);
180 }
181 addInviteUri(intent);
182 startActivity(intent);
183 });
184 }
185
186 @Override
187 public boolean onCreateOptionsMenu(Menu menu) {
188 getMenuInflater().inflate(R.menu.welcome_menu, menu);
189 final MenuItem scan = menu.findItem(R.id.action_scan_qr_code);
190 scan.setVisible(Compatibility.hasFeatureCamera(this));
191 return super.onCreateOptionsMenu(menu);
192 }
193
194
195
196 @Override
197 public boolean onOptionsItemSelected(MenuItem item) {
198 switch (item.getItemId()) {
199 case R.id.action_import_backup:
200 if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
201 startActivity(new Intent(this, ImportBackupActivity.class));
202 }
203 break;
204 case R.id.action_scan_qr_code:
205 UriHandlerActivity.scan(this, true);
206 break;
207 case R.id.action_add_account_with_cert:
208 addAccountFromKey();
209 break;
210 }
211 return super.onOptionsItemSelected(item);
212 }
213
214 private void addAccountFromKey() {
215 try {
216 KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
217 } catch (ActivityNotFoundException e) {
218 Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
219 }
220 }
221
222 @Override
223 public void alias(final String alias) {
224 if (alias != null) {
225 xmppConnectionService.createAccountFromKey(alias, this);
226 }
227 }
228
229 @Override
230 public void onAccountCreated(final Account account) {
231 final Intent intent = new Intent(this, EditAccountActivity.class);
232 intent.putExtra("jid", account.getJid().asBareJid().toEscapedString());
233 intent.putExtra("init", true);
234 addInviteUri(intent);
235 startActivity(intent);
236 }
237
238 @Override
239 public void informUser(final int r) {
240 runOnUiThread(() -> Toast.makeText(this, r, Toast.LENGTH_LONG).show());
241 }
242
243 @Override
244 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
245 UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
246 if (grantResults.length > 0) {
247 if (allGranted(grantResults)) {
248 switch (requestCode) {
249 case REQUEST_IMPORT_BACKUP:
250 startActivity(new Intent(this, ImportBackupActivity.class));
251 break;
252 }
253 } else if (Arrays.asList(permissions).contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
254 Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
255 }
256 }
257 if (writeGranted(grantResults, permissions)) {
258 if (xmppConnectionService != null) {
259 xmppConnectionService.restartFileObserver();
260 }
261 }
262 }
263
264 protected boolean hasInviteUri() {
265 final Intent from = getIntent();
266 if (from != null && from.hasExtra(StartConversationActivity.EXTRA_INVITE_URI)) return true;
267 return this.inviteUri != null;
268 }
269
270 public void addInviteUri(Intent to) {
271 final Intent from = getIntent();
272 if (from != null && from.hasExtra(StartConversationActivity.EXTRA_INVITE_URI)) {
273 final String invite = from.getStringExtra(StartConversationActivity.EXTRA_INVITE_URI);
274 to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, invite);
275 } else if (this.inviteUri != null) {
276 Log.d(Config.LOGTAG, "injecting referrer uri into on-boarding flow");
277 to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, this.inviteUri.toString());
278 }
279 }
280
281}