diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8838315b1..4aabb1e92 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,6 +47,7 @@ connected10: # this file changes every time but should not be cached - rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock - exit $EXITVALUE + allow_failure: true # remove once install segfaults are gone connected17: variables: diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 882654abd..9d9c19eed 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -139,7 +139,9 @@ public class FDroidApp extends Application { } /** - * Initialize the settings needed to run a local swap repo. + * Initialize the settings needed to run a local swap repo. This should + * only ever be called in {@link org.fdroid.fdroid.net.WifiStateChangeService.WifiInfoThread}, + * after the single init call in {@link FDroidApp#onCreate()}. */ public static void initWifiSettings() { port = 8888; diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java index e720a6031..d626e2983 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java @@ -57,7 +57,9 @@ public class RepoUpdater { @NonNull private final Repo repo; private boolean hasChanged; + @Nullable + private ProgressListener downloadProgressListener; private ProgressListener committingProgressListener; private ProgressListener processXmlProgressListener; private String cacheTag; @@ -83,6 +85,10 @@ public class RepoUpdater { this.indexUrl = url; } + public void setDownloadProgressListener(ProgressListener progressListener) { + this.downloadProgressListener = progressListener; + } + public void setProcessXmlProgressListener(ProgressListener progressListener) { this.processXmlProgressListener = progressListener; } @@ -100,6 +106,7 @@ public class RepoUpdater { try { downloader = DownloaderFactory.create(context, indexUrl); downloader.setCacheTag(repo.lastetag); + downloader.setListener(downloadProgressListener); downloader.download(); if (downloader.isCached()) { diff --git a/app/src/main/java/org/fdroid/fdroid/UpdateService.java b/app/src/main/java/org/fdroid/fdroid/UpdateService.java index 8dc291305..3f163f438 100644 --- a/app/src/main/java/org/fdroid/fdroid/UpdateService.java +++ b/app/src/main/java/org/fdroid/fdroid/UpdateService.java @@ -49,8 +49,6 @@ import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.installer.InstallManagerService; -import org.fdroid.fdroid.net.Downloader; -import org.fdroid.fdroid.net.DownloaderService; import java.net.URL; import java.util.ArrayList; @@ -166,7 +164,6 @@ public class UpdateService extends IntentService { public void onDestroy() { super.onDestroy(); notificationManager.cancel(NOTIFY_ID_UPDATING); - localBroadcastManager.unregisterReceiver(downloadProgressReceiver); localBroadcastManager.unregisterReceiver(updateStatusReceiver); } @@ -195,26 +192,6 @@ public class UpdateService extends IntentService { LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } - private final BroadcastReceiver downloadProgressReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String repoAddress = intent.getDataString(); - int downloadedSize = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, -1); - String downloadedSizeFriendly = Utils.getFriendlySize(downloadedSize); - int totalSize = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, -1); - int percent = (int) ((double) downloadedSize / totalSize * 100); - String message; - if (totalSize == -1) { - message = getString(R.string.status_download_unknown_size, repoAddress, downloadedSizeFriendly); - percent = -1; - } else { - String totalSizeFriendly = Utils.getFriendlySize(totalSize); - message = getString(R.string.status_download, repoAddress, downloadedSizeFriendly, totalSizeFriendly, percent); - } - sendStatus(context, STATUS_INFO, message, percent); - } - }; - // For receiving results from the UpdateService when we've told it to // update in response to a user request. private final BroadcastReceiver updateStatusReceiver = new BroadcastReceiver() { @@ -375,10 +352,7 @@ public class UpdateService extends IntentService { sendStatus(this, STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address)); RepoUpdater updater = new RepoUpdater(getBaseContext(), repo); - localBroadcastManager.registerReceiver(downloadProgressReceiver, - DownloaderService.getIntentFilter(updater.indexUrl, Downloader.ACTION_PROGRESS)); - updater.setProcessXmlProgressListener(processXmlProgressListener); - updater.setCommittingProgressListener(committingProgressListener); + setProgressListeners(updater); try { updater.update(); if (updater.hasChanged()) { @@ -392,7 +366,6 @@ public class UpdateService extends IntentService { repoErrors.add(e.getMessage()); Log.e(TAG, "Error updating repository " + repo.address, e); } - localBroadcastManager.unregisterReceiver(downloadProgressReceiver); // now that downloading the index is done, start downloading updates if (changes && fdroidPrefs.isAutoDownloadEnabled()) { @@ -528,25 +501,50 @@ public class UpdateService extends IntentService { notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build()); } - private final ProgressListener processXmlProgressListener = new ProgressListener() { - @Override - public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) { - String downloadedSize = Utils.getFriendlySize(bytesRead); - String totalSize = Utils.getFriendlySize(totalBytes); - int percent = -1; - if (totalBytes > 0) { - percent = (int) ((double) bytesRead / totalBytes * 100); + /** + * Set up the various {@link ProgressListener}s needed to get feedback to the UI. + * Note: {@code ProgressListener}s do not need to be unregistered, they can just + * be set again for each download. + */ + private void setProgressListeners(RepoUpdater updater) { + updater.setDownloadProgressListener(new ProgressListener() { + @Override + public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) { + Log.i(TAG, "downloadProgressReceiver " + sourceUrl); + String downloadedSizeFriendly = Utils.getFriendlySize(bytesRead); + int percent = (int) ((double) bytesRead / totalBytes * 100); + String message; + if (totalBytes == -1) { + message = getString(R.string.status_download_unknown_size, sourceUrl, downloadedSizeFriendly); + percent = -1; + } else { + String totalSizeFriendly = Utils.getFriendlySize(totalBytes); + message = getString(R.string.status_download, sourceUrl, downloadedSizeFriendly, totalSizeFriendly, percent); + } + sendStatus(getApplicationContext(), STATUS_INFO, message, percent); } - String message = getString(R.string.status_processing_xml_percent, sourceUrl, downloadedSize, totalSize, percent); - sendStatus(getApplicationContext(), STATUS_INFO, message, percent); - } - }; + }); - private final ProgressListener committingProgressListener = new ProgressListener() { - @Override - public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) { - String message = getString(R.string.status_inserting_apps); - sendStatus(getApplicationContext(), STATUS_INFO, message); - } - }; + updater.setProcessXmlProgressListener(new ProgressListener() { + @Override + public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) { + String downloadedSize = Utils.getFriendlySize(bytesRead); + String totalSize = Utils.getFriendlySize(totalBytes); + int percent = -1; + if (totalBytes > 0) { + percent = (int) ((double) bytesRead / totalBytes * 100); + } + String message = getString(R.string.status_processing_xml_percent, sourceUrl, downloadedSize, totalSize, percent); + sendStatus(getApplicationContext(), STATUS_INFO, message, percent); + } + }); + + updater.setCommittingProgressListener(new ProgressListener() { + @Override + public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) { + String message = getString(R.string.status_inserting_apps); + sendStatus(getApplicationContext(), STATUS_INFO, message); + } + }); + } } diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index dda0ae0ba..e53460ab3 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -21,8 +21,12 @@ import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class App extends ValueObject implements Comparable { @@ -108,12 +112,17 @@ public class App extends ValueObject implements Comparable { public boolean uninstallable; + public static String getIconName(String packageName, int versionCode) { + return packageName + "_" + versionCode + ".png"; + } + @Override public int compareTo(App app) { return name.compareToIgnoreCase(app.name); } - public App() { } + public App() { + } public App(Cursor cursor) { @@ -273,21 +282,35 @@ public class App extends ValueObject implements Comparable { + ", last updated on " + this.lastUpdated + ")

