diff --git a/app/build.gradle b/app/build.gradle index e65ac10ed..417c06675 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,6 +24,5 @@ android { } dependencies { - compile 'ch.acra:acra:4.9.0' compile 'com.github.yeriomin:play-store-api:556e2bf' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a9ebc4f63..5420f09c5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java b/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java index dbd24c01e..59b70073e 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java @@ -23,6 +23,12 @@ public class AboutActivity extends YalpStoreActivity { TextView gsfIdView = (TextView) findViewById(R.id.gsf_id); gsfIdView.setText(gsfId); gsfIdView.setOnClickListener(new CopyToClipboardListener()); + findViewById(R.id.developer_email).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new YalpStoreUncaughtExceptionHandler(AboutActivity.this).send(null); + } + }); } private class CopyToClipboardListener implements View.OnClickListener { diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/DetailsActivity.java b/app/src/main/java/com/github/yeriomin/yalpstore/DetailsActivity.java index b3ce05303..1b3aef93e 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/DetailsActivity.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/DetailsActivity.java @@ -171,19 +171,19 @@ public class DetailsActivity extends YalpStoreActivity { if (null == versionName || versionName.isEmpty()) { return; } - String label = getString(R.string.details_versionName, versionName); - if (app.isInstalled()) { - try { - PackageInfo info = getPackageManager().getPackageInfo(app.getPackageName(), 0); - if (info.versionCode != app.getVersionCode()) { - label = getString(R.string.details_versionName_updatable, info.versionName, versionName); - } - } catch (PackageManager.NameNotFoundException e) { - // We've checked for that already - } - } - textView.setText(label); + textView.setText(getString(R.string.details_versionName, versionName)); textView.setVisibility(View.VISIBLE); + if (!app.isInstalled()) { + return; + } + try { + PackageInfo info = getPackageManager().getPackageInfo(app.getPackageName(), 0); + if (info.versionCode != app.getVersionCode()) { + textView.setText(getString(R.string.details_versionName_updatable, info.versionName, versionName)); + } + } catch (PackageManager.NameNotFoundException e) { + // We've checked for that already + } } private void drawDescription(App app) { diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/NativeDeviceInfoProvider.java b/app/src/main/java/com/github/yeriomin/yalpstore/NativeDeviceInfoProvider.java index d28ac9b18..c3f330697 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/NativeDeviceInfoProvider.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/NativeDeviceInfoProvider.java @@ -3,6 +3,7 @@ package com.github.yeriomin.yalpstore; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.os.Build; import android.util.DisplayMetrics; @@ -22,24 +23,6 @@ public class NativeDeviceInfoProvider implements DeviceInfoProvider { // Getting this requires a permission and google services to be installed static private final int GOOGLE_SERVICES_VERSION_CODE = 80711500; - static private final String[] sharedLibraries = new String[] { - "ConnectivityExt", - "activation.jar", - "android-support-v13.jar", - "android-support-v4.jar", - "android-support-v7-recyclerview.jar", - "cloud-common.jar", - "com.android.media.remotedisplay", - "com.android.mediadrm.signer", - "android.test.runner", - "com.android.future.usb.accessory", - "com.android.location.provider", - "com.android.nfc_extras", - "com.google.android.maps", - "com.google.android.media.effects", - "com.google.widevine.software.drm", - "javax.obex" - }; static private final String[] glExtensions = new String[] { "GL_AMD_compressed_ATC_texture", "GL_AMD_performance_monitor", @@ -220,27 +203,43 @@ public class NativeDeviceInfoProvider implements DeviceInfoProvider { } public DeviceConfigurationProto getDeviceConfigurationProto() { - DisplayMetrics metrics = this.context.getResources().getDisplayMetrics(); - return DeviceConfigurationProto.newBuilder() - .setTouchScreen(3) - .setKeyboard(1) - .setNavigation(1) - .setScreenLayout(2) - .setHasHardKeyboard(false) - .setHasFiveWayNavigation(false) - .setScreenDensity((int) (metrics.density * 160f)) - .setScreenWidth(metrics.widthPixels) - .setScreenHeight(metrics.heightPixels) + DeviceConfigurationProto.Builder builder = DeviceConfigurationProto.newBuilder(); + addDisplayMetrics(builder); + addConfiguration(builder); + return builder .addAllNativePlatform(getPlatforms()) - .addAllSystemSharedLibrary(Arrays.asList(sharedLibraries)) - .addAllSystemAvailableFeature(getFeatures()) + .addAllSystemSharedLibrary(getSharedLibraries(context)) + .addAllSystemAvailableFeature(getFeatures(context)) .addAllSystemSupportedLocale(getLocales()) .setGlEsVersion(196609) // Getting this and next list requires messing with ndk .addAllGlExtension(Arrays.asList(glExtensions)) .build(); } - private List getPlatforms() { + private DeviceConfigurationProto.Builder addDisplayMetrics(DeviceConfigurationProto.Builder builder) { + DisplayMetrics metrics = this.context.getResources().getDisplayMetrics(); + builder + .setScreenDensity((int) (metrics.density * 160f)) + .setScreenWidth(metrics.widthPixels) + .setScreenHeight(metrics.heightPixels) + ; + return builder; + } + + private DeviceConfigurationProto.Builder addConfiguration(DeviceConfigurationProto.Builder builder) { + Configuration config = this.context.getResources().getConfiguration(); + builder + .setTouchScreen(config.touchscreen) + .setKeyboard(config.keyboard) + .setNavigation(config.navigation) + .setScreenLayout(config.screenLayout & 15) + .setHasHardKeyboard(config.keyboard == Configuration.KEYBOARD_QWERTY) + .setHasFiveWayNavigation(config.navigation == Configuration.NAVIGATIONHIDDEN_YES) + ; + return builder; + } + + static public List getPlatforms() { List platforms = new ArrayList<>(); if (Build.VERSION.SDK_INT >= 21) { platforms = Arrays.asList(Build.SUPPORTED_ABIS); @@ -255,8 +254,8 @@ public class NativeDeviceInfoProvider implements DeviceInfoProvider { return platforms; } - private List getFeatures() { - PackageManager packageManager = this.context.getPackageManager(); + static public List getFeatures(Context context) { + PackageManager packageManager = context.getPackageManager(); FeatureInfo[] featuresList = packageManager.getSystemAvailableFeatures(); List featureStringList = new ArrayList<>(); for (FeatureInfo feature : featuresList) { @@ -267,7 +266,7 @@ public class NativeDeviceInfoProvider implements DeviceInfoProvider { return featureStringList; } - private List getLocales() { + static public List getLocales() { List localeStringList = new ArrayList<>(); for (Locale locale : Locale.getAvailableLocales()) { String localeString = locale.toString(); @@ -277,4 +276,8 @@ public class NativeDeviceInfoProvider implements DeviceInfoProvider { } return localeStringList; } + + static public List getSharedLibraries(Context context) { + return Arrays.asList(context.getPackageManager().getSystemSharedLibraryNames()); + } } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreActivity.java b/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreActivity.java index cf4f5ec49..6be6043b2 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreActivity.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreActivity.java @@ -16,6 +16,7 @@ public abstract class YalpStoreActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { logout = false; ThemeManager.setTheme(this); + Thread.setDefaultUncaughtExceptionHandler(new YalpStoreUncaughtExceptionHandler(this)); super.onCreate(savedInstanceState); } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreApplication.java b/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreApplication.java deleted file mode 100644 index 032b86d25..000000000 --- a/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreApplication.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.yeriomin.yalpstore; - -import android.app.Application; -import android.content.Context; -import android.util.Log; - -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.ReportingInteractionMode; -import org.acra.config.ACRAConfiguration; -import org.acra.config.ACRAConfigurationException; -import org.acra.config.ConfigurationBuilder; - -public class YalpStoreApplication extends Application { - - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - - try { - ACRAConfiguration config = new ConfigurationBuilder(this) - .setMailTo(getString(R.string.about_developer_email)) - .setReportingInteractionMode(ReportingInteractionMode.DIALOG) - .setResDialogText(R.string.application_crashed) - .setCustomReportContent( - ReportField.ANDROID_VERSION, - ReportField.STACK_TRACE_HASH, - ReportField.STACK_TRACE, - ReportField.BUILD, - ReportField.BUILD_CONFIG, - ReportField.DISPLAY, - ReportField.DEVICE_FEATURES - ) - .build(); - ACRA.init(this, config); - } catch (ACRAConfigurationException e) { - Log.e(getClass().getName(), "Could not configure ACRA: " + e.getMessage()); - } catch (Throwable e) { - Log.e(getClass().getName(), "Unknown problem with ACRA: " + e.getMessage()); - } - } -} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreUncaughtExceptionHandler.java b/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreUncaughtExceptionHandler.java new file mode 100644 index 000000000..6f05bc226 --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/YalpStoreUncaughtExceptionHandler.java @@ -0,0 +1,145 @@ +package com.github.yeriomin.yalpstore; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.os.Looper; +import android.text.ClipboardManager; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; + +import java.util.LinkedHashMap; +import java.util.Map; + +class YalpStoreUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + + private Activity activity; + + public YalpStoreUncaughtExceptionHandler(Activity activity) { + this.activity = activity; + } + + @Override + public void uncaughtException(Thread t, final Throwable e) { + e.printStackTrace(); + Thread thread = new Thread() { + @Override + public void run() { + Looper.prepare(); + showCrashDialog(e); + Looper.loop(); + } + }; + try { + thread.start(); + } catch (Throwable ee) { + Log.e(getClass().getName(), "Failed to process an uncaught exception: " + ee.getMessage()); + System.exit(1); + } + } + + public AlertDialog showCrashDialog(final Throwable e) { + return new AlertDialog.Builder(activity) + .setTitle(activity.getString(R.string.dialog_title_application_crashed)) + .setMessage(activity.getString(R.string.dialog_message_application_crashed)) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + System.exit(1); + } + }) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + send(e); + dialog.dismiss(); + System.exit(1); + } + }) + .show() + ; + } + + public void send(Throwable e) { + Intent emailIntent = new Intent(Intent.ACTION_SENDTO); + emailIntent.setData(Uri.fromParts("mailto", activity.getString(R.string.about_developer_email), null)); + emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, activity.getPackageName() + " Crash Report"); + emailIntent.putExtra(Intent.EXTRA_TEXT, buildBody(e)); + try { + activity.startActivity(emailIntent); + } catch (ActivityNotFoundException ee) { + Log.e(getClass().getName(), ee.getClass().toString() + " " + ee.getMessage()); + ((ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE)).setText(buildBody(e)); + } + } + + private String buildBody(Throwable e) { + StringBuilder body = new StringBuilder(); + if (null != e) { + body.append("\n\n").append(Log.getStackTraceString(e)).append("\n\n"); + } + Map values = new LinkedHashMap<>(); + values.putAll(getBuildValues()); + values.putAll(getConfigurationValues()); + values.putAll(getDisplayMetricsValues()); + values.putAll(getPackageManagerValues()); + for (String key : values.keySet()) { + body.append(key).append(" = ").append(values.get(key)).append("\n"); + } + return body.toString(); + } + + private Map getBuildValues() { + Map values = new LinkedHashMap<>(); + values.put("Build.FINGERPRINT", Build.FINGERPRINT); + values.put("Build.HARDWARE", Build.HARDWARE); + values.put("Build.BRAND", Build.BRAND); + values.put("Build.RADIO", Build.RADIO); + values.put("Build.BOOTLOADER", Build.BOOTLOADER); + values.put("Build.DEVICE", Build.DEVICE); + values.put("Build.VERSION.SDK_INT", Integer.toString(Build.VERSION.SDK_INT)); + values.put("Build.MODEL", Build.MODEL); + values.put("Build.MANUFACTURER", Build.MANUFACTURER); + values.put("Build.PRODUCT", Build.PRODUCT); + return values; + } + + private Map getConfigurationValues() { + Map values = new LinkedHashMap<>(); + Configuration config = activity.getResources().getConfiguration(); + values.put("TouchScreen", Integer.toString(config.touchscreen)); + values.put("Keyboard", Integer.toString(config.keyboard)); + values.put("Navigation", Integer.toString(config.navigation)); + values.put("ScreenLayout", Integer.toString(config.screenLayout & 15)); + values.put("HasHardKeyboard", Boolean.toString(config.keyboard == Configuration.KEYBOARD_QWERTY)); + values.put("HasFiveWayNavigation", Boolean.toString(config.navigation == Configuration.NAVIGATIONHIDDEN_YES)); + return values; + } + + private Map getDisplayMetricsValues() { + Map values = new LinkedHashMap<>(); + DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); + values.put("Screen.Density", Integer.toString((int) (metrics.density * 160f))); + values.put("Screen.Width", Integer.toString(metrics.widthPixels)); + values.put("Screen.Height", Integer.toString(metrics.heightPixels)); + return values; + } + + private Map getPackageManagerValues() { + Map values = new LinkedHashMap<>(); + values.put("Platforms", TextUtils.join(",", NativeDeviceInfoProvider.getPlatforms())); + values.put("SharedLibraries", TextUtils.join(",", NativeDeviceInfoProvider.getSharedLibraries(activity))); + values.put("Features", TextUtils.join(",", activity.getPackageManager().getSystemSharedLibraryNames())); + values.put("Locales", TextUtils.join(",", NativeDeviceInfoProvider.getLocales())); + return values; + } +} diff --git a/app/src/main/res/layout/about_activity_layout.xml b/app/src/main/res/layout/about_activity_layout.xml index 038244333..581fe2387 100644 --- a/app/src/main/res/layout/about_activity_layout.xml +++ b/app/src/main/res/layout/about_activity_layout.xml @@ -39,7 +39,7 @@ android:id="@+id/developer_email" android:layout_width="match_parent" android:layout_height="wrap_content" - android:autoLink="email" + android:textColor="#5555ff" android:textSize="18sp" android:text="@string/about_developer_email" /> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f2362288c..88e9556d2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,6 +1,5 @@ - В Yalp Store произошёл сбой.\n\nВы можете помочь разработчику исправить это, отправив email с подробностями об ошибке. Нажмите OK чтобы открыть ваше email-приложение с письмом для разработчика, готовым к отправке.\n\nНикакой чувствительной и личной информации отправлено не будет, проверьте текст письма, если сомеваетесь. Почта разработчика: Ваш текущий аккаунт: Скопировано в буфер обмена @@ -9,6 +8,8 @@ Обновлений нет Ничего не найдено несовместимо + В Yalp Store произошёл сбой + Вы можете помочь разработчику исправить это, отправив email с подробностями об ошибке. Нажмите OK чтобы открыть ваше email-приложение с письмом для разработчика, готовым к отправке.\n\nЕсли хотите, опишите проблему сами или добавьте любое другое сообщение.\n\nНикакой чувствительной и личной информации отправлено не будет, проверьте текст письма, если сомеваетесь. Позднее Создать пароль Двухэтапная аутентификация diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1b3b54ee..1932b498b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,7 +4,6 @@ yalp.store.dev@gmail.com Google Services Framework id: - Sorry, Yalp Store crashed.\n\nYou can help fix this by sending error data to the developer. Clicking OK will take you to your default email app with a complete email ready to be sent to the developer.\n\nNo private or sensitive information is collected, please review the letter content if you doubt it. Developer\'s email: You are logged in as: Copied to clipboard @@ -24,6 +23,8 @@ Ignore updates All apps Search applications + Sorry, Yalp Store crashed + You can help fix this by sending error data to the developer. Clicking OK will take you to your default email app with a complete email ready to be sent to the developer.\n\nFeel free to add your own description of the problem or any other message.\n\nNo private or sensitive information is collected, please review the letter content if you doubt it. Not now Create password 2-Step Verification