Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import android.os.SystemClock;

import androidx.test.core.app.ActivityScenario;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static org.hamcrest.Matchers.allOf;
import androidx.test.espresso.intent.Intents;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
Expand All @@ -42,6 +44,7 @@
import dev.wander.android.opentagviewer.db.repo.model.UserSettings;
import dev.wander.android.opentagviewer.python.FakeAppleAuthService;
import dev.wander.android.opentagviewer.python.AppDependencies;
import dev.wander.android.opentagviewer.ui.error.ErrorReportActivity;
import dev.wander.android.opentagviewer.python.PythonAuthService.AuthMethodPhone;
import dev.wander.android.opentagviewer.util.android.AppCryptographyUtil;

Expand Down Expand Up @@ -342,6 +345,50 @@ public void appleDecliningIsNotBlamedOnThePasswordOrTheNetwork() {
not(withText(containsString("Grand Slam")))));
}

/**
* <b>A failed sign-in can produce a log without a second machine.</b>
*
* <p>The report page was reachable from the map and the device list and from nowhere else, so
* the one screen whose failures most need diagnosing was the one screen that could not hand a
* user any evidence. Anyone whose sign-in failed had adb on a laptop or had nothing.
*
* <p>Asserted on the cause reaching the page rather than merely on the page opening: the
* point is that the report names the failure that was on screen, and an intent that opened a
* blank report would pass a check that only looked at the component.
*/
@Test
public void afailedSignInOffersTheLogWithoutADeveloperMachine() {
this.apple = FakeAppleAuthService.rejectsTheSignIn("Bad password");
AppDependencies.replaceAuthService(this.apple);

launch();
signIn();

Eventually.check(() -> onView(withId(R.id.login_error_export_logs))
.check(matches(isDisplayed())));
onView(withId(R.id.login_error_export_logs)).perform(click());

Eventually.check(() -> intended(allOf(
hasComponent(ErrorReportActivity.class.getName()),
hasExtra(ErrorReportActivity.EXTRA_CAUSE, containsString("Bad password")))));
}

/**
* And it is only offered once there is something to report.
*
* <p>A button sitting under an empty error box invites a log of a sign-in that has not been
* attempted, which is a report with nothing in it.
*/
@Test
public void thelogButtonIsNotOfferedBeforeAnythingHasFailed() {
this.apple = FakeAppleAuthService.rejectsTheSignIn("Bad password");
AppDependencies.replaceAuthService(this.apple);

launch();

onView(withId(R.id.login_error_export_logs)).check(matches(not(isDisplayed())));
}

/** A rejected code says so, and gives the boxes back rather than stranding them. */
@Test
public void aWrongCodeIsReportedAndTheBoxesComeBack() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import androidx.core.os.LocaleListCompat;
import androidx.databinding.DataBindingUtil;

import dev.wander.android.opentagviewer.ui.error.ErrorReportActivity;
import dev.wander.android.opentagviewer.ui.compat.WindowPaddingUtil;
import androidx.lifecycle.ViewModelProvider;

Expand Down Expand Up @@ -264,6 +265,9 @@ public void handleOnBackPressed() {
this.loginButton = this.findViewById(R.id.login_button_main);
this.twoFactorAuthChoiceBackButton = this.findViewById(R.id.twofactorauthchoice_back_button);

this.findViewById(R.id.login_error_export_logs)
.setOnClickListener(v -> this.openTheReportPageForTheLoginFailure());

this.findViewById(R.id.login_terms_agree_button)
.setOnClickListener(v -> this.onAgreeToTerms());

Expand Down Expand Up @@ -581,17 +585,13 @@ public void onClickLoginButton(View view) {

// undo loading and allow user to try again, basically. Backwards, because
// that is what it is: the step the user just left, handed back to them.
this.hideLoading();
this.showPage(R.id.login_maininfo_container, Direction.BACK);
emailOrPhoneInput.setEnabled(true);
passwordInput.setEnabled(true);
loginButton.setClickable(true);

FrameLayout loginErrorMessage = this.findViewById(R.id.login_error_container);
loginErrorMessage.setVisibility(VISIBLE);

TextView loginErrorText = this.findViewById(R.id.login_error_message_text);
loginErrorText.setText(this.describeLoginFailure(error));
//
// **Through showLoginFailure rather than inline.** This block did the same five
// things by hand, and the copy drifted the moment that method grew a sixth -
// recording the failure so the Export logs button can name it. The report opened
// saying `cause=unknown`, which reads as the button being broken rather than as
// one of two paths having been missed.
this.showLoginFailure(error);
});
}

Expand Down Expand Up @@ -698,6 +698,15 @@ private void onAgreeToTerms() {
});
}

/**
* The failure the error box is currently showing, for the report page to name.
*
* <p>Kept rather than re-derived: {@link #describeLoginFailure} produces a sentence for a
* person, and the report page wants the exception - class and message - which is a different
* string and the one worth pasting into an issue.
*/
private Throwable lastLoginFailure;

