mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
17 Commits
detect-gli
...
1479-show-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0703548e97 | ||
|
|
c5d2661c1d | ||
|
|
629cff20a3 | ||
|
|
6cfb70db95 | ||
|
|
737ecfb620 | ||
|
|
5a424b178e | ||
|
|
59f4e7c34a | ||
|
|
2480824d69 | ||
|
|
a6c2000d81 | ||
|
|
a38a3139d9 | ||
|
|
4c8adaa02b | ||
|
|
a546fecc01 | ||
|
|
3e7e37f5f6 | ||
|
|
d095ba0b15 | ||
|
|
7fab97d26c | ||
|
|
6b61725c6a | ||
|
|
e4a66615a7 |
@@ -95,6 +95,7 @@ dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation project(':bramble-android')
|
||||
|
||||
implementation 'androidx.fragment:fragment:1.3.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
|
||||
@@ -46,6 +46,7 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.Process.myPid;
|
||||
import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -59,6 +60,7 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGO
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK;
|
||||
import static org.briarproject.briar.api.android.LockManager.EXTRA_PID;
|
||||
|
||||
public class BriarService extends Service {
|
||||
|
||||
@@ -210,7 +212,12 @@ public class BriarService extends Service {
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (ACTION_LOCK.equals(intent.getAction())) {
|
||||
lockManager.setLocked(true);
|
||||
int pid = intent.getIntExtra(EXTRA_PID, -1);
|
||||
if (pid == myPid()) lockManager.setLocked(true);
|
||||
else if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Tried to lock process " + pid + " but this is " +
|
||||
myPid());
|
||||
}
|
||||
}
|
||||
return START_NOT_STICKY; // Don't restart automatically if killed
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public class DozeFragment extends SetupFragment
|
||||
private DozeView dozeView;
|
||||
private HuaweiView huaweiView;
|
||||
private Button next;
|
||||
private ProgressBar progressBar;
|
||||
private boolean secondAttempt = false;
|
||||
|
||||
public static DozeFragment newInstance() {
|
||||
@@ -58,11 +57,19 @@ public class DozeFragment extends SetupFragment
|
||||
huaweiView = v.findViewById(R.id.huaweiView);
|
||||
huaweiView.setOnCheckedChangedListener(this);
|
||||
next = v.findViewById(R.id.next);
|
||||
progressBar = v.findViewById(R.id.progress);
|
||||
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||
|
||||
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
|
||||
next.setOnClickListener(this);
|
||||
|
||||
viewModel.getIsCreatingAccount()
|
||||
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
|
||||
if (isCreatingAccount) {
|
||||
next.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -104,15 +111,6 @@ public class DozeFragment extends SetupFragment
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
setNextClicked();
|
||||
viewModel.dozeExceptionConfirmed();
|
||||
}
|
||||
|
||||
@Override
|
||||
void setNextClicked() {
|
||||
super.setNextClicked();
|
||||
|
||||
next.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,10 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static android.app.AlarmManager.ELAPSED_REALTIME;
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
import static android.app.PendingIntent.getService;
|
||||
import static android.content.Context.ALARM_SERVICE;
|
||||
import static android.os.Process.myPid;
|
||||
import static android.os.SystemClock.elapsedRealtime;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -75,23 +77,25 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
|
||||
LockManagerImpl(Application app, SettingsManager settingsManager,
|
||||
AndroidNotificationManager notificationManager,
|
||||
@DatabaseExecutor Executor dbExecutor) {
|
||||
this.appContext = app.getApplicationContext();
|
||||
appContext = app.getApplicationContext();
|
||||
this.settingsManager = settingsManager;
|
||||
this.notificationManager = notificationManager;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.alarmManager =
|
||||
alarmManager =
|
||||
(AlarmManager) appContext.getSystemService(ALARM_SERVICE);
|
||||
Intent i =
|
||||
new Intent(ACTION_LOCK, null, appContext, BriarService.class);
|
||||
this.lockIntent = getService(appContext, 0, i, 0);
|
||||
this.timeoutNever = Integer.valueOf(
|
||||
i.putExtra(EXTRA_PID, myPid());
|
||||
// When not using FLAG_UPDATE_CURRENT, the intent might have no extras
|
||||
lockIntent = getService(appContext, 0, i, FLAG_UPDATE_CURRENT);
|
||||
timeoutNever = Integer.parseInt(
|
||||
appContext.getString(R.string.pref_lock_timeout_value_never));
|
||||
this.timeoutDefault = Integer.valueOf(
|
||||
timeoutDefault = Integer.parseInt(
|
||||
appContext.getString(R.string.pref_lock_timeout_value_default));
|
||||
this.timeoutMinutes = timeoutNever;
|
||||
timeoutMinutes = timeoutNever;
|
||||
|
||||
// setting this in the constructor makes #getValue() @NonNull
|
||||
this.lockable.setValue(false);
|
||||
lockable.setValue(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,7 +152,7 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
|
||||
boolean oldValue = lockable.getValue();
|
||||
boolean newValue = hasScreenLock(appContext) && lockableSetting;
|
||||
if (oldValue != newValue) {
|
||||
this.lockable.setValue(newValue);
|
||||
lockable.setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
private TextInputEditText passwordConfirmation;
|
||||
private StrengthMeter strengthMeter;
|
||||
private Button nextButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
public static SetPasswordFragment newInstance() {
|
||||
return new SetPasswordFragment();
|
||||
@@ -64,7 +63,7 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
v.findViewById(R.id.password_confirm_wrapper);
|
||||
passwordConfirmation = v.findViewById(R.id.password_confirm);
|
||||
nextButton = v.findViewById(R.id.next);
|
||||
progressBar = v.findViewById(R.id.progress);
|
||||
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||
|
||||
passwordEntry.addTextChangedListener(this);
|
||||
passwordConfirmation.addTextChangedListener(this);
|
||||
@@ -75,6 +74,17 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
|
||||
}
|
||||
|
||||
viewModel.getIsCreatingAccount()
|
||||
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
|
||||
if (isCreatingAccount) {
|
||||
nextButton.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
// this also avoids the keyboard popping up
|
||||
passwordEntry.setFocusable(false);
|
||||
passwordConfirmation.setFocusable(false);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -116,20 +126,6 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
IBinder token = passwordEntry.getWindowToken();
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
|
||||
setNextClicked();
|
||||
viewModel.setPassword(passwordEntry.getText().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
void setNextClicked() {
|
||||
super.setNextClicked();
|
||||
|
||||
passwordEntry.setFocusable(false);
|
||||
passwordConfirmation.setFocusable(false);
|
||||
if (!viewModel.needToShowDozeFragment()) {
|
||||
nextButton.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
@@ -19,8 +18,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@@ -35,7 +32,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
||||
OnEditorActionListener, OnClickListener {
|
||||
|
||||
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
|
||||
private boolean clicked = false;
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
@@ -48,27 +44,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
||||
.get(SetupViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
|
||||
}
|
||||
if (clicked) {
|
||||
setNextClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(STATE_KEY_CLICKED, clicked);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void setNextClicked() {
|
||||
this.clicked = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.help_action, menu);
|
||||
@@ -114,5 +89,4 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
||||
public void afterTextChanged(Editable editable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
|
||||
@@ -36,6 +38,8 @@ class SetupViewModel extends AndroidViewModel {
|
||||
@Nullable
|
||||
private String authorName, password;
|
||||
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
|
||||
private final MutableLiveData<Boolean> isCreatingAccount =
|
||||
new MutableLiveData<>(false);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
private final Executor ioExecutor;
|
||||
@@ -67,6 +71,10 @@ class SetupViewModel extends AndroidViewModel {
|
||||
return state;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getIsCreatingAccount() {
|
||||
return isCreatingAccount;
|
||||
}
|
||||
|
||||
void setAuthorName(String authorName) {
|
||||
this.authorName = authorName;
|
||||
state.setEvent(SET_PASSWORD);
|
||||
@@ -97,6 +105,7 @@ class SetupViewModel extends AndroidViewModel {
|
||||
private void createAccount() {
|
||||
if (authorName == null) throw new IllegalStateException();
|
||||
if (password == null) throw new IllegalStateException();
|
||||
isCreatingAccount.setValue(true);
|
||||
ioExecutor.execute(() -> {
|
||||
if (accountManager.createAccount(authorName, password)) {
|
||||
LOG.info("Created account");
|
||||
|
||||
@@ -77,7 +77,7 @@ public class UnlockActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_KEYGUARD_UNLOCK) {
|
||||
if (resultCode == RESULT_OK) unlock();
|
||||
|
||||
@@ -34,7 +34,6 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
|
||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
|
||||
|
||||
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -102,7 +101,8 @@ public class ContactListFragment extends BaseFragment
|
||||
.observe(getViewLifecycleOwner(), result -> {
|
||||
result.onError(this::handleException).onSuccess(items -> {
|
||||
adapter.submitList(items);
|
||||
if (requireNonNull(items).size() == 0) list.showData();
|
||||
// TODO remove when BriarRecyclerView was adapted
|
||||
list.showData();
|
||||
});
|
||||
});
|
||||
viewModel.getHasPendingContacts()
|
||||
|
||||
@@ -10,9 +10,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -47,13 +46,11 @@ public abstract class BaseFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
listener.onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
requireActivity().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -79,6 +76,7 @@ public abstract class BaseFragment extends Fragment
|
||||
void handleException(Exception e);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@CallSuper
|
||||
@Override
|
||||
public void runOnUiThreadUnlessDestroyed(Runnable r) {
|
||||
|
||||
@@ -411,6 +411,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
@UiThread
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions,
|
||||
grantResults);
|
||||
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
|
||||
throw new AssertionError();
|
||||
if (gotPermission(CAMERA, permissions, grantResults)) {
|
||||
|
||||
@@ -49,14 +49,12 @@ import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
@@ -67,9 +65,7 @@ import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static androidx.core.view.GravityCompat.START;
|
||||
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
||||
import static androidx.lifecycle.Lifecycle.State.STARTED;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
@@ -146,7 +142,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
|
||||
});
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = setUpCustomToolbar(false);
|
||||
drawerLayout = findViewById(R.id.drawer_layout);
|
||||
navigation = findViewById(R.id.navigation);
|
||||
GridView transportsView = findViewById(R.id.transportsView);
|
||||
@@ -156,11 +152,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
startActivity(new Intent(this, TransportsActivity.class));
|
||||
});
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar actionBar = requireNonNull(getSupportActionBar());
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
|
||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
|
||||
R.string.nav_drawer_open_description,
|
||||
R.string.nav_drawer_close_description) {
|
||||
@@ -184,9 +175,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
|
||||
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
|
||||
showSignOutFragment();
|
||||
} else if (state == null) {
|
||||
startFragment(ContactListFragment.newInstance(),
|
||||
R.id.nav_btn_contacts);
|
||||
}
|
||||
if (state == null) {
|
||||
// do not call this again when there's existing state
|
||||
@@ -276,7 +264,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
drawerLayout.closeDrawer(START);
|
||||
clearBackStack();
|
||||
if (item.getItemId() == R.id.nav_btn_lock) {
|
||||
lockManager.setLocked(true);
|
||||
ActivityCompat.finishAfterTransition(this);
|
||||
@@ -296,8 +283,8 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
|
||||
finish();
|
||||
} else if (fm.getBackStackEntryCount() == 0
|
||||
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||
} else if (fm.getBackStackEntryCount() == 0 &&
|
||||
fm.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||
// don't start fragments in the wrong part of lifecycle (#1904)
|
||||
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
|
||||
LOG.warning("Tried to start contacts fragment in state " +
|
||||
@@ -346,30 +333,12 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
startFragment(fragment);
|
||||
}
|
||||
|
||||
private void startFragment(BaseFragment fragment) {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
startFragment(fragment, false);
|
||||
else startFragment(fragment, true);
|
||||
}
|
||||
|
||||
private void startFragment(BaseFragment fragment,
|
||||
boolean isAddedToBackStack) {
|
||||
FragmentTransaction trans =
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.fade_in,
|
||||
R.anim.fade_out, R.anim.fade_in,
|
||||
R.anim.fade_out)
|
||||
.replace(R.id.fragmentContainer, fragment,
|
||||
fragment.getUniqueTag());
|
||||
if (isAddedToBackStack) {
|
||||
trans.addToBackStack(fragment.getUniqueTag());
|
||||
}
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
private void clearBackStack() {
|
||||
getSupportFragmentManager().popBackStackImmediate(null,
|
||||
POP_BACK_STACK_INCLUSIVE);
|
||||
private void startFragment(BaseFragment f) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out,
|
||||
R.anim.fade_in, R.anim.fade_out)
|
||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,10 +9,13 @@ import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -32,7 +35,7 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private SettingsViewModel settingsViewModel;
|
||||
private SettingsViewModel viewModel;
|
||||
|
||||
private static final String ARG_URI = "uri";
|
||||
private Uri uri;
|
||||
@@ -51,6 +54,9 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
|
||||
public void onAttach(Context ctx) {
|
||||
super.onAttach(ctx);
|
||||
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
|
||||
ViewModelProvider provider =
|
||||
new ViewModelProvider(requireActivity(), viewModelFactory);
|
||||
viewModel = provider.get(SettingsViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,32 +66,34 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
|
||||
uri = Uri.parse(argUri);
|
||||
|
||||
FragmentActivity activity = requireActivity();
|
||||
|
||||
ViewModelProvider provider =
|
||||
new ViewModelProvider(activity, viewModelFactory);
|
||||
settingsViewModel = provider.get(SettingsViewModel.class);
|
||||
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
LayoutInflater inflater = LayoutInflater.from(activity);
|
||||
final View view =
|
||||
inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null);
|
||||
builder.setView(view);
|
||||
|
||||
builder.setTitle(R.string.dialog_confirm_profile_picture_title);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.change,
|
||||
(dialog, id) -> settingsViewModel.setAvatar(uri));
|
||||
|
||||
ImageView imageView = view.findViewById(R.id.image);
|
||||
imageView.setImageURI(uri);
|
||||
|
||||
TextView textViewUserName = view.findViewById(R.id.username);
|
||||
settingsViewModel.getOwnIdentityInfo().observe(activity,
|
||||
us -> textViewUserName.setText(us.getLocalAuthor().getName()));
|
||||
|
||||
return builder.create();
|
||||
GlideApp.with(imageView)
|
||||
.load(uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.error(R.drawable.ic_image_broken)
|
||||
.into(imageView)
|
||||
.waitForLayout();
|
||||
|
||||
// we can't use getViewLifecycleOwner() here
|
||||
// as this fragment technically doesn't have a view
|
||||
viewModel.getOwnIdentityInfo().observe(activity, us ->
|
||||
textViewUserName.setText(us.getLocalAuthor().getName())
|
||||
);
|
||||
|
||||
int theme = R.style.BriarDialogTheme;
|
||||
return new AlertDialog.Builder(activity, theme)
|
||||
.setView(view)
|
||||
.setTitle(R.string.dialog_confirm_profile_picture_title)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.change, (d, id) ->
|
||||
viewModel.setAvatar(uri)
|
||||
)
|
||||
.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class SettingsViewModel extends AndroidViewModel {
|
||||
return ownIdentityInfo;
|
||||
}
|
||||
|
||||
public LiveEvent<Boolean> getSetAvatarFailed() {
|
||||
LiveEvent<Boolean> getSetAvatarFailed() {
|
||||
return setAvatarFailed;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
import static android.content.Context.KEYGUARD_SERVICE;
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.content.Intent.ACTION_GET_CONTENT;
|
||||
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
||||
import static android.content.Intent.CATEGORY_DEFAULT;
|
||||
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
||||
@@ -258,8 +257,9 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static Intent createSelectImageIntent(boolean allowMultiple) {
|
||||
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||
// ACTION_GET_CONTENT returns more content providers than
|
||||
// ACTION_OPEN_DOCUMENT (https://stackoverflow.com/questions/27568139/)
|
||||
Intent intent = new Intent(ACTION_GET_CONTENT);
|
||||
intent.setType("image/*");
|
||||
intent.addCategory(CATEGORY_OPENABLE);
|
||||
if (SDK_INT >= 19)
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
||||
public interface LockManager {
|
||||
|
||||
String ACTION_LOCK = "lock";
|
||||
String EXTRA_PID = "PID";
|
||||
|
||||
/**
|
||||
* Stops the inactivity timer when the user interacts with the app.
|
||||
|
||||
@@ -52,16 +52,17 @@
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/close"
|
||||
android:scaleType="center"
|
||||
app:srcCompat="@drawable/ic_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_close"
|
||||
app:tint="@color/briar_text_tertiary_inverse" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainer"
|
||||
android:name="org.briarproject.briar.android.contact.ContactListFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -5,27 +5,28 @@
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/nav_btn_contacts"
|
||||
android:checked="true"
|
||||
android:icon="@drawable/ic_contacts"
|
||||
android:title="@string/contact_list_button"/>
|
||||
android:title="@string/contact_list_button" />
|
||||
<item
|
||||
android:id="@+id/nav_btn_groups"
|
||||
android:icon="@drawable/ic_group"
|
||||
android:title="@string/groups_button"/>
|
||||
android:title="@string/groups_button" />
|
||||
<item
|
||||
android:id="@+id/nav_btn_forums"
|
||||
android:icon="@drawable/ic_forums_black_24dp"
|
||||
android:title="@string/forums_button"/>
|
||||
android:title="@string/forums_button" />
|
||||
<item
|
||||
android:id="@+id/nav_btn_blogs"
|
||||
android:icon="@drawable/blogs"
|
||||
android:title="@string/blogs_button"/>
|
||||
android:title="@string/blogs_button" />
|
||||
</group>
|
||||
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/nav_btn_settings"
|
||||
android:icon="@drawable/ic_settings_black"
|
||||
android:title="@string/settings_button"/>
|
||||
android:title="@string/settings_button" />
|
||||
<item
|
||||
android:id="@+id/nav_btn_lock"
|
||||
android:icon="@drawable/startup_lock"
|
||||
@@ -35,7 +36,7 @@
|
||||
<item
|
||||
android:id="@+id/nav_btn_signout"
|
||||
android:icon="@drawable/ic_signout"
|
||||
android:title="@string/sign_out_button"/>
|
||||
android:title="@string/sign_out_button" />
|
||||
</group>
|
||||
|
||||
</menu>
|
||||
@@ -456,7 +456,7 @@
|
||||
<!-- Settings Profile Picture -->
|
||||
<string name="change_profile_picture">Tap to change your profile picture</string>
|
||||
<string name="dialog_confirm_profile_picture_title">Change profile picture</string>
|
||||
<string name="dialog_confirm_profile_picture_remark">Only your contacts can see your profile image</string>
|
||||
<string name="dialog_confirm_profile_picture_remark">Only your contacts can see this picture</string>
|
||||
<string name="change_profile_picture_failed_message">We\'re sorry, but something went wrong while updating your profile picture</string>
|
||||
|
||||
<!-- Settings Display -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'androidx.activity:activity-ktx:1.1.0:activity-ktx-1.1.0.aar:1996c36d3d2d62db5020b8ec634b5f854b1a698960c3552e1a00c69221baeabe',
|
||||
'androidx.activity:activity:1.1.0:activity-1.1.0.aar:4f2b35916768032f7d0c20e250e28b29037ed4ce9ebf3da4fcd51bcb0c6067ef',
|
||||
'androidx.activity:activity:1.2.0:activity-1.2.0.aar:ac27a810554e47b2122bce1f338934e77b173a5a9267eb35f134b6d34f931bae',
|
||||
'androidx.annotation:annotation-experimental:1.0.0:annotation-experimental-1.0.0.aar:b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11',
|
||||
'androidx.annotation:annotation:1.1.0:annotation-1.1.0.jar:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
|
||||
'androidx.appcompat:appcompat-resources:1.2.0:appcompat-resources-1.2.0.aar:c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5',
|
||||
@@ -24,29 +24,28 @@ dependencyVerification {
|
||||
'androidx.exifinterface:exifinterface:1.3.1:exifinterface-1.3.1.aar:ef168daa6eb744c8395c22b49afa5235e6099868a0377175b6d5e3cdff8d7ffc',
|
||||
'androidx.fragment:fragment-ktx:1.2.5:fragment-ktx-1.2.5.aar:50f0f3b734f93829eeac7456b7cb13e5430741e555c535911a958ee4a8242bca',
|
||||
'androidx.fragment:fragment-testing:1.2.5:fragment-testing-1.2.5.aar:ef3cc3387115f9187665b283e313b13a2bb8826673380317057e2972351df09c',
|
||||
'androidx.fragment:fragment:1.2.4:fragment-1.2.4.aar:1dc194942574302bf35dae7b81b82273505ec2d38f81d9258ad5c0448daddd82',
|
||||
'androidx.fragment:fragment:1.2.5:fragment-1.2.5.aar:d19e82d142def6c4e136da70bf92f194c0ecc61d14ab4e84567b2ced0920fa93',
|
||||
'androidx.fragment:fragment:1.3.0:fragment-1.3.0.aar:66db3ed2b11bb5e572a079b87cd3fae9bc5c33c373c71b25f1e3eac7607ab526',
|
||||
'androidx.interpolator:interpolator:1.0.0:interpolator-1.0.0.aar:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a',
|
||||
'androidx.legacy:legacy-support-core-utils:1.0.0:legacy-support-core-utils-1.0.0.aar:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7',
|
||||
'androidx.lifecycle:lifecycle-common:2.2.0:lifecycle-common-2.2.0.jar:63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b',
|
||||
'androidx.lifecycle:lifecycle-common:2.3.0:lifecycle-common-2.3.0.jar:15848fb56db32f4c7cdc72b324003183d52a4884d6bf09be708ac7f587d139b5',
|
||||
'androidx.lifecycle:lifecycle-extensions:2.2.0:lifecycle-extensions-2.2.0.aar:648c8de1d10b025d524a2e46ac994fc3f6bf186826c09ec1a62d250bf1b877ae',
|
||||
'androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0:lifecycle-livedata-core-ktx-2.2.0.aar:5951f882e95b7e05ceb9adfca0fa2ebd511d63ea5a00da4eae6c6d0c1903da18',
|
||||
'androidx.lifecycle:lifecycle-livedata-core:2.2.0:lifecycle-livedata-core-2.2.0.aar:556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614',
|
||||
'androidx.lifecycle:lifecycle-livedata-core:2.3.0:lifecycle-livedata-core-2.3.0.aar:89f480888f2bb8eb62d9b7b1eb34be69b59ec84b24a1b0bdbeb49973478c6da3',
|
||||
'androidx.lifecycle:lifecycle-livedata:2.2.0:lifecycle-livedata-2.2.0.aar:d83af94860aa9f64cbdc51f40796a7cf55b116f0e6efd752e845c0104c8b16f6',
|
||||
'androidx.lifecycle:lifecycle-process:2.2.0:lifecycle-process-2.2.0.aar:3a977e7778fc8418742d388409daaba7ea8fea8823d21ffb96e4c4236f715070',
|
||||
'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0:lifecycle-runtime-ktx-2.2.0.aar:c29fc87694e6ce116b61207221e53ed285862a6628055790b0bcf9ce45d8cc68',
|
||||
'androidx.lifecycle:lifecycle-runtime:2.2.0:lifecycle-runtime-2.2.0.aar:2f866c07a1f33a8c9bb69a9545d4f20b4f0628cd0a155432386d7cb081e1e0bc',
|
||||
'androidx.lifecycle:lifecycle-runtime:2.3.0:lifecycle-runtime-2.3.0.aar:94f528fd5fb123f75b6e65d07a6ef5cd6c0e69ac604d106aaa12705282456234',
|
||||
'androidx.lifecycle:lifecycle-service:2.2.0:lifecycle-service-2.2.0.aar:ca2801ffc069555afed8eddd2292130f436956452bc8bbad30fb56f8e4e382a0',
|
||||
'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0:lifecycle-viewmodel-ktx-2.2.0.aar:f791001f2211947e56ad3d96d12c9ae93fc5589b88f08603f69a2265c9a7d702',
|
||||
'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0:lifecycle-viewmodel-savedstate-2.2.0.aar:3ce866fb822b20fe2f188f974992869a0a6233fe40acbefcff090d6def5e7f33',
|
||||
'androidx.lifecycle:lifecycle-viewmodel:2.2.0:lifecycle-viewmodel-2.2.0.aar:967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27',
|
||||
'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0:lifecycle-viewmodel-savedstate-2.3.0.aar:49f9532b5104cc1ee64900ed4f696d031d807fba726e0d5d6a52459e8fba4a1d',
|
||||
'androidx.lifecycle:lifecycle-viewmodel:2.3.0:lifecycle-viewmodel-2.3.0.aar:cea8f26fa232037922b69af9cd1bde2df1211acc8b75253e425b7150a5fca59d',
|
||||
'androidx.loader:loader:1.0.0:loader-1.0.0.aar:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025',
|
||||
'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
|
||||
'androidx.preference:preference:1.1.1:preference-1.1.1.aar:317dcbc38242aea2f6262c06d51b8a22827e98959967edd40f82600a15cb4bff',
|
||||
'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
|
||||
'androidx.recyclerview:recyclerview-selection:1.1.0-rc03:recyclerview-selection-1.1.0-rc03.aar:a548a0771c2c8ca8cf98f1f755b0eef4fac73d1697e6eeb1a6383f557e0eba13',
|
||||
'androidx.recyclerview:recyclerview:1.1.0:recyclerview-1.1.0.aar:f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f',
|
||||
'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
|
||||
'androidx.savedstate:savedstate:1.1.0:savedstate-1.1.0.aar:d60bbe44c2c08083a17c5dc678a6d6b4d0a2d664858016ab5c049cbea90a63b7',
|
||||
'androidx.test.espresso:espresso-contrib:3.3.0:espresso-contrib-3.3.0.aar:f400cabdc181356acf6b210e4509dcb9649d9e2b6b6e218c60fcfc15e8a756d1',
|
||||
'androidx.test.espresso:espresso-core:3.3.0:espresso-core-3.3.0.aar:23ebf6014645e0c60aec7d1ed924d4d4c848ae8c3673b7d8d06b2ec6a56cafee',
|
||||
'androidx.test.espresso:espresso-idling-resource:3.3.0:espresso-idling-resource-3.3.0.aar:29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6',
|
||||
@@ -57,6 +56,7 @@ dependencyVerification {
|
||||
'androidx.test:monitor:1.3.0:monitor-1.3.0.aar:f73a31306a783e63150c60c49e140dc38da39a1b7947690f4b73387b5ebad77e',
|
||||
'androidx.test:rules:1.3.0:rules-1.3.0.aar:c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a',
|
||||
'androidx.test:runner:1.3.0:runner-1.3.0.aar:61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b',
|
||||
'androidx.tracing:tracing:1.0.0:tracing-1.0.0.aar:07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612',
|
||||
'androidx.transition:transition:1.2.0:transition-1.2.0.aar:a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e',
|
||||
'androidx.vectordrawable:vectordrawable-animated:1.1.0:vectordrawable-animated-1.1.0.aar:76da2c502371d9c38054df5e2b248d00da87809ed058f3363eae87ce5e2403f8',
|
||||
'androidx.vectordrawable:vectordrawable:1.1.0:vectordrawable-1.1.0.aar:46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26',
|
||||
|
||||
@@ -105,7 +105,7 @@ The link and the alias should be posted as a JSON object:
|
||||
}
|
||||
```
|
||||
|
||||
This starts the process of adding the contact.
|
||||
Adding a pending contact starts the process of adding the contact.
|
||||
Until it is completed, a pending contact is returned as JSON:
|
||||
|
||||
```json
|
||||
@@ -116,6 +116,71 @@ Until it is completed, a pending contact is returned as JSON:
|
||||
}
|
||||
```
|
||||
|
||||
Possible errors when adding a pending contact are:
|
||||
|
||||
#### 400: Pending contact's link is invalid
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "INVALID_LINK"
|
||||
}
|
||||
```
|
||||
|
||||
#### 400: Pending contact's handshake public key is invalid
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "INVALID_PUBLIC_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
#### 403: A contact with the same handshake public key already exists
|
||||
|
||||
This error may be caused by someone attacking the user with the goal
|
||||
of discovering the contacts of the user.
|
||||
|
||||
In the Android client, upon encountering this issue a message dialog
|
||||
is shown that asks whether the contact and the just added pending contact
|
||||
are the same person. If that's the case, a message is shown that the
|
||||
contact already exists and the pending contact isn't added.
|
||||
If that's not the case and they are two different persons, the Android
|
||||
client
|
||||
[shows the following message](https://code.briarproject.org/briar/briar/-/blob/beta-1.2.14/briar-android/src/main/res/values/strings.xml#L271)
|
||||
when this happens:
|
||||
> [Alice] and [Bob] sent you the same link.
|
||||
>
|
||||
> One of them may be trying to discover who your contacts are.
|
||||
>
|
||||
> Don't tell them you received the same link from someone else.
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "CONTACT_EXISTS",
|
||||
"remoteAuthorName": "Bob"
|
||||
}
|
||||
```
|
||||
|
||||
#### 403: A pending contact with the same handshake public key already exists
|
||||
|
||||
This error, too, may be caused by someone attacking the user with the goal
|
||||
of discovering the contacts of the user.
|
||||
|
||||
Just like above, upon encountering this issue a message dialog is shown in
|
||||
the Android client that asks whether the contact and the just added pending
|
||||
contact are the same person. If that's the case, the pending contact gets
|
||||
updated. If that's not the case and they are two different persons, the
|
||||
Android client shows the same message as above, warning the user about the
|
||||
possible attack.
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "PENDING_EXISTS",
|
||||
"pendingContactId": "jsTgWcsEQ2g9rnomeK1g/hmO8M1Ix6ZIGWAjgBtlS9U=",
|
||||
"pendingContactAlias": "Alice"
|
||||
}
|
||||
```
|
||||
-----------
|
||||
|
||||
Before users can send messages to contacts, they become pending contacts.
|
||||
In this state Briar still needs to do some work in the background (e.g.
|
||||
spinning up a dedicated hidden service and letting the contact connect to it).
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.briarproject.briar.headless.contact
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.javalin.http.BadRequestResponse
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.http.ForbiddenResponse
|
||||
import io.javalin.http.HttpResponseException
|
||||
import io.javalin.http.NotFoundResponse
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry
|
||||
import org.briarproject.bramble.api.contact.ContactManager
|
||||
@@ -12,8 +14,10 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent
|
||||
import org.briarproject.bramble.api.db.ContactExistsException
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException
|
||||
import org.briarproject.bramble.api.db.NoSuchPendingContactException
|
||||
import org.briarproject.bramble.api.db.PendingContactExistsException
|
||||
import org.briarproject.bramble.api.event.Event
|
||||
import org.briarproject.bramble.api.event.EventListener
|
||||
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
|
||||
@@ -25,8 +29,11 @@ import org.briarproject.briar.headless.event.WebSocketController
|
||||
import org.briarproject.briar.headless.getContactIdFromPathParam
|
||||
import org.briarproject.briar.headless.getFromJson
|
||||
import org.briarproject.briar.headless.json.JsonDict
|
||||
import org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400
|
||||
import org.eclipse.jetty.http.HttpStatus.FORBIDDEN_403
|
||||
import org.spongycastle.util.encoders.Base64
|
||||
import org.spongycastle.util.encoders.DecoderException
|
||||
import java.security.GeneralSecurityException
|
||||
import javax.annotation.concurrent.Immutable
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -91,9 +98,32 @@ constructor(
|
||||
override fun addPendingContact(ctx: Context): Context {
|
||||
val link = ctx.getFromJson(objectMapper, "link")
|
||||
val alias = ctx.getFromJson(objectMapper, "alias")
|
||||
if (!LINK_REGEX.matcher(link).find()) throw BadRequestResponse("Invalid Link")
|
||||
if (!LINK_REGEX.matcher(link).find()) {
|
||||
ctx.status(BAD_REQUEST_400)
|
||||
val details = mapOf("error" to "INVALID_LINK")
|
||||
return ctx.json(details)
|
||||
}
|
||||
checkAliasLength(alias)
|
||||
val pendingContact = contactManager.addPendingContact(link, alias)
|
||||
val pendingContact = try {
|
||||
contactManager.addPendingContact(link, alias)
|
||||
} catch (e: GeneralSecurityException) {
|
||||
ctx.status(BAD_REQUEST_400)
|
||||
val details = mapOf("error" to "INVALID_PUBLIC_KEY")
|
||||
return ctx.json(details)
|
||||
} catch (e: ContactExistsException) {
|
||||
ctx.status(FORBIDDEN_403)
|
||||
val details =
|
||||
mapOf("error" to "CONTACT_EXISTS", "remoteAuthorName" to e.remoteAuthor.name)
|
||||
return ctx.json(details)
|
||||
} catch (e: PendingContactExistsException) {
|
||||
ctx.status(FORBIDDEN_403)
|
||||
val details = mapOf(
|
||||
"error" to "PENDING_EXISTS",
|
||||
"pendingContactId" to e.pendingContact.id.bytes,
|
||||
"pendingContactAlias" to e.pendingContact.alias
|
||||
)
|
||||
return ctx.json(details)
|
||||
}
|
||||
return ctx.json(pendingContact.output())
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,49 @@ class ContactControllerIntegrationTest: IntegrationTest() {
|
||||
assertEquals(401, response.statusCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `adding a pending contact with invalid link`() {
|
||||
val alias = "AliasFoo"
|
||||
val json = """{
|
||||
"link": "briar://invalid",
|
||||
"alias": "$alias"
|
||||
}"""
|
||||
val response = post("$url/contacts/add/pending", json)
|
||||
assertEquals(400, response.statusCode)
|
||||
assertEquals("INVALID_LINK", response.jsonObject.getString("error"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `adding a pending contact with invalid public key`() {
|
||||
val alias = "AliasFoo"
|
||||
val json = """{
|
||||
"link": "briar://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"alias": "$alias"
|
||||
}"""
|
||||
val response = post("$url/contacts/add/pending", json)
|
||||
assertEquals(400, response.statusCode)
|
||||
assertEquals("INVALID_PUBLIC_KEY", response.jsonObject.getString("error"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `adding a pending contact that already exists as pending contact`() {
|
||||
val alias = "AliasFoo"
|
||||
val json = """{
|
||||
"link": "${getRealHandshakeLink(crypto)}",
|
||||
"alias": "$alias"
|
||||
}"""
|
||||
var response = post("$url/contacts/add/pending", json)
|
||||
assertEquals(200, response.statusCode)
|
||||
|
||||
val pendingContactId = response.jsonObject.getString("pendingContactId")
|
||||
|
||||
response = post("$url/contacts/add/pending", json)
|
||||
assertEquals(403, response.statusCode)
|
||||
assertEquals("PENDING_EXISTS", response.jsonObject.getString("error"))
|
||||
assertEquals(pendingContactId, response.jsonObject.getString("pendingContactId"))
|
||||
assertEquals(alias, response.jsonObject.getString("pendingContactAlias"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `removing a pending contact needs authentication token`() {
|
||||
val response = deleteWithWrongToken("$url/contacts/add/pending")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.briarproject.briar.headless.contact
|
||||
|
||||
import io.javalin.http.BadRequestResponse
|
||||
import io.javalin.http.ForbiddenResponse
|
||||
import io.javalin.http.HttpResponseException
|
||||
import io.javalin.http.NotFoundResponse
|
||||
import io.javalin.plugin.json.JavalinJson.toJson
|
||||
import io.mockk.Runs
|
||||
@@ -8,6 +10,7 @@ import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import org.briarproject.bramble.api.Pair
|
||||
import org.briarproject.bramble.api.contact.Contact
|
||||
import org.briarproject.bramble.api.contact.ContactId
|
||||
@@ -18,8 +21,10 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent
|
||||
import org.briarproject.bramble.api.db.ContactExistsException
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException
|
||||
import org.briarproject.bramble.api.db.NoSuchPendingContactException
|
||||
import org.briarproject.bramble.api.db.PendingContactExistsException
|
||||
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
|
||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
|
||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
|
||||
@@ -30,9 +35,11 @@ import org.briarproject.bramble.util.StringUtils.getRandomString
|
||||
import org.briarproject.briar.headless.ControllerTest
|
||||
import org.briarproject.briar.headless.getFromJson
|
||||
import org.briarproject.briar.headless.json.JsonDict
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.security.GeneralSecurityException
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class ContactControllerTest : ControllerTest() {
|
||||
@@ -96,9 +103,10 @@ internal class ContactControllerTest : ControllerTest() {
|
||||
"alias": "$alias"
|
||||
}"""
|
||||
every { ctx.body() } returns body
|
||||
assertThrows(BadRequestResponse::class.java) {
|
||||
controller.addPendingContact(ctx)
|
||||
}
|
||||
every { ctx.status(400) } returns ctx
|
||||
every { ctx.json(mapOf("error" to "INVALID_LINK")) } returns ctx
|
||||
controller.addPendingContact(ctx)
|
||||
verify { ctx.status(400) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,6 +147,84 @@ internal class ContactControllerTest : ControllerTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddPendingContactPublicKeyInvalid() {
|
||||
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
|
||||
val alias = "Alias123"
|
||||
val body = """{
|
||||
"link": "$link",
|
||||
"alias": "$alias"
|
||||
}"""
|
||||
every { ctx.body() } returns body
|
||||
every { ctx.status(400) } returns ctx
|
||||
every {
|
||||
contactManager.addPendingContact(
|
||||
link,
|
||||
alias
|
||||
)
|
||||
} throws GeneralSecurityException()
|
||||
every { ctx.json(mapOf("error" to "INVALID_PUBLIC_KEY")) } returns ctx
|
||||
controller.addPendingContact(ctx)
|
||||
verify { ctx.status(400) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddPendingContactSameContactKey() {
|
||||
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
|
||||
val alias = "Alias123"
|
||||
val body = """{
|
||||
"link": "$link",
|
||||
"alias": "$alias"
|
||||
}"""
|
||||
every { ctx.body() } returns body
|
||||
every { ctx.status(403) } returns ctx
|
||||
every {
|
||||
contactManager.addPendingContact(
|
||||
link,
|
||||
alias
|
||||
)
|
||||
} throws ContactExistsException(null, author)
|
||||
every {
|
||||
ctx.json(
|
||||
mapOf(
|
||||
"error" to "CONTACT_EXISTS",
|
||||
"remoteAuthorName" to author.name
|
||||
)
|
||||
)
|
||||
} returns ctx
|
||||
controller.addPendingContact(ctx)
|
||||
verify { ctx.status(403) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAddPendingContactSamePendingContactKey() {
|
||||
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
|
||||
val alias = "Alias123"
|
||||
val body = """{
|
||||
"link": "$link",
|
||||
"alias": "$alias"
|
||||
}"""
|
||||
every { ctx.body() } returns body
|
||||
every { ctx.status(403) } returns ctx
|
||||
every {
|
||||
contactManager.addPendingContact(
|
||||
link,
|
||||
alias
|
||||
)
|
||||
} throws PendingContactExistsException(pendingContact)
|
||||
every {
|
||||
ctx.json(
|
||||
mapOf(
|
||||
"error" to "PENDING_EXISTS",
|
||||
"pendingContactId" to pendingContact.id.bytes,
|
||||
"pendingContactAlias" to pendingContact.alias
|
||||
)
|
||||
)
|
||||
} returns ctx
|
||||
controller.addPendingContact(ctx)
|
||||
verify { ctx.status(403) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListPendingContacts() {
|
||||
every { contactManager.pendingContacts } returns listOf(
|
||||
|
||||
Reference in New Issue
Block a user