diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 232ff7474..9f8e4ba29 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -36,7 +36,7 @@ import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.hotspot.AbstractTabsFragment; -import org.briarproject.briar.android.hotspot.HotspotHelpFragment; +import org.briarproject.briar.android.hotspot.FallbackFragment; import org.briarproject.briar.android.hotspot.HotspotIntroFragment; import org.briarproject.briar.android.hotspot.ManualHotspotFragment; import org.briarproject.briar.android.hotspot.QrHotspotFragment; @@ -224,5 +224,5 @@ public interface AndroidComponent void inject(ManualHotspotFragment manualHotspotFragment); - void inject(HotspotHelpFragment hotspotHelpFragment); + void inject(FallbackFragment fallbackFragment); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/fragment/ErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ErrorFragment.java index 201e313ec..c6e1d85f8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/fragment/ErrorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/fragment/ErrorFragment.java @@ -40,8 +40,7 @@ public class ErrorFragment extends BaseFragment { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args == null) throw new AssertionError(); + Bundle args = requireArguments(); errorMessage = args.getString(ERROR_MSG); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/FallbackFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/FallbackFragment.java new file mode 100644 index 000000000..7b8d3702d --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/FallbackFragment.java @@ -0,0 +1,130 @@ +package org.briarproject.briar.android.hotspot; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.fragment.BaseFragment; + +import java.util.List; + +import javax.inject.Inject; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; + +import static android.content.Intent.ACTION_SEND; +import static android.content.Intent.EXTRA_STREAM; +import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; +import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; +import static android.os.Build.VERSION.SDK_INT; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static androidx.activity.result.contract.ActivityResultContracts.CreateDocument; +import static androidx.transition.TransitionManager.beginDelayedTransition; +import static org.briarproject.briar.android.AppModule.getAndroidComponent; +import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName; + + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class FallbackFragment extends BaseFragment { + + public static final String TAG = FallbackFragment.class.getName(); + + @Inject + ViewModelProvider.Factory viewModelFactory; + + private HotspotViewModel viewModel; + private final ActivityResultLauncher launcher = + registerForActivityResult(new CreateDocument(), + this::onDocumentCreated); + private Button fallbackButton; + private ProgressBar progressBar; + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + FragmentActivity activity = requireActivity(); + getAndroidComponent(activity).inject(this); + viewModel = new ViewModelProvider(activity, viewModelFactory) + .get(HotspotViewModel.class); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater + .inflate(R.layout.fragment_hotspot_save_apk, container, false); + } + + @Override + public void onViewCreated(View v, @Nullable Bundle savedInstanceState) { + super.onViewCreated(v, savedInstanceState); + + fallbackButton = v.findViewById(R.id.fallbackButton); + progressBar = v.findViewById(R.id.progressBar); + fallbackButton.setOnClickListener(view -> { + beginDelayedTransition((ViewGroup) v); + fallbackButton.setVisibility(INVISIBLE); + progressBar.setVisibility(VISIBLE); + + if (SDK_INT >= 19) launcher.launch(getApkFileName()); + else viewModel.exportApk(); + }); + viewModel.getSavedApkToUri() + .observeEvent(this, uri -> shareUri(this, uri)); + } + + private void onDocumentCreated(@Nullable Uri uri) { + showButton(); + if (uri != null) viewModel.exportApk(uri); + } + + private void showButton() { + beginDelayedTransition((ViewGroup) requireView()); + fallbackButton.setVisibility(VISIBLE); + progressBar.setVisibility(INVISIBLE); + } + + static void shareUri(Fragment fragment, Uri uri) { + Intent i = new Intent(ACTION_SEND); + i.putExtra(EXTRA_STREAM, uri); + i.setType("*/*"); // gives us all sharing options + i.addFlags(FLAG_GRANT_READ_URI_PERMISSION); + Context ctx = fragment.requireContext(); + if (SDK_INT <= 19) { + // Workaround for Android bug: + // ctx.grantUriPermission also needed for Android 4 + List resInfoList = ctx.getPackageManager() + .queryIntentActivities(i, MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + ctx.grantUriPermission(packageName, uri, + FLAG_GRANT_READ_URI_PERMISSION); + } + } + fragment.startActivity(Intent.createChooser(i, null)); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java index 78d10ef14..76c43c5d7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotActivity.java @@ -61,14 +61,8 @@ public class HotspotActivity extends BriarActivity showFragment(fm, new HotspotFragment(), tag); } } else if (hotspotState instanceof HotspotError) { - // TODO: handle rotation gracefully. If we just use - // fm.findFragmentByTag(HotspotErrorFragment.TAG) == null) - // we might hide multiple errors. Maybe we could update the - // error message of the existing fragment in that case - String error = ((HotspotError) hotspotState).getError(); - Fragment f = HotspotErrorFragment.newInstance(error); - showFragment(getSupportFragmentManager(), f, - HotspotErrorFragment.TAG); + HotspotError error = ((HotspotError) hotspotState); + showErrorFragment(error.getError()); } }); @@ -80,6 +74,15 @@ public class HotspotActivity extends BriarActivity } } + private void showErrorFragment(String error) { + FragmentManager fm = getSupportFragmentManager(); + String tag = HotspotErrorFragment.TAG; + if (fm.findFragmentByTag(tag) == null) { + Fragment f = HotspotErrorFragment.newInstance(error); + showFragment(fm, f, tag, false); + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotErrorFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotErrorFragment.java index 956dc76ab..f765c1630 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotErrorFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotErrorFragment.java @@ -12,7 +12,11 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.fragment.BaseFragment; +import javax.inject.Inject; + import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.ViewModelProvider; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; @@ -23,6 +27,9 @@ public class HotspotErrorFragment extends BaseFragment { public static final String TAG = HotspotErrorFragment.class.getName(); + @Inject + ViewModelProvider.Factory viewModelFactory; + private static final String ERROR_MSG = "errorMessage"; public static HotspotErrorFragment newInstance(String message) { @@ -44,8 +51,7 @@ public class HotspotErrorFragment extends BaseFragment { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args == null) throw new AssertionError(); + Bundle args = requireArguments(); errorMessage = args.getString(ERROR_MSG); } @@ -54,8 +60,13 @@ public class HotspotErrorFragment extends BaseFragment { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View v = inflater + return inflater .inflate(R.layout.fragment_hotspot_error, container, false); + } + + @Override + public void onViewCreated(View v, @Nullable Bundle savedInstanceState) { + super.onViewCreated(v, savedInstanceState); TextView msg = v.findViewById(R.id.errorMessageDetail); msg.setText(errorMessage); @@ -63,10 +74,9 @@ public class HotspotErrorFragment extends BaseFragment { feedbackButton.setOnClickListener( button -> triggerFeedback(requireContext())); - Button fallbackButton = v.findViewById(R.id.fallbackButton); - // TODO: export apk - - return v; + FallbackFragment fallbackFragment = new FallbackFragment(); + FragmentTransaction ta = getChildFragmentManager().beginTransaction(); + ta.replace(R.id.fallbackPlaceholder, fallbackFragment).commit(); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotHelpFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotHelpFragment.java index b0947f1da..6d8d5507f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotHelpFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotHelpFragment.java @@ -1,42 +1,21 @@ package org.briarproject.briar.android.hotspot; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.net.Uri; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ProgressBar; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; -import java.util.List; - import javax.inject.Inject; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts.CreateDocument; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; -import static android.content.Intent.ACTION_SEND; -import static android.content.Intent.EXTRA_STREAM; -import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; -import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; -import static android.os.Build.VERSION.SDK_INT; -import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; -import static androidx.transition.TransitionManager.beginDelayedTransition; -import static org.briarproject.briar.android.AppModule.getAndroidComponent; -import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName; - @MethodsNotNullByDefault @ParametersNotNullByDefault public class HotspotHelpFragment extends Fragment { @@ -46,22 +25,6 @@ public class HotspotHelpFragment extends Fragment { @Inject ViewModelProvider.Factory viewModelFactory; - private HotspotViewModel viewModel; - private final ActivityResultLauncher launcher = - registerForActivityResult(new CreateDocument(), - this::onDocumentCreated); - private Button button; - private ProgressBar progressBar; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - FragmentActivity activity = requireActivity(); - getAndroidComponent(activity).inject(this); - viewModel = new ViewModelProvider(activity, viewModelFactory) - .get(HotspotViewModel.class); - } - @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @@ -73,48 +36,10 @@ public class HotspotHelpFragment extends Fragment { @Override public void onViewCreated(View v, @Nullable Bundle savedInstanceState) { super.onViewCreated(v, savedInstanceState); - button = v.findViewById(R.id.fallbackButton); - progressBar = v.findViewById(R.id.progressBar); - button.setOnClickListener(view -> { - beginDelayedTransition((ViewGroup) v); - button.setVisibility(INVISIBLE); - progressBar.setVisibility(VISIBLE); - if (SDK_INT >= 19) launcher.launch(getApkFileName()); - else viewModel.exportApk(); - }); - viewModel.getSavedApkToUri().observeEvent(this, this::shareUri); - } - - private void onDocumentCreated(@Nullable Uri uri) { - showButton(); - if (uri != null) viewModel.exportApk(uri); - } - - private void shareUri(Uri uri) { - Intent i = new Intent(ACTION_SEND); - i.putExtra(EXTRA_STREAM, uri); - i.setType("*/*"); // gives us all sharing options - i.addFlags(FLAG_GRANT_READ_URI_PERMISSION); - Context ctx = requireContext(); - if (SDK_INT <= 19) { - // Workaround for Android bug: - // ctx.grantUriPermission also needed for Android 4 - List resInfoList = ctx.getPackageManager() - .queryIntentActivities(i, MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - ctx.grantUriPermission(packageName, uri, - FLAG_GRANT_READ_URI_PERMISSION); - } - } - startActivity(Intent.createChooser(i, null)); - } - - private void showButton() { - beginDelayedTransition((ViewGroup) requireView()); - button.setVisibility(VISIBLE); - progressBar.setVisibility(INVISIBLE); + FallbackFragment fallbackFragment = new FallbackFragment(); + FragmentTransaction ta = getChildFragmentManager().beginTransaction(); + ta.replace(R.id.fallbackPlaceholder, fallbackFragment).commit(); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java index 3b9e65209..b98edb023 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/hotspot/HotspotViewModel.java @@ -145,7 +145,7 @@ class HotspotViewModel extends DbViewModel @Override public void onHotspotError(String error) { LOG.warning("Hotspot error: " + error); - state.setValue(new HotspotError(error)); + state.postValue(new HotspotError(error)); ioExecutor.execute(webServerManager::stopWebServer); notificationManager.clearHotspotNotification(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index ab03767f3..bd0911d48 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -61,6 +61,7 @@ import androidx.core.util.Consumer; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; @@ -143,13 +144,18 @@ public class UiUtils { public static void showFragment(FragmentManager fm, Fragment f, @Nullable String tag) { - fm.beginTransaction() + showFragment(fm, f, tag, true); + } + + public static void showFragment(FragmentManager fm, Fragment f, + @Nullable String tag, boolean addToBackStack) { + FragmentTransaction ta = fm.beginTransaction() .setCustomAnimations(R.anim.step_next_in, R.anim.step_previous_out, R.anim.step_previous_in, R.anim.step_next_out) - .replace(R.id.fragmentContainer, f, tag) - .addToBackStack(tag) - .commit(); + .replace(R.id.fragmentContainer, f, tag); + if (addToBackStack) ta.addToBackStack(tag); + ta.commit(); } public static String getContactDisplayName(Author author, diff --git a/briar-android/src/main/res/layout/fragment_error.xml b/briar-android/src/main/res/layout/fragment_error.xml index 347d24b9d..ecb2bfe88 100644 --- a/briar-android/src/main/res/layout/fragment_error.xml +++ b/briar-android/src/main/res/layout/fragment_error.xml @@ -9,11 +9,7 @@ android:id="@+id/errorIcon" android:layout_width="128dp" android:layout_height="128dp" - android:layout_marginStart="8dp" - android:layout_marginLeft="8dp" - android:layout_marginTop="8dp" - android:layout_marginEnd="8dp" - android:layout_marginRight="8dp" + android:layout_margin="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -25,11 +21,7 @@ android:id="@+id/errorTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginLeft="8dp" - android:layout_marginTop="8dp" - android:layout_marginEnd="8dp" - android:layout_marginRight="8dp" + android:layout_margin="8dp" android:text="@string/sorry" android:textSize="@dimen/text_size_xlarge" app:layout_constraintEnd_toEndOf="parent" diff --git a/briar-android/src/main/res/layout/fragment_hotspot_error.xml b/briar-android/src/main/res/layout/fragment_hotspot_error.xml index d47d74c48..7534159ed 100644 --- a/briar-android/src/main/res/layout/fragment_hotspot_error.xml +++ b/briar-android/src/main/res/layout/fragment_hotspot_error.xml @@ -7,63 +7,44 @@ + android:layout_height="wrap_content" + android:padding="16dp"> - - + app:layout_constraintStart_toEndOf="@+id/errorIcon" + app:layout_constraintTop_toTopOf="parent" /> - -