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 @@
-
+
-
+