"; this.name = (String) appInfo.loadLabel(pm); + this.icon = getIconName(packageName, packageInfo.versionCode); - final SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir); final Apk apk = new Apk(); apk.versionName = packageInfo.versionName; apk.versionCode = packageInfo.versionCode; - apk.hashType = "sha256"; - apk.hash = Utils.getBinaryHash(apkFile, apk.hashType); apk.added = this.added; apk.minSdkVersion = Utils.getMinSdkVersion(context, packageName); apk.maxSdkVersion = Utils.getMaxSdkVersion(context, packageName); apk.packageName = this.packageName; - apk.installedFile = apkFile; apk.permissions = Utils.CommaSeparatedList.make(packageInfo.requestedPermissions); apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk"; + final SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir); + apk.hashType = "sha256"; + apk.hash = Utils.getBinaryHash(apkFile, apk.hashType); + apk.installedFile = apkFile; + + JarFile jarFile = new JarFile(apkFile); + HashSet abis = new HashSet<>(3); + Pattern pattern = Pattern.compile("^lib/([a-z0-9-]+)/.*"); + for (Enumeration jarEntries = jarFile.entries(); jarEntries.hasMoreElements();) { + JarEntry jarEntry = jarEntries.nextElement(); + Matcher matcher = pattern.matcher(jarEntry.getName()); + if (matcher.matches()) { + abis.add(matcher.group(1)); + } + } + apk.nativecode = Utils.CommaSeparatedList.make(abis.toArray(new String[abis.size()])); + final FeatureInfo[] features = packageInfo.reqFeatures; if (features != null && features.length > 0) { final String[] featureNames = new String[features.length]; diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index 4c65692e0..322f8ab0e 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -15,7 +15,7 @@ import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; -import android.util.Log; +import android.widget.Toast; import org.fdroid.fdroid.AppDetails; import org.fdroid.fdroid.R; @@ -135,12 +135,23 @@ public class InstallManagerService extends Service { Utils.debugLog(TAG, "onStartCommand " + intent); if (!ACTION_INSTALL.equals(intent.getAction())) { - Log.i(TAG, "Ignoring " + intent + " as it is not an " + ACTION_INSTALL + " intent"); + Utils.debugLog(TAG, "Ignoring " + intent + " as it is not an " + ACTION_INSTALL + " intent"); return START_NOT_STICKY; } String urlString = intent.getDataString(); + if (TextUtils.isEmpty(urlString)) { + Utils.debugLog(TAG, "empty urlString, nothing to do"); + return START_NOT_STICKY; + } + Apk apk = ACTIVE_APKS.get(urlString); + if (apk == null) { + Utils.debugLog(TAG, urlString + " is not in ACTIVE_APKS, why are we trying to download it?"); + Toast.makeText(this, urlString + " failed with an imcomplete download request!", + Toast.LENGTH_LONG).show(); + return START_NOT_STICKY; + } Notification notification = createNotification(intent.getDataString(), apk).build(); notificationManager.notify(urlString.hashCode(), notification); diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java index faccfd992..f45a92656 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -3,7 +3,6 @@ package org.fdroid.fdroid.localrepo; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.graphics.Bitmap; @@ -237,6 +236,13 @@ public final class LocalRepoManager { } } + /** + * Get the {@code index.jar} file that represents the local swap repo. + */ + public File getIndexJar() { + return xmlIndexJar; + } + public void deleteRepo() { deleteContents(repoDir); } @@ -267,8 +273,6 @@ public final class LocalRepoManager { if (!app.isValid()) { return; } - PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA); - app.icon = getIconFile(packageName, packageInfo.versionCode).getName(); } catch (PackageManager.NameNotFoundException | CertificateEncodingException | IOException e) { Log.e(TAG, "Error adding app to local repo", e); return; @@ -277,10 +281,6 @@ public final class LocalRepoManager { apps.put(packageName, app); } - public List getApps() { - return new ArrayList<>(apps.keySet()); - } - public void copyIconsToRepo() { ApplicationInfo appInfo; for (final App app : apps.values()) { @@ -321,7 +321,7 @@ public final class LocalRepoManager { } private File getIconFile(String packageName, int versionCode) { - return new File(iconsDir, packageName + "_" + versionCode + ".png"); + return new File(iconsDir, App.getIconName(packageName, versionCode)); } /** @@ -456,6 +456,7 @@ public final class LocalRepoManager { tag("added", app.installedApk.added); tagFeatures(app); tagPermissions(app); + tagNativecode(app); serializer.endTag("", "package"); } @@ -485,6 +486,14 @@ public final class LocalRepoManager { serializer.endTag("", "features"); } + private void tagNativecode(App app) throws IOException { + if (app.installedApk.nativecode != null) { + serializer.startTag("", "nativecode"); + serializer.text(Utils.CommaSeparatedList.str(app.installedApk.nativecode)); + serializer.endTag("", "nativecode"); + } + } + private void tagHash(App app) throws IOException { serializer.startTag("", "hash"); serializer.attribute("", "type", app.installedApk.hashType); diff --git a/app/src/main/java/org/fdroid/fdroid/net/WifiStateChangeService.java b/app/src/main/java/org/fdroid/fdroid/net/WifiStateChangeService.java index dda912c58..bd624427d 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/app/src/main/java/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -53,7 +53,6 @@ public class WifiStateChangeService extends IntentService { protected void onHandleIntent(Intent intent) { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); Utils.debugLog(TAG, "WiFi change service started, clearing info about wifi state until we have figured it out again."); - FDroidApp.initWifiSettings(); NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); wifiManager = (WifiManager) getSystemService(WIFI_SERVICE); int wifiState = wifiManager.getWifiState(); @@ -79,6 +78,7 @@ public class WifiStateChangeService extends IntentService { public void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); try { + FDroidApp.initWifiSettings(); Utils.debugLog(TAG, "Checking wifi state (in background thread)."); WifiInfo wifiInfo = null; diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java index bdb34a58a..e40d6c09f 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java @@ -245,6 +245,9 @@ public class SwapAppsView extends ListView implements private final BroadcastReceiver downloadProgressReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (progressView.getVisibility() != View.VISIBLE) { + showProgress(); + } int read = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0); int total = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0); if (total > 0) { diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 6062d643e..d789ac7d0 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -376,7 +376,10 @@ public class SwapWorkflowActivity extends AppCompatActivity { getService().swapWith(null); if (!getService().isEnabled()) { - prepareInitialRepo(); + if (!LocalRepoManager.get(this).getIndexJar().exists()) { + Utils.debugLog(TAG, "Preparing initial repo with only F-Droid, until we have allowed the user to configure their own repo."); + new PrepareInitialSwapRepo().execute(); + } } inflateInnerView(R.layout.swap_blank); @@ -452,16 +455,6 @@ public class SwapWorkflowActivity extends AppCompatActivity { } } - private void prepareInitialRepo() { - // TODO: Make it so that this and updateSwappableAppsTask (the _real_ swap repo task) - // don't stomp on eachothers toes. The other one should wait for this to finish, or cancel - // this, but this should never take precedence over the other. - // TODO: Also don't allow this to run multiple times (e.g. if a user keeps navigating back - // to the main screen. - Utils.debugLog(TAG, "Preparing initial repo with only F-Droid, until we have allowed the user to configure their own repo."); - new PrepareInitialSwapRepo().execute(); - } - /** * Once the UpdateAsyncTask has finished preparing our repository index, we can * show the next screen to the user. This will be one of two things: