diff --git a/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java b/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java index 3ee37b4409..e51e8f992c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/help/HelpFragment.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.help; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; @@ -27,8 +28,10 @@ import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.emoji.EmojiImageView; +import org.thoughtcrime.securesms.util.AppSignatureUtil; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.IntentUtils; +import org.thoughtcrime.securesms.util.SupportEmailUtil; import org.thoughtcrime.securesms.util.text.AfterTextChanged; import java.util.ArrayList; @@ -150,70 +153,36 @@ public class HelpFragment extends Fragment { .findFirst().orElse(null); CommunicationActions.openEmail(requireContext(), - getString(R.string.RegistrationActivity_support_email), + SupportEmailUtil.getSupportEmailAddress(requireContext()), getEmailSubject(), - getEmailBody(debugLog, feeling).toString()); + getEmailBody(debugLog, feeling)); } private String getEmailSubject() { return getString(R.string.HelpFragment__signal_android_support_request); } - private Spanned getEmailBody(@Nullable String debugLog, @Nullable Feeling feeling) { - SpannableStringBuilder builder = new SpannableStringBuilder(); - - builder.append(problem.getText().toString()); - builder.append("\n\n"); - builder.append("--- "); - builder.append(getString(R.string.HelpFragment__support_info)); - builder.append(" ---\n"); - builder.append(getString(R.string.HelpFragment__subject)); - builder.append(" "); - builder.append(getString(R.string.HelpFragment__signal_android_support_request)); - builder.append("\n"); - builder.append(getString(R.string.HelpFragment__device_info)); - builder.append(" "); - builder.append(getDeviceInfo()); - builder.append("\n"); - builder.append(getString(R.string.HelpFragment__android_version)); - builder.append(" "); - builder.append(getAndroidVersion()); - builder.append("\n"); - builder.append(getString(R.string.HelpFragment__signal_version)); - builder.append(" "); - builder.append(getSignalVersion()); - builder.append("\n"); - builder.append(getString(R.string.HelpFragment__locale)); - builder.append(" "); - builder.append(Locale.getDefault().toString()); + private String getEmailBody(@Nullable String debugLog, @Nullable Feeling feeling) { + StringBuilder suffix = new StringBuilder(); if (debugLog != null) { - builder.append("\n"); - builder.append(getString(R.string.HelpFragment__debug_log)); - builder.append(" "); - builder.append(debugLog); + suffix.append("\n"); + suffix.append(getString(R.string.HelpFragment__debug_log)); + suffix.append(" "); + suffix.append(debugLog); } if (feeling != null) { - builder.append("\n\n"); - builder.append(feeling.getEmojiCode()); - builder.append("\n"); - builder.append(getString(feeling.getStringId())); + suffix.append("\n\n"); + suffix.append(feeling.getEmojiCode()); + suffix.append("\n"); + suffix.append(getString(feeling.getStringId())); } - return builder; - } - - private static CharSequence getDeviceInfo() { - return String.format("%s %s (%s)", Build.MANUFACTURER, Build.MODEL, Build.PRODUCT); - } - - private static CharSequence getAndroidVersion() { - return String.format("%s (%s, %s)", Build.VERSION.RELEASE, Build.VERSION.INCREMENTAL, Build.DISPLAY); - } - - private static CharSequence getSignalVersion() { - return BuildConfig.VERSION_NAME; + return SupportEmailUtil.generateSupportEmailBody(requireContext(), + getString(R.string.HelpFragment__signal_android_support_request), + problem.getText().toString() + "\n\n", + suffix.toString()); } private static void setSpinning(@Nullable CircularProgressButton button) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java index 95a2a83cd8..3034923492 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionSystemInfo.java @@ -10,6 +10,8 @@ import android.view.WindowManager; import androidx.annotation.NonNull; +import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.util.AppSignatureUtil; import org.thoughtcrime.securesms.util.ByteUnit; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -60,6 +62,7 @@ public class LogSectionSystemInfo implements LogSection { } catch (PackageManager.NameNotFoundException nnfe) { builder.append("Unknown\n"); } + builder.append("Package : ").append(BuildConfig.APPLICATION_ID).append(" (").append(getSigningString(context)).append(")"); return builder; } @@ -134,4 +137,8 @@ public class LogSectionSystemInfo implements LogSection { private static @NonNull String getScreenRefreshRate(@NonNull Context context) { return String.format(Locale.ENGLISH, "%.2f hz", ServiceUtil.getWindowManager(context).getDefaultDisplay().getRefreshRate()); } + + private static String getSigningString(@NonNull Context context) { + return AppSignatureUtil.getAppSignature(context).or("Unknown"); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java index 25c9c66950..43185b433c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/PinRestoreEntryFragment.java @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.registration.RegistrationUtil; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.ServiceUtil; +import org.thoughtcrime.securesms.util.SupportEmailUtil; import org.thoughtcrime.securesms.util.ViewUtil; import java.util.Locale; @@ -195,14 +196,14 @@ public class PinRestoreEntryFragment extends Fragment { .setMessage(getString(R.string.PinRestoreEntryFragment_your_pin_is_a_d_digit_code, KbsConstants.MINIMUM_PIN_LENGTH)) .setPositiveButton(R.string.PinRestoreEntryFragment_create_new_pin, null) .setNeutralButton(R.string.PinRestoreEntryFragment_contact_support, (dialog, which) -> { + String body = SupportEmailUtil.generateSupportEmailBody(requireContext(), + getString(R.string.PinRestoreEntryFragment_signal_registration_need_help_with_pin), + null, + null); CommunicationActions.openEmail(requireContext(), - getString(R.string.PinRestoreEntryFragment_support_email), + SupportEmailUtil.getSupportEmailAddress(requireContext()), getString(R.string.PinRestoreEntryFragment_signal_registration_need_help_with_pin), - getString(R.string.PinRestoreEntryFragment_subject_signal_registration, - getDevice(), - getAndroidVersion(), - BuildConfig.VERSION_NAME, - Locale.getDefault())); + body); }) .setNegativeButton(R.string.PinRestoreEntryFragment_cancel, null) .show(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java index 575a7a4e35..2c5d676fb8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java @@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest; import org.thoughtcrime.securesms.registration.service.RegistrationService; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; import org.thoughtcrime.securesms.util.CommunicationActions; +import org.thoughtcrime.securesms.util.SupportEmailUtil; import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; @@ -306,21 +307,13 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { } private void sendEmailToSupport() { + String body = SupportEmailUtil.generateSupportEmailBody(requireContext(), + getString(R.string.RegistrationActivity_code_support_subject), + null, + null); CommunicationActions.openEmail(requireContext(), - getString(R.string.RegistrationActivity_support_email), + SupportEmailUtil.getSupportEmailAddress(requireContext()), getString(R.string.RegistrationActivity_code_support_subject), - getString(R.string.RegistrationActivity_code_support_body, - getDevice(), - getAndroidVersion(), - BuildConfig.VERSION_NAME, - Locale.getDefault())); - } - - private static String getDevice() { - return String.format("%s %s (%s)", Build.MANUFACTURER, Build.MODEL, Build.PRODUCT); - } - - private static String getAndroidVersion() { - return String.format("%s (%s, %s)", Build.VERSION.RELEASE, Build.VERSION.INCREMENTAL, Build.DISPLAY); + body); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AppSignatureUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/AppSignatureUtil.java new file mode 100644 index 0000000000..cf25fe1bc0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AppSignatureUtil.java @@ -0,0 +1,71 @@ +package org.thoughtcrime.securesms.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.os.Build; + +import androidx.annotation.NonNull; + +import org.thoughtcrime.securesms.logging.Log; +import org.whispersystems.libsignal.util.guava.Optional; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public final class AppSignatureUtil { + + private static final String TAG = Log.tag(AppSignatureUtil.class); + + private static final String HASH_TYPE = "SHA-256"; + private static final int HASH_LENGTH_BYTES = 9; + private static final int HASH_LENGTH_CHARS = 11; + + private AppSignatureUtil() {} + + /** + * Only intended to be used for logging. + */ + @SuppressLint("PackageManagerGetSignatures") + public static Optional getAppSignature(@NonNull Context context) { + try { + String packageName = context.getPackageName(); + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + Signature[] signatures = packageInfo.signatures; + + if (signatures.length > 0) { + String hash = hash(packageName, signatures[0].toCharsString()); + return Optional.fromNullable(hash); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, e); + } + + return Optional.absent(); + } + + private static String hash(String packageName, String signature) { + String appInfo = packageName + " " + signature; + try { + MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE); + messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8)); + + byte[] hashSignature = messageDigest.digest(); + hashSignature = Arrays.copyOfRange(hashSignature, 0, HASH_LENGTH_BYTES); + + String base64Hash = Base64.encodeBytes(hashSignature); + base64Hash = base64Hash.substring(0, HASH_LENGTH_CHARS); + + return base64Hash; + } catch (NoSuchAlgorithmException e) { + Log.w(TAG, e); + } + + return null; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SupportEmailUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SupportEmailUtil.java new file mode 100644 index 0000000000..04a51f6084 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SupportEmailUtil.java @@ -0,0 +1,66 @@ +package org.thoughtcrime.securesms.util; + +import android.content.Context; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.R; + +import java.util.Locale; + +public final class SupportEmailUtil { + + private SupportEmailUtil() { } + + public static @NonNull String getSupportEmailAddress(@NonNull Context context) { + return context.getString(R.string.SupportEmailUtil_support_email); + } + + /** + * Generates a support email body with system info near the top. + */ + public static @NonNull String generateSupportEmailBody(@NonNull Context context, + @NonNull String subject, + @Nullable String prefix, + @Nullable String suffix) + { + prefix = Util.firstNonNull(prefix, ""); + suffix = Util.firstNonNull(suffix, ""); + return String.format("%s\n%s\n%s", prefix, buildSystemInfo(context, subject), suffix); + } + + private static @NonNull String buildSystemInfo(@NonNull Context context, @NonNull String subject) { + return "--- " + context.getString(R.string.HelpFragment__support_info) + " ---" + + "\n" + + context.getString(R.string.SupportEmailUtil_subject) + " " + subject + + "\n" + + context.getString(R.string.SupportEmailUtil_device_info) + " " + getDeviceInfo() + + "\n" + + context.getString(R.string.SupportEmailUtil_android_version) + " " + getAndroidVersion() + + "\n" + + context.getString(R.string.SupportEmailUtil_signal_version) + " " + getSignalVersion() + + "\n" + + context.getString(R.string.SupportEmailUtil_signal_package) + " " + getSignalPackage(context) + + "\n" + + context.getString(R.string.SupportEmailUtil_locale) + " " + Locale.getDefault().toString(); + } + + private static CharSequence getDeviceInfo() { + return String.format("%s %s (%s)", Build.MANUFACTURER, Build.MODEL, Build.PRODUCT); + } + + private static CharSequence getAndroidVersion() { + return String.format("%s (%s, %s)", Build.VERSION.RELEASE, Build.VERSION.INCREMENTAL, Build.DISPLAY); + } + + private static CharSequence getSignalVersion() { + return BuildConfig.VERSION_NAME; + } + + private static CharSequence getSignalPackage(@NonNull Context context) { + return String.format("%s (%s)", BuildConfig.APPLICATION_ID, AppSignatureUtil.getAppSignature(context).or("Unknown")); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a62cb4396..0776f238e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -856,9 +856,7 @@ You have %1$d attempt remaining. If you run out of attempts, you can create a new PIN. You can register and use your account but you\'ll lose some saved settings like your profile information. You have %1$d attempts remaining. If you run out of attempts, you can create a new PIN. You can register and use your account but you\'ll lose some saved settings like your profile information. - support@signal.org Signal Registration - Need Help with PIN for Android - Subject: Signal Registration - Need Help with PIN for Android\nDevice info: %1$s\nAndroid version: %2$s\nSignal version: %3$s\nLocale: %4$s Enter alphanumeric PIN Enter numeric PIN @@ -1054,6 +1052,16 @@ Ok Share + + support@signal.org + Subject: + Signal Android Support Request + Device info: + Android version: + Signal version: + Signal package: + Locale: + Group updated Left the group @@ -1634,12 +1642,7 @@ https://support.signal.org/hc/articles/360007318591 https://support.signal.org Support Info - Subject: Signal Android Support Request - Device info: - Android version: - Signal version: - Locale: Debug Log: n/a Could not upload logs @@ -2159,9 +2162,7 @@ Wrong number Call me instead \n (Available in %1$02d:%2$02d) Contact Signal Support - support@signal.org Signal Registration - Verification Code for Android - Subject: Signal Registration - Verification Code for Android\nDevice info: %1$s\nAndroid version: %2$s\nSignal version: %3$s\nLocale: %4$s Never Unknown Screen lock