WelcomeActivity.java

  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.view.View;
 15import android.view.ViewGroup;
 16import android.widget.Toast;
 17
 18import androidx.annotation.NonNull;
 19import androidx.appcompat.app.AppCompatActivity;
 20import androidx.databinding.DataBindingUtil;
 21import androidx.viewpager.widget.PagerAdapter;
 22import androidx.viewpager.widget.ViewPager;
 23
 24import java.security.SecureRandom;
 25import java.util.Arrays;
 26import java.util.List;
 27import java.util.HashSet;
 28import java.util.UUID;
 29
 30import eu.siacs.conversations.Config;
 31import eu.siacs.conversations.R;
 32import eu.siacs.conversations.databinding.ActivityWelcomeBinding;
 33import eu.siacs.conversations.entities.Account;
 34import eu.siacs.conversations.services.XmppConnectionService;
 35import eu.siacs.conversations.utils.CryptoHelper;
 36import eu.siacs.conversations.utils.Compatibility;
 37import eu.siacs.conversations.utils.InstallReferrerUtils;
 38import eu.siacs.conversations.utils.SignupUtils;
 39import eu.siacs.conversations.utils.XmppUri;
 40import eu.siacs.conversations.xmpp.Jid;
 41
 42import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
 43import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
 44
 45public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, XmppConnectionService.OnAccountUpdate, KeyChainAliasCallback {
 46
 47    private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
 48
 49    private XmppUri inviteUri;
 50    private Account onboardingAccount = null;
 51    private ActivityWelcomeBinding binding = null;
 52
 53    public static void launch(AppCompatActivity activity) {
 54        Intent intent = new Intent(activity, WelcomeActivity.class);
 55        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
 56        activity.startActivity(intent);
 57        activity.overridePendingTransition(0, 0);
 58    }
 59
 60    public void onInstallReferrerDiscovered(final Uri referrer) {
 61        Log.d(Config.LOGTAG, "welcome activity: on install referrer discovered " + referrer);
 62        if ("xmpp".equalsIgnoreCase(referrer.getScheme())) {
 63            final XmppUri xmppUri = new XmppUri(referrer);
 64            runOnUiThread(() -> processXmppUri(xmppUri));
 65        } else {
 66            Log.i(Config.LOGTAG, "install referrer was not an XMPP uri");
 67        }
 68    }
 69
 70    private void processXmppUri(final XmppUri xmppUri) {
 71        if (!xmppUri.isValidJid()) {
 72            return;
 73        }
 74        final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH);
 75        final Jid jid = xmppUri.getJid();
 76        final Intent intent;
 77        if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
 78            intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth);
 79        } else if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) {
 80            intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth);
 81            intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
 82        } else {
 83            intent = null;
 84        }
 85        if (intent != null) {
 86            startActivity(intent);
 87            finish();
 88            return;
 89        }
 90        this.inviteUri = xmppUri;
 91    }
 92
 93    @Override
 94    protected synchronized void refreshUiReal() {
 95        if (onboardingAccount == null) return;
 96        if (onboardingAccount.getStatus() != Account.State.ONLINE) return;
 97
 98        Intent intent = new Intent(this, StartConversationActivity.class);
 99        intent.putExtra("init", true);
100        intent.putExtra(EXTRA_ACCOUNT, onboardingAccount.getJid().asBareJid().toString());
101        onboardingAccount = null;
102        startActivity(intent);
103        finish();
104    }
105
106    @Override
107    public void onAccountUpdate() {
108        refreshUi();
109    }
110
111    @Override
112    protected void onBackendConnected() {
113        if (xmppConnectionService.isOnboarding()) {
114            binding.registerNewAccount.setText("Working...");
115            binding.registerNewAccount.setEnabled(false);
116            binding.slideshowPager.setCurrentItem(4);
117            onboardingAccount = xmppConnectionService.getAccounts().get(0);
118            xmppConnectionService.reconnectAccountInBackground(onboardingAccount);
119        }
120    }
121
122    @Override
123    public void onStart() {
124        super.onStart();
125        new InstallReferrerUtils(this);
126    }
127
128    @Override
129    public void onStop() {
130        super.onStop();
131    }
132
133    @Override
134    public void onNewIntent(Intent intent) {
135        if (intent != null) {
136            setIntent(intent);
137        }
138    }
139
140    @Override
141    protected void onCreate(final Bundle savedInstanceState) {
142        if (getResources().getBoolean(R.bool.portrait_only)) {
143            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
144        }
145        super.onCreate(savedInstanceState);
146        getPreferences().edit().putStringSet("pstn_gateways", new HashSet<>()).apply();
147        binding = DataBindingUtil.setContentView(this, R.layout.activity_welcome);
148        Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
149        binding.slideshowPager.setAdapter(new WelcomePagerAdapter(binding.slideshowPager));
150        binding.dotsIndicator.setViewPager(binding.slideshowPager);
151        binding.slideshowPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
152            public void onPageScrollStateChanged(int state) { }
153            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { }
154
155            public void onPageSelected(int position) {
156                binding.buttonNext.setVisibility(position > 1 ? View.GONE : View.VISIBLE);
157                binding.buttonPrivacy.setVisibility(position < 2 ? View.GONE : View.VISIBLE);
158            }
159        });
160        binding.buttonNext.setOnClickListener((v) ->
161            binding.slideshowPager.setCurrentItem(binding.slideshowPager.getCurrentItem() + 1)
162        );
163        binding.buttonPrivacy.setOnClickListener(
164                (v) -> {
165                    try {
166                        startActivity(
167                                new Intent(
168                                        Intent.ACTION_VIEW,
169                                        Uri.parse("https://cheogram.com/android-privacy.html")));
170                    } catch (final ActivityNotFoundException e) {
171                        Toast.makeText(this, R.string.no_application_found_to_open_link, Toast.LENGTH_SHORT)
172                                .show();
173                    }
174                });
175        configureActionBar(getSupportActionBar(), false);
176        binding.registerNewAccount.setOnClickListener(v -> {
177            if (hasInviteUri()) {
178                final Intent intent = new Intent(this, MagicCreateActivity.class);
179                addInviteUri(intent);
180                startActivity(intent);
181            } else {
182                binding.registerNewAccount.setText("Working...");
183                binding.registerNewAccount.setEnabled(false);
184                onboardingAccount = new Account(Jid.ofLocalAndDomain(UUID.randomUUID().toString(), Config.ONBOARDING_DOMAIN.toString()), CryptoHelper.createPassword(new SecureRandom()));
185                onboardingAccount.setOption(Account.OPTION_REGISTER, true);
186                onboardingAccount.setOption(Account.OPTION_FIXED_USERNAME, true);
187                xmppConnectionService.createAccount(onboardingAccount);
188            }
189        });
190        binding.useExisting.setOnClickListener(v -> {
191            final List<Account> accounts = xmppConnectionService.getAccounts();
192            Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class);
193            intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, false);
194            if (accounts.size() == 1) {
195                intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
196                intent.putExtra("init", true);
197            } else if (accounts.size() >= 1) {
198                intent = new Intent(WelcomeActivity.this, ManageAccountActivity.class);
199            }
200            addInviteUri(intent);
201            startActivity(intent);
202        });
203        binding.useSnikket.setOnClickListener(v -> {
204            final List<Account> accounts = xmppConnectionService.getAccounts();
205            Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class);
206            intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, false);
207            intent.putExtra("snikket", true);
208            if (accounts.size() == 1) {
209                intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
210                intent.putExtra("init", true);
211            } else if (accounts.size() >= 1) {
212                intent = new Intent(WelcomeActivity.this, ManageAccountActivity.class);
213            }
214            addInviteUri(intent);
215            startActivity(intent);
216        });
217
218        binding.useBackup.setOnClickListener(v -> {
219            if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
220                startActivity(new Intent(this, ImportBackupActivity.class));
221            }
222        });
223    }
224
225    @Override
226    public boolean onCreateOptionsMenu(Menu menu) {
227        getMenuInflater().inflate(R.menu.welcome_menu, menu);
228        final MenuItem scan = menu.findItem(R.id.action_scan_qr_code);
229        scan.setVisible(Compatibility.hasFeatureCamera(this));
230        return super.onCreateOptionsMenu(menu);
231    }
232
233
234
235    @Override
236    public boolean onOptionsItemSelected(MenuItem item) {
237        switch (item.getItemId()) {
238            case R.id.action_import_backup:
239                if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
240                    startActivity(new Intent(this, ImportBackupActivity.class));
241                }
242                break;
243            case R.id.action_scan_qr_code:
244                UriHandlerActivity.scan(this, true);
245                break;
246            case R.id.action_add_account_with_cert:
247                addAccountFromKey();
248                break;
249        }
250        return super.onOptionsItemSelected(item);
251    }
252
253    private void addAccountFromKey() {
254        try {
255            KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
256        } catch (ActivityNotFoundException e) {
257            Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
258        }
259    }
260
261    @Override
262    public void alias(final String alias) {
263        if (alias != null) {
264            xmppConnectionService.createAccountFromKey(alias, this);
265        }
266    }
267
268    @Override
269    public void onAccountCreated(final Account account) {
270        final Intent intent = new Intent(this, EditAccountActivity.class);
271        intent.putExtra("jid", account.getJid().asBareJid().toString());
272        intent.putExtra("init", true);
273        addInviteUri(intent);
274        startActivity(intent);
275    }
276
277    @Override
278    public void informUser(final int r) {
279        runOnUiThread(() -> Toast.makeText(this, r, Toast.LENGTH_LONG).show());
280    }
281
282    @Override
283    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
284        UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
285        if (grantResults.length > 0) {
286            if (allGranted(grantResults)) {
287                switch (requestCode) {
288                    case REQUEST_IMPORT_BACKUP:
289                        startActivity(new Intent(this, ImportBackupActivity.class));
290                        break;
291                }
292            } else if (Arrays.asList(permissions).contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
293                Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
294            }
295        }
296        if (writeGranted(grantResults, permissions)) {
297            if (xmppConnectionService != null) {
298                xmppConnectionService.restartFileObserver();
299            }
300        }
301    }
302
303    protected boolean hasInviteUri() {
304        final Intent from = getIntent();
305        if (from != null && from.hasExtra(StartConversationActivity.EXTRA_INVITE_URI)) return true;
306        return this.inviteUri != null;
307    }
308
309    public void addInviteUri(Intent to) {
310        final Intent from = getIntent();
311        if (from != null && from.hasExtra(StartConversationActivity.EXTRA_INVITE_URI)) {
312            final String invite = from.getStringExtra(StartConversationActivity.EXTRA_INVITE_URI);
313            to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, invite);
314        } else if (this.inviteUri != null) {
315            Log.d(Config.LOGTAG, "injecting referrer uri into on-boarding flow");
316            to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, this.inviteUri.toString());
317        }
318    }
319
320    class WelcomePagerAdapter extends PagerAdapter {
321        protected View[] pages;
322
323        public WelcomePagerAdapter(ViewPager p) {
324            super();
325            pages = new View[]{ p.getChildAt(0), p.getChildAt(1), p.getChildAt(2) };
326            for (View v : pages) {
327                p.removeView(v);
328            }
329        }
330
331        @Override
332        public int getCount() {
333            return 3;
334        }
335
336        @NonNull
337        @Override
338        public Object instantiateItem(@NonNull ViewGroup container, int position) {
339            container.addView(pages[position]);
340            return pages[position];
341        }
342
343        @Override
344        public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
345            return view == o;
346        }
347
348        @Override
349        public void destroyItem(@NonNull ViewGroup container, int position, Object o) {
350            container.removeView(pages[position]);
351        }
352    }
353}