/** Hand the sign-in step back to the user with the failure on it. */
private void showLoginFailure(final Throwable error) {
this.hideLoading();
Expand All @@ -707,11 +716,26 @@ private void showLoginFailure(final Throwable error) {
this.findViewById(R.id.password_input_field).setEnabled(true);
this.findViewById(R.id.login_button_main).setClickable(true);

this.lastLoginFailure = error;
this.findViewById(R.id.login_error_container).setVisibility(VISIBLE);
((TextView) this.findViewById(R.id.login_error_message_text))
.setText(this.describeLoginFailure(error));
}

/**
* Open the report page on the failure just shown.
*
* <p><b>A separate activity on purpose.</b> Closing it returns here with the error box still
* up and the typed Apple ID still in place, so looking at the log costs the user nothing -
* which matters because the alternative was a second machine running adb.
*/
private void openTheReportPageForTheLoginFailure() {
this.startActivity(ErrorReportActivity.intentFor(
this,
ErrorReportActivity.describe(ErrorReportActivity.rootOf(this.lastLoginFailure)),
R.string.error_report_body_login));
}

/**
* What to put on screen when a sign-in fails.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,19 +583,10 @@ private void onBundleExportFailed(final Throwable error) {
// **The cause, not the wrapper.** Rx wraps what a `map` throws, so `describe(error)` here
// would put "RuntimeException" on the page and bury the sentence the reporter needs.
this.startActivity(ErrorReportActivity.intentFor(
this, ErrorReportActivity.describe(rootOf(error)),
this, ErrorReportActivity.describe(ErrorReportActivity.rootOf(error)),
R.string.error_report_body_export));
}

/** The innermost cause, which is the one that says what actually happened. */
private static Throwable rootOf(final Throwable error) {
Throwable cause = error;
while (cause.getCause() != null && cause.getCause() != cause) {
cause = cause.getCause();
}
return cause;
}

private void exportHistoryForSelection() {
this.pendingExport = this.deviceListAdaptor.getSelectedBeacons();

Expand Down Expand Up @@ -744,7 +735,7 @@ private void historyImportFailed(@NonNull final Throwable error) {
|| failure.getReason() == HistoryImportException.Reason.UNEXPECTED) {
this.startActivity(ErrorReportActivity.intentFor(
this,
ErrorReportActivity.describe(rootOf(error)),
ErrorReportActivity.describe(ErrorReportActivity.rootOf(error)),
R.string.error_report_body_history_import));
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ public static Intent intentFor(
* exactly this string and two of them writing it slightly differently makes two reports of
* one bug look like two bugs.
*/
/**
* The innermost cause, which is the one that says what actually happened.
*
* <p>Here beside {@link #describe} because every caller of that needs this first, and for a
* reason that is easy to miss: Rx wraps what a {@code map} throws, so describing the error as
* it arrives puts "RuntimeException" on the page and buries the sentence the reporter needs.
* Two screens writing their own copy of this is two reports of one bug looking like two.
*/
public static Throwable rootOf(final Throwable error) {
Throwable cause = error;
while (cause != null && cause.getCause() != null && cause.getCause() != cause) {
cause = cause.getCause();
}
return cause;
}

public static String describe(final Throwable error) {
if (error == null) {
return "unknown";
Expand Down
42 changes: 34 additions & 8 deletions app/src/main/res/layout/activity_apple_login.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,42 @@
android:visibility="invisible"
tools:visibility="visible">

<TextView
android:id="@+id/login_error_message_text"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/nunito_medium"
android:padding="8dp"
android:text="@string/login_failed_x"
android:textColor="?attr/colorOnErrorContainer"
android:textSize="14sp"
tools:visibility="visible" />
android:orientation="vertical">

<TextView
android:id="@+id/login_error_message_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/nunito_medium"
android:padding="8dp"
android:text="@string/login_failed_x"
android:textColor="?attr/colorOnErrorContainer"
android:textSize="14sp"
tools:visibility="visible" />

<!--
**The only way off this screen with a log in hand.** A sign-in failure
never reached the error report page, so the one screen whose failures
need diagnosing was the one screen that could not produce evidence -
short of adb on a second machine. Opens the report page as its own
activity, so closing it comes straight back here with the error still
showing.
-->
<Button
android:id="@+id/login_error_export_logs"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="@string/login_export_logs"
android:textColor="?attr/colorOnErrorContainer"
app:strokeColor="?attr/colorOnErrorContainer" />
</LinearLayout>
</FrameLayout>

<com.google.android.material.textfield.TextInputLayout
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@ Deine Tags und ihr Standortverlauf sind davon nicht betroffen, und aus deinem Ap

Es wurde kein Verlauf importiert und bereits gespeicherte Daten wurden nicht geändert.</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">Protokolle exportieren</string>
<string name="error_report_body_login">Die Anmeldung ist nicht durchgekommen. Das Protokoll unten nennt den fehlgeschlagenen Schritt und die Antwort darauf – meist genau der Teil, der es erklärt.

Wenn Apple die Anfrage lediglich abgelehnt hat, lohnt es sich, zu warten und es erneut zu versuchen. Schlägt es weiterhin fehl, macht ein Bericht mit diesem Protokoll es behebbar.</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@ Your tags and their location history are not affected, and nothing was removed f

No history was imported and nothing already stored was changed.</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">Export logs</string>
<string name="error_report_body_login">Signing in did not get through. The log below says which step failed and what came back, which is almost always the part that explains it.

If Apple simply refused the request, waiting and trying again is worth doing first. If it keeps failing, a report with this log attached is what makes it fixable.</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@ Vos tags et leur historique de position ne sont pas touchés, et rien n’a ét
<string name="error_report_body_history_import">L’application n’a pas pu restaurer l’historique depuis le fichier sélectionné et ne peut pas en déterminer la raison — réessayer avec le même fichier risque donc de ne pas fonctionner.

Aucun historique n’a été importé et les données déjà enregistrées n’ont pas été modifiées.</string>
<string name="login_export_logs">Exporter les journaux</string>
<string name="error_report_body_login">La connexion n\'a pas abouti. Le journal ci-dessous indique quelle étape a échoué et ce qui a été renvoyé, ce qui explique presque toujours le problème.

Si Apple a simplement refusé la requête, il vaut mieux attendre et réessayer. Si l\'échec persiste, un rapport accompagné de ce journal permet de corriger le problème.</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@

履歴はインポートされず、すでに保存されているデータも変更されていません。</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">ログを書き出す</string>
<string name="error_report_body_login">サインインが完了しませんでした。下のログには、どの手順で失敗し、何が返されたかが記録されています。原因はたいていそこにあります。

Appleが要求を拒否しただけなら、少し待ってからもう一度お試しください。それでも失敗する場合は、このログを添えて報告すると修正できます。</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@

기록을 가져오지 않았으며 이미 저장된 데이터도 변경되지 않았습니다.</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">로그 내보내기</string>
<string name="error_report_body_login">로그인이 완료되지 않았습니다. 아래 로그에 어느 단계에서 실패했고 무엇이 반환되었는지 나와 있으며, 대개 그 부분이 원인입니다.

Apple이 요청을 거부한 것뿐이라면 잠시 기다렸다가 다시 시도해 보세요. 계속 실패한다면 이 로그를 첨부해 신고하면 해결할 수 있습니다.</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-nl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@ Je tags en hun locatiegeschiedenis blijven ongemoeid, en er is niets uit je Appl

Er is geen geschiedenis geïmporteerd en eerder opgeslagen gegevens zijn niet gewijzigd.</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">Logboeken exporteren</string>
<string name="error_report_body_login">Aanmelden is niet gelukt. Het logboek hieronder vermeldt welke stap is mislukt en wat er terugkwam; dat is bijna altijd het deel dat het verklaart.

Als Apple het verzoek simpelweg weigerde, is even wachten en opnieuw proberen het eerste om te doen. Blijft het mislukken, dan maakt een melding met dit logboek het oplosbaar.</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@

История не была импортирована, а уже сохранённые данные не изменились.</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">Экспорт журналов</string>
<string name="error_report_body_login">Вход не выполнен. В журнале ниже указано, какой шаг не удался и что вернулось, — обычно именно это всё и объясняет.

Если Apple просто отклонила запрос, сначала стоит подождать и попробовать снова. Если ошибка повторяется, отчёт с этим журналом позволит её исправить.</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@

未导入任何历史记录,已存储的数据也没有更改。</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">导出日志</string>
<string name="error_report_body_login">登录未能完成。下面的日志会说明哪一步失败以及返回了什么,原因通常就在其中。

如果只是 Apple 拒绝了请求,先等一会儿再试。若持续失败,附上此日志的报告才能让问题得到修复。</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,8 @@

未匯入任何歷史記錄,已儲存的資料也沒有變更。</string>
<string name="map_provider_osm">OpenStreetMap</string>
<string name="login_export_logs">匯出日誌</string>
<string name="error_report_body_login">登入未能完成。下方的日誌會說明哪一步失敗以及回傳了什麼,原因通常就在其中。

如果只是 Apple 拒絕了請求,先等一會兒再試。若持續失敗,附上此日誌的回報才能讓問題獲得修正。</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -416,4 +416,8 @@ Your tags and their location history are not affected, and nothing was removed f
<string name="error_report_body_history_import">The app could not restore history from the file you picked, and cannot say why — so trying again with the same file may not help.

No history was imported and nothing already stored was changed.</string>
<string name="login_export_logs">Export logs</string>
<string name="error_report_body_login">Signing in did not get through. The log below says which step failed and what came back, which is almost always the part that explains it.

If Apple simply refused the request, waiting and trying again is worth doing first. If it keeps failing, a report with this log attached is what makes it fixable.</string>
</resources>
Loading