diff --git a/app/src/basic/res/xml/preferences.xml b/app/src/basic/res/xml/preferences.xml index 6744145bb..8b249dae8 100644 --- a/app/src/basic/res/xml/preferences.xml +++ b/app/src/basic/res/xml/preferences.xml @@ -24,6 +24,16 @@ android:targetPackage="@string/applicationId" android:targetClass="org.fdroid.fdroid.views.ManageReposActivity"/> + + + @@ -110,13 +120,16 @@ - + - + + + + diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 2163cd363..19ab12592 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -1,5 +1,9 @@ /* - * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com + * Copyright (C) 2010-2012 Ciaran Gultnieks, ciaran@ciarang.com + * Copyright (C) 2013-2016 Peter Serwylo + * Copyright (C) 2014-2018 Hans-Christoph Steiner + * Copyright (C) 2015-2016 Daniel Martí + * Copyright (c) 2018 Senecto Limited * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -33,11 +37,13 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.StrictMode; import android.support.v4.util.LongSparseArray; import android.text.TextUtils; +import android.util.Base64; import android.util.Log; import android.view.Display; import android.view.WindowManager; @@ -66,14 +72,17 @@ import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.installer.ApkFileProvider; import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.net.ConnectivityMonitorService; +import org.fdroid.fdroid.net.HttpDownloader; import org.fdroid.fdroid.net.ImageLoaderForUIL; import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.views.hiding.HidingManager; import javax.microedition.khronos.opengles.GL10; import java.io.IOException; +import java.nio.ByteBuffer; import java.security.Security; import java.util.List; +import java.util.UUID; @ReportsCrashes(mailTo = "reports@f-droid.org", mode = ReportingInteractionMode.DIALOG, @@ -341,9 +350,11 @@ public class FDroidApp extends Application { Preferences.setup(this); Languages.setLanguage(this); - ACRA.init(this); - if (isAcraProcess() || HidingManager.isHidden(this)) { - return; + if (Preferences.get().promptToSendCrashReports()) { + ACRA.init(this); + if (isAcraProcess() || HidingManager.isHidden(this)) { + return; + } } PRNGFixes.apply(); @@ -480,6 +491,30 @@ public class FDroidApp extends Application { UpdateService.forceUpdateRepo(this); } atStartTime.edit().putInt("build-version", Build.VERSION.SDK_INT).apply(); + + final String queryStringKey = "http-downloader-query-string"; + if (Preferences.get().sendVersionAndUUIDToServers()) { + HttpDownloader.queryString = atStartTime.getString(queryStringKey, null); + if (HttpDownloader.queryString == null) { + UUID uuid = UUID.randomUUID(); + ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE * 2); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + String id = Base64.encodeToString(buffer.array(), + Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING); + StringBuilder builder = new StringBuilder("id=").append(id); + String versionName = Uri.encode(Utils.getVersionName(this)); + if (versionName != null) { + builder.append("&client_version=").append(versionName); + } + HttpDownloader.queryString = builder.toString(); + } + if (!atStartTime.contains(queryStringKey)) { + atStartTime.edit().putString(queryStringKey, HttpDownloader.queryString).apply(); + } + } else { + atStartTime.edit().remove(queryStringKey).apply(); + } } /** diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 59c0f7565..3fa6dec15 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -84,6 +84,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh public static final String PREF_SHOW_ROOT_APPS = "rooted"; public static final String PREF_SHOW_ANTI_FEATURE_APPS = "showAntiFeatureApps"; public static final String PREF_FORCE_TOUCH_APPS = "ignoreTouchscreen"; + public static final String PREF_PROMPT_TO_SEND_CRASH_REPORTS = "promptToSendCrashReports"; public static final String PREF_KEEP_CACHE_TIME = "keepCacheFor"; public static final String PREF_UNSTABLE_UPDATES = "unstableUpdates"; public static final String PREF_KEEP_INSTALL_HISTORY = "keepInstallHistory"; @@ -106,6 +107,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh public static final String PREF_PANIC_HIDE = "pref_panic_hide"; public static final String PREF_HIDE_ON_LONG_PRESS_SEARCH = "hideOnLongPressSearch"; public static final String PREF_HIDE_ALL_NOTIFICATIONS = "hideAllNotifications"; + public static final String PREF_SEND_VERSION_AND_UUID_TO_SERVERS = "sendVersionAndUUIDToServers"; public static final int OVER_NETWORK_NEVER = 0; public static final int OVER_NETWORK_ON_DEMAND = 1; @@ -172,6 +174,10 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh initialized.put(key, false); } + public boolean promptToSendCrashReports() { + return preferences.getBoolean(PREF_PROMPT_TO_SEND_CRASH_REPORTS, IGNORED_B); + } + public boolean isForceOldIndexEnabled() { return preferences.getBoolean(PREF_FORCE_OLD_INDEX, IGNORED_B); } @@ -498,6 +504,14 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh return preferences.getBoolean(PREF_HIDE_ALL_NOTIFICATIONS, IGNORED_B); } + /** + * Whether to include the version of this app and a randomly generated ID + * to the server when downloading from it. + */ + public boolean sendVersionAndUUIDToServers() { + return preferences.getBoolean(PREF_SEND_VERSION_AND_UUID_TO_SERVERS, IGNORED_B); + } + /** * This is cached as it is called several times inside app list adapters. * Providing it here means the shared preferences file only needs to be diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java index 80e1ad988..08e25cd41 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java @@ -111,12 +111,7 @@ public class RepoUpdater { } protected String getIndexUrl(@NonNull Repo repo) { - String url = repo.address + "/index.jar"; - String versionName = Utils.getVersionName(context); - if (versionName != null) { - url += "?client_version=" + versionName; - } - return url; + return repo.address + "/index.jar"; } public boolean hasChanged() { diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java index 6d930c850..7c280c8db 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallHistoryService.java @@ -28,7 +28,7 @@ import android.net.Uri; import android.os.Process; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; - +import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; @@ -47,7 +47,8 @@ import java.util.List; public class InstallHistoryService extends IntentService { public static final String TAG = "InstallHistoryService"; - public static final Uri LOG_URI = Uri.parse("content://org.fdroid.fdroid.installer/install_history/all"); + public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".installer"; + public static final Uri LOG_URI = Uri.parse("content://" + AUTHORITY + "/install_history/all"); private static BroadcastReceiver broadcastReceiver; diff --git a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java index 218dd71a7..75f9416e5 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java @@ -1,3 +1,24 @@ +/* + * Copyright (C) 2014-2017 Peter Serwylo + * Copyright (C) 2014-2018 Hans-Christoph Steiner + * Copyright (C) 2015-2016 Daniel Martí + * Copyright (c) 2018 Senecto Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package org.fdroid.fdroid.net; import android.annotation.TargetApi; @@ -40,6 +61,11 @@ public class HttpDownloader extends Downloader { private HttpURLConnection connection; private boolean newFileAvailableOnServer; + /** + * String to append to all HTTP downloads, created in {@link FDroidApp#onCreate()} + */ + public static String queryString; + HttpDownloader(Uri uri, File destFile) throws FileNotFoundException, MalformedURLException { this(uri, destFile, null, null); @@ -142,7 +168,11 @@ public class HttpDownloader extends Downloader { // swap never works with a proxy, its unrouted IP on the same subnet connection = (HttpURLConnection) sourceUrl.openConnection(); } else { - connection = NetCipher.getHttpURLConnection(sourceUrl); + if (queryString != null) { + connection = NetCipher.getHttpURLConnection(new URL(urlString + "?" + queryString)); + } else { + connection = NetCipher.getHttpURLConnection(sourceUrl); + } } connection.setRequestProperty("User-Agent", "F-Droid " + BuildConfig.VERSION_NAME); diff --git a/app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java b/app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java new file mode 100644 index 000000000..6d550baa1 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 Blue Jay Wireless + * Copyright (C) 2018 Senecto Limited + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.fdroid.fdroid.views; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; +import org.apache.commons.io.IOUtils; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.installer.InstallHistoryService; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.Charset; + +public class InstallHistoryActivity extends AppCompatActivity { + public static final String TAG = "InstallHistoryActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_install_history); + Toolbar toolbar = findViewById(R.id.toolbar); + toolbar.setTitle(getString(R.string.install_history)); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + String text = ""; + try { + ContentResolver resolver = getContentResolver(); + + Cursor cursor = resolver.query(InstallHistoryService.LOG_URI, null, null, null, null); + if (cursor != null) { + cursor.moveToFirst(); + cursor.close(); + } + + ParcelFileDescriptor pfd = resolver.openFileDescriptor(InstallHistoryService.LOG_URI, "r"); + FileDescriptor fd = pfd.getFileDescriptor(); + FileInputStream fileInputStream = new FileInputStream(fd); + text = IOUtils.toString(fileInputStream, Charset.defaultCharset()); + } catch (IOException | SecurityException | IllegalStateException e) { + e.printStackTrace(); + } + TextView textView = findViewById(R.id.text); + textView.setText(text); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.install_history, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + switch (item.getItemId()) { + case R.id.menu_delete: + getContentResolver().delete(InstallHistoryService.LOG_URI, null, null); + TextView textView = findViewById(R.id.text); + textView.setText(""); + break; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java index 29ddebce6..8c49f0617 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java @@ -337,10 +337,13 @@ public class PreferencesFragment extends PreferenceFragment case Preferences.PREF_KEEP_INSTALL_HISTORY: CheckBoxPreference p = (CheckBoxPreference) findPreference(key); + Preference installHistory = findPreference("installHistory"); if (p.isChecked()) { InstallHistoryService.register(getActivity()); + installHistory.setVisible(true); } else { InstallHistoryService.unregister(getActivity()); + installHistory.setVisible(false); } break; } diff --git a/app/src/main/res/layout/activity_install_history.xml b/app/src/main/res/layout/activity_install_history.xml new file mode 100644 index 000000000..38adba30f --- /dev/null +++ b/app/src/main/res/layout/activity_install_history.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/install_history.xml b/app/src/main/res/menu/install_history.xml new file mode 100644 index 000000000..7dd18dbb9 --- /dev/null +++ b/app/src/main/res/menu/install_history.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2a2912ef..f16836e0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,6 +12,10 @@ by %s Delete Enable NFC Send… + Prompt to send crash reports + Gather data about crashes and ask to send them to the + developer + Keep cached apps Updates Unstable updates @@ -20,8 +24,13 @@ Prevent all actions from showing in the status bar and notification drawer. + Install history + View the private log of all installs and uninstalls Keep install history Store a log of all installs and uninstalls in a private store + Send version and UUID to servers + Include this app\'s version and a random, unique ID when + downloading, takes affect next app restart. Force old index format In case there are bugs or compatibility issues, use the XML app index Other diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3d9e4526f..8d49dc77b 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -24,6 +24,16 @@ android:targetPackage="@string/applicationId" android:targetClass="org.fdroid.fdroid.views.ManageReposActivity"/> + + + @@ -116,7 +126,11 @@ - + - +