From b1c3d647484ce5c2a5a316da35c5ba9d6a55b461 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 13 Jun 2018 17:45:18 +0200 Subject: [PATCH 1/3] add expert option to send debug version/UUID on each HTTP download When debugging issues, tracking the client can be quite useful. This makes an "Expert" preference that adds the app version and a randomly generated, stored UUID to the query string each time it downloads an index or an app package. This is also useful in whitelabeling, for use cases where there needs to be some kind of identifier to make it work. --- app/src/basic/res/xml/preferences.xml | 6 ++++ .../java/org/fdroid/fdroid/FDroidApp.java | 35 ++++++++++++++++++- .../java/org/fdroid/fdroid/Preferences.java | 9 +++++ .../java/org/fdroid/fdroid/RepoUpdater.java | 7 +--- .../org/fdroid/fdroid/net/HttpDownloader.java | 32 ++++++++++++++++- app/src/main/res/values/strings.xml | 3 ++ app/src/main/res/xml/preferences.xml | 6 ++++ 7 files changed, 90 insertions(+), 8 deletions(-) diff --git a/app/src/basic/res/xml/preferences.xml b/app/src/basic/res/xml/preferences.xml index 6744145bb..ceccd9c9f 100644 --- a/app/src/basic/res/xml/preferences.xml +++ b/app/src/basic/res/xml/preferences.xml @@ -150,6 +150,12 @@ android:summary="@string/hide_all_notifications_summary" android:defaultValue="false" android:dependency="expert"/> + + * 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, @@ -480,6 +489,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..3a6a27074 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -106,6 +106,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; @@ -498,6 +499,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/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/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2a2912ef..5b71f95a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,9 @@ 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..3e9c38c74 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -171,6 +171,12 @@ android:summary="@string/hide_all_notifications_summary" android:defaultValue="false" android:dependency="expert"/> + Date: Wed, 13 Jun 2018 14:34:47 +0200 Subject: [PATCH 2/3] allow user to disable ACRA entirely with a preference ACRA is sometimes problematic or annoying. Also, whitelabel versions might want to disable ACRA. The setting the preference defaultValue is an easy way to do that. refs #1483 --- app/src/basic/res/xml/preferences.xml | 7 +++++-- app/src/main/java/org/fdroid/fdroid/FDroidApp.java | 8 +++++--- app/src/main/java/org/fdroid/fdroid/Preferences.java | 5 +++++ app/src/main/res/values/strings.xml | 4 ++++ app/src/main/res/xml/preferences.xml | 7 +++++-- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/src/basic/res/xml/preferences.xml b/app/src/basic/res/xml/preferences.xml index ceccd9c9f..026a8ed0c 100644 --- a/app/src/basic/res/xml/preferences.xml +++ b/app/src/basic/res/xml/preferences.xml @@ -110,13 +110,16 @@ - + - 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 diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3e9c38c74..2a9309d09 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -116,7 +116,11 @@ - + - Date: Mon, 25 Jun 2018 23:18:01 +0200 Subject: [PATCH 3/3] basic Install History viewer, available only when logging is enabled This provides a super simple way to view the install history directly in F-Droid. --- app/src/basic/res/xml/preferences.xml | 10 ++ app/src/main/AndroidManifest.xml | 6 ++ .../installer/InstallHistoryService.java | 5 +- .../fdroid/views/InstallHistoryActivity.java | 92 +++++++++++++++++++ .../views/fragments/PreferencesFragment.java | 3 + .../res/layout/activity_install_history.xml | 40 ++++++++ app/src/main/res/menu/install_history.xml | 7 ++ app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 10 ++ 9 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/fdroid/fdroid/views/InstallHistoryActivity.java create mode 100644 app/src/main/res/layout/activity_install_history.xml create mode 100644 app/src/main/res/menu/install_history.xml diff --git a/app/src/basic/res/xml/preferences.xml b/app/src/basic/res/xml/preferences.xml index 026a8ed0c..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"/> + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 457f02c1e..bff6cbc32 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -550,6 +550,12 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".views.main.MainActivity"/> + + + 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/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 f5a44f9c7..f16836e0c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,8 @@ 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 diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 2a9309d09..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"/> + + +