Merge branch 'bug-fixes-1.7' into 'master'
Bug fixes 1.7 Closes #1678 and #1757 See merge request fdroid/fdroidclient!820
This commit is contained in:
		
						commit
						fac36457ea
					
				@ -42,6 +42,7 @@
 | 
			
		||||
            android:id="@+id/find_people_button"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:maxEms="16"
 | 
			
		||||
            android:text="@string/nearby_splash__find_people_button"
 | 
			
		||||
            style="@style/DetailsSecondaryButtonStyle"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/title"
 | 
			
		||||
@ -85,6 +86,7 @@
 | 
			
		||||
            android:id="@+id/request_read_external_storage_button"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:maxEms="16"
 | 
			
		||||
            android:text="@string/nearby_splash__request_permission"
 | 
			
		||||
            style="@style/DetailsSecondaryButtonStyle"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,9 @@
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:backgroundTint="@color/swap_light_blue"
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:maxEms="10"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:singleLine="true"
 | 
			
		||||
            android:text="@string/menu_install"
 | 
			
		||||
            tools:ignore="UnusedAttribute" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -28,13 +28,11 @@ public class DeleteCacheService extends JobIntentService {
 | 
			
		||||
        Log.w(TAG, "Deleting all cached contents!");
 | 
			
		||||
        try {
 | 
			
		||||
            File cacheDir = getCacheDir();
 | 
			
		||||
            if (cacheDir != null) {
 | 
			
		||||
                FileUtils.deleteDirectory(cacheDir);
 | 
			
		||||
            }
 | 
			
		||||
            FileUtils.deleteDirectory(cacheDir);
 | 
			
		||||
            for (File dir : ContextCompat.getExternalCacheDirs(this)) {
 | 
			
		||||
                FileUtils.deleteDirectory(dir);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
        } catch (Throwable e) { // NOPMD
 | 
			
		||||
            // ignored
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -414,7 +414,7 @@ public class IndexUpdater {
 | 
			
		||||
 | 
			
		||||
        Utils.debugLog(TAG, "Saving new signing certificate in the database for " + repo.address);
 | 
			
		||||
        ContentValues values = new ContentValues(2);
 | 
			
		||||
        values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatDate(new Date(), ""));
 | 
			
		||||
        values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
 | 
			
		||||
        values.put(RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
 | 
			
		||||
        RepoProvider.Helper.update(context, repo, values);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -470,7 +470,7 @@ public class IndexV1Updater extends IndexUpdater {
 | 
			
		||||
            }
 | 
			
		||||
            Utils.debugLog(TAG, "Saving new signing certificate to database for " + repo.address);
 | 
			
		||||
            ContentValues values = new ContentValues(2);
 | 
			
		||||
            values.put(Schema.RepoTable.Cols.LAST_UPDATED, Utils.formatDate(new Date(), ""));
 | 
			
		||||
            values.put(Schema.RepoTable.Cols.LAST_UPDATED, Utils.formatTime(new Date(), ""));
 | 
			
		||||
            values.put(Schema.RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
 | 
			
		||||
            RepoProvider.Helper.update(context, repo, values);
 | 
			
		||||
            repo.signingCertificate = certFromJar;
 | 
			
		||||
 | 
			
		||||
@ -236,7 +236,7 @@ public class UpdateService extends JobIntentService {
 | 
			
		||||
                    Utils.debugLog(TAG, "scheduling update because there is good internet");
 | 
			
		||||
                    schedule(context);
 | 
			
		||||
                }
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
            } catch (Throwable e) { // NOPMD
 | 
			
		||||
                Utils.debugLog(TAG, e.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
            isScheduleIfStillOnWifiRunning = false;
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,7 @@ import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.TimeZone;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
@ -96,6 +97,8 @@ public final class Utils {
 | 
			
		||||
    private static final SimpleDateFormat TIME_FORMAT =
 | 
			
		||||
            new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.ENGLISH);
 | 
			
		||||
 | 
			
		||||
    private static final TimeZone UTC = TimeZone.getTimeZone("Etc/GMT");
 | 
			
		||||
 | 
			
		||||
    private static final String[] FRIENDLY_SIZE_FORMAT = {
 | 
			
		||||
            "%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB",
 | 
			
		||||
    };
 | 
			
		||||
@ -384,7 +387,7 @@ public final class Utils {
 | 
			
		||||
            }
 | 
			
		||||
            ret = formatter.toString();
 | 
			
		||||
            formatter.close();
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
        } catch (Throwable e) { // NOPMD
 | 
			
		||||
            Log.w(TAG, "Unable to get certificate fingerprint", e);
 | 
			
		||||
        }
 | 
			
		||||
        return ret;
 | 
			
		||||
@ -583,6 +586,7 @@ public final class Utils {
 | 
			
		||||
        }
 | 
			
		||||
        Date result;
 | 
			
		||||
        try {
 | 
			
		||||
            format.setTimeZone(UTC);
 | 
			
		||||
            result = format.parse(str);
 | 
			
		||||
        } catch (ArrayIndexOutOfBoundsException | NumberFormatException | ParseException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
@ -595,21 +599,34 @@ public final class Utils {
 | 
			
		||||
        if (date == null) {
 | 
			
		||||
            return fallback;
 | 
			
		||||
        }
 | 
			
		||||
        format.setTimeZone(UTC);
 | 
			
		||||
        return format.format(date);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parses a date string into UTC time
 | 
			
		||||
     */
 | 
			
		||||
    public static Date parseDate(String str, Date fallback) {
 | 
			
		||||
        return parseDateFormat(DATE_FORMAT, str, fallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Formats UTC time into a date string
 | 
			
		||||
     */
 | 
			
		||||
    public static String formatDate(Date date, String fallback) {
 | 
			
		||||
        return formatDateFormat(DATE_FORMAT, date, fallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parses a date/time string into UTC time
 | 
			
		||||
     */
 | 
			
		||||
    public static Date parseTime(String str, Date fallback) {
 | 
			
		||||
        return parseDateFormat(TIME_FORMAT, str, fallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Formats UTC time into a date/time string
 | 
			
		||||
     */
 | 
			
		||||
    public static String formatTime(Date date, String fallback) {
 | 
			
		||||
        return formatDateFormat(TIME_FORMAT, date, fallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -165,7 +165,8 @@ public class Repo extends ValueObject {
 | 
			
		||||
                    inuse = cursor.getInt(i) == 1;
 | 
			
		||||
                    break;
 | 
			
		||||
                case Cols.LAST_UPDATED:
 | 
			
		||||
                    lastUpdated = Utils.parseTime(cursor.getString(i), null);
 | 
			
		||||
                    String dateString = cursor.getString(i);
 | 
			
		||||
                    lastUpdated = Utils.parseTime(dateString, Utils.parseDate(dateString, null));
 | 
			
		||||
                    break;
 | 
			
		||||
                case Cols.MAX_AGE:
 | 
			
		||||
                    maxage = cursor.getInt(i);
 | 
			
		||||
@ -296,7 +297,7 @@ public class Repo extends ValueObject {
 | 
			
		||||
 | 
			
		||||
        if (values.containsKey(Cols.LAST_UPDATED)) {
 | 
			
		||||
            final String dateString = values.getAsString(Cols.LAST_UPDATED);
 | 
			
		||||
            lastUpdated = Utils.parseTime(dateString, null);
 | 
			
		||||
            lastUpdated = Utils.parseTime(dateString, Utils.parseDate(dateString, null));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (values.containsKey(Cols.MAX_AGE)) {
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@ import android.support.annotation.NonNull;
 | 
			
		||||
import android.support.annotation.Nullable;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
 | 
			
		||||
import org.fdroid.fdroid.AppUpdateStatusManager;
 | 
			
		||||
import org.fdroid.fdroid.Utils;
 | 
			
		||||
import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
			
		||||
@ -278,7 +277,8 @@ public class RepoProvider extends FDroidProvider {
 | 
			
		||||
            if (cursor != null) {
 | 
			
		||||
                if (cursor.getCount() > 0) {
 | 
			
		||||
                    cursor.moveToFirst();
 | 
			
		||||
                    lastUpdate = Utils.parseDate(cursor.getString(0), null);
 | 
			
		||||
                    String dateString = cursor.getString(0);
 | 
			
		||||
                    lastUpdate = Utils.parseTime(dateString, Utils.parseDate(dateString, null));
 | 
			
		||||
                }
 | 
			
		||||
                cursor.close();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -265,7 +265,7 @@ public class PrivilegedInstaller extends Installer {
 | 
			
		||||
        PackageManager pm = context.getPackageManager();
 | 
			
		||||
        try {
 | 
			
		||||
            pm.getPackageInfo(PRIVILEGED_EXTENSION_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
 | 
			
		||||
            return true;
 | 
			
		||||
            return pm.getApplicationInfo(PRIVILEGED_EXTENSION_PACKAGE_NAME, 0).enabled;
 | 
			
		||||
        } catch (PackageManager.NameNotFoundException e) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -555,11 +555,16 @@ public class AppDetailsRecyclerViewAdapter
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            updateAntiFeaturesWarning();
 | 
			
		||||
            buttonSecondaryView.setText(R.string.menu_uninstall);
 | 
			
		||||
            buttonSecondaryView.setVisibility(app.isUninstallable(context) ? View.VISIBLE : View.INVISIBLE);
 | 
			
		||||
            buttonSecondaryView.setOnClickListener(onUnInstallClickListener);
 | 
			
		||||
            buttonPrimaryView.setText(R.string.menu_install);
 | 
			
		||||
            buttonPrimaryView.setVisibility(versions.size() > 0 ? View.VISIBLE : View.GONE);
 | 
			
		||||
            buttonSecondaryView.setText(R.string.menu_uninstall);
 | 
			
		||||
            buttonSecondaryView.setVisibility(app.isUninstallable(context) ? View.VISIBLE : View.INVISIBLE);
 | 
			
		||||
            buttonSecondaryView.setOnClickListener(new View.OnClickListener() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onClick(View v) {
 | 
			
		||||
                    callbacks.uninstallApk();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (callbacks.isAppDownloading()) {
 | 
			
		||||
                buttonPrimaryView.setText(R.string.downloading);
 | 
			
		||||
                buttonPrimaryView.setEnabled(false);
 | 
			
		||||
@ -568,22 +573,37 @@ public class AppDetailsRecyclerViewAdapter
 | 
			
		||||
            } else if (!app.isInstalled(context) && suggestedApk != null) {
 | 
			
		||||
                // Check count > 0 due to incompatible apps resulting in an empty list.
 | 
			
		||||
                callbacks.disableAndroidBeam();
 | 
			
		||||
                progressLayout.setVisibility(View.GONE);
 | 
			
		||||
                // Set Install button and hide second button
 | 
			
		||||
                buttonPrimaryView.setText(R.string.menu_install);
 | 
			
		||||
                buttonPrimaryView.setOnClickListener(onInstallClickListener);
 | 
			
		||||
                buttonPrimaryView.setEnabled(true);
 | 
			
		||||
                buttonLayout.setVisibility(View.VISIBLE);
 | 
			
		||||
                progressLayout.setVisibility(View.GONE);
 | 
			
		||||
                buttonPrimaryView.setOnClickListener(new View.OnClickListener() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void onClick(View v) {
 | 
			
		||||
                        callbacks.installApk();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            } else if (app.isInstalled(context)) {
 | 
			
		||||
                callbacks.enableAndroidBeam();
 | 
			
		||||
                if (app.canAndWantToUpdate(context) && suggestedApk != null) {
 | 
			
		||||
                    buttonPrimaryView.setText(R.string.menu_upgrade);
 | 
			
		||||
                    buttonPrimaryView.setOnClickListener(onUpgradeClickListener);
 | 
			
		||||
                    buttonPrimaryView.setOnClickListener(new View.OnClickListener() {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void onClick(View v) {
 | 
			
		||||
                            callbacks.installApk();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    Apk mediaApk = app.getMediaApkifInstalled(context);
 | 
			
		||||
                    if (context.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
 | 
			
		||||
                        buttonPrimaryView.setText(R.string.menu_launch);
 | 
			
		||||
                        buttonPrimaryView.setOnClickListener(onLaunchClickListener);
 | 
			
		||||
                        buttonPrimaryView.setOnClickListener(new View.OnClickListener() {
 | 
			
		||||
                            @Override
 | 
			
		||||
                            public void onClick(View v) {
 | 
			
		||||
                                callbacks.launchApk();
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    } else if (!app.isApk && mediaApk != null) {
 | 
			
		||||
                        final File installedFile = new File(mediaApk.getMediaInstallPath(context), mediaApk.apkName);
 | 
			
		||||
                        if (!installedFile.toString().startsWith(context.getApplicationInfo().dataDir)) {
 | 
			
		||||
@ -1324,34 +1344,6 @@ public class AppDetailsRecyclerViewAdapter
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final View.OnClickListener onInstallClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            callbacks.installApk();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private final View.OnClickListener onUnInstallClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            callbacks.uninstallApk();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private final View.OnClickListener onUpgradeClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            callbacks.installApk();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private final View.OnClickListener onLaunchClickListener = new View.OnClickListener() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void onClick(View v) {
 | 
			
		||||
            callbacks.launchApk();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private boolean uriIsSetAndCanBeOpened(String s) {
 | 
			
		||||
        if (TextUtils.isEmpty(s)) {
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
@ -143,6 +143,9 @@
 | 
			
		||||
                android:layout_marginTop="5dp"
 | 
			
		||||
                android:layout_marginRight="4dp"
 | 
			
		||||
                android:layout_marginEnd="4dp"
 | 
			
		||||
                android:maxEms="10"
 | 
			
		||||
                android:ellipsize="end"
 | 
			
		||||
                android:singleLine="true"
 | 
			
		||||
                tools:text="@string/menu_install"/>
 | 
			
		||||
 | 
			
		||||
            <Button
 | 
			
		||||
 | 
			
		||||
@ -42,6 +42,7 @@
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginEnd="16dp"
 | 
			
		||||
        android:layout_marginRight="16dp"
 | 
			
		||||
        android:maxEms="10"
 | 
			
		||||
        android:text="@string/update_all"
 | 
			
		||||
        style="@style/DetailsPrimaryButtonStyle"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
@ -54,6 +55,8 @@
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_margin="8dp"
 | 
			
		||||
        android:ellipsize="middle"
 | 
			
		||||
        android:singleLine="true"
 | 
			
		||||
        tools:text="Show apps"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,8 @@ import org.robolectric.RuntimeEnvironment;
 | 
			
		||||
import org.robolectric.annotation.Config;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.TimeZone;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
@ -77,9 +79,9 @@ public class UtilsTest {
 | 
			
		||||
        assertEquals("three", tripleValue[2]);
 | 
			
		||||
 | 
			
		||||
        assertNull(Utils.serializeCommaSeparatedString(null));
 | 
			
		||||
        assertNull(Utils.serializeCommaSeparatedString(new String[] {}));
 | 
			
		||||
        assertEquals("Single", Utils.serializeCommaSeparatedString(new String[] {"Single"}));
 | 
			
		||||
        assertEquals("One,TWO,three", Utils.serializeCommaSeparatedString(new String[] {"One", "TWO", "three"}));
 | 
			
		||||
        assertNull(Utils.serializeCommaSeparatedString(new String[]{}));
 | 
			
		||||
        assertEquals("Single", Utils.serializeCommaSeparatedString(new String[]{"Single"}));
 | 
			
		||||
        assertEquals("One,TWO,three", Utils.serializeCommaSeparatedString(new String[]{"One", "TWO", "three"}));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
@ -192,4 +194,25 @@ public class UtilsTest {
 | 
			
		||||
    }
 | 
			
		||||
    // TODO write tests that work with a Certificate
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testIndexDatesWithTimeZones() {
 | 
			
		||||
        for (int h = 0; h < 12; h++) {
 | 
			
		||||
            for (int m = 0; m < 60; m = m + 15) {
 | 
			
		||||
                TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT+%d%02d", h, m)));
 | 
			
		||||
 | 
			
		||||
                String timeString = "2017-11-27_20:13:24";
 | 
			
		||||
                Date time = Utils.parseTime(timeString, null);
 | 
			
		||||
                assertEquals("The String representation must match", timeString, Utils.formatTime(time, null));
 | 
			
		||||
                assertEquals(timeString + " failed to parse", 1511813604000L, time.getTime());
 | 
			
		||||
                assertEquals("time zones should match", -((h * 60) + m), time.getTimezoneOffset());
 | 
			
		||||
 | 
			
		||||
                TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT+%d%02d", h, m)));
 | 
			
		||||
                String dateString = "2017-11-27";
 | 
			
		||||
                Date date = Utils.parseDate(dateString, null);
 | 
			
		||||
                assertEquals("The String representation must match", dateString, Utils.formatDate(date, null));
 | 
			
		||||
                assertEquals(dateString + " failed to parse", 1511740800000L, date.getTime());
 | 
			
		||||
                assertEquals("time zones should match", -((h * 60) + m), date.getTimezoneOffset());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,12 @@ import android.net.Uri;
 | 
			
		||||
import org.fdroid.fdroid.Assert;
 | 
			
		||||
import org.fdroid.fdroid.BuildConfig;
 | 
			
		||||
import org.fdroid.fdroid.TestUtils;
 | 
			
		||||
import org.fdroid.fdroid.Utils;
 | 
			
		||||
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
 | 
			
		||||
import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
			
		||||
import org.fdroid.fdroid.mock.MockApk;
 | 
			
		||||
import org.fdroid.fdroid.mock.MockRepo;
 | 
			
		||||
import org.junit.BeforeClass;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.robolectric.RobolectricTestRunner;
 | 
			
		||||
@ -18,6 +20,7 @@ import org.robolectric.annotation.Config;
 | 
			
		||||
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.TimeZone;
 | 
			
		||||
 | 
			
		||||
import static org.fdroid.fdroid.Assert.assertCantDelete;
 | 
			
		||||
import static org.fdroid.fdroid.Assert.assertResultCount;
 | 
			
		||||
@ -34,6 +37,13 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
			
		||||
 | 
			
		||||
    private static final String[] PROJ = Cols.ALL;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
    public static void setRandomTimeZone() {
 | 
			
		||||
        TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
 | 
			
		||||
                System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
 | 
			
		||||
        System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAppApks() {
 | 
			
		||||
        App fdroidApp = insertApp(context, "org.fdroid.fdroid", "F-Droid");
 | 
			
		||||
@ -153,7 +163,7 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testCount() {
 | 
			
		||||
        String[] projectionCount = new String[] {Cols._COUNT};
 | 
			
		||||
        String[] projectionCount = new String[]{Cols._COUNT};
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 13; i++) {
 | 
			
		||||
            Assert.insertApk(context, "com.example", i);
 | 
			
		||||
@ -315,12 +325,13 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
			
		||||
        assertNull(apk.added);
 | 
			
		||||
        assertNull(apk.hashType);
 | 
			
		||||
 | 
			
		||||
        apk.antiFeatures = new String[] {"KnownVuln", "Other anti feature"};
 | 
			
		||||
        apk.features = new String[] {"one", "two", "three" };
 | 
			
		||||
        long dateTimestamp = System.currentTimeMillis();
 | 
			
		||||
        apk.added = new Date(dateTimestamp);
 | 
			
		||||
        apk.antiFeatures = new String[]{"KnownVuln", "Other anti feature"};
 | 
			
		||||
        apk.features = new String[]{"one", "two", "three"};
 | 
			
		||||
        apk.hashType = "i'm a hash type";
 | 
			
		||||
 | 
			
		||||
        Date testTime = Utils.parseDate(Utils.formatTime(new Date(System.currentTimeMillis()), null), null);
 | 
			
		||||
        apk.added = testTime;
 | 
			
		||||
 | 
			
		||||
        ApkProvider.Helper.update(context, apk);
 | 
			
		||||
 | 
			
		||||
        // Should not have inserted anything else, just updated the already existing apk.
 | 
			
		||||
@ -340,9 +351,10 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
			
		||||
 | 
			
		||||
        assertArrayEquals(new String[]{"KnownVuln", "Other anti feature"}, updatedApk.antiFeatures);
 | 
			
		||||
        assertArrayEquals(new String[]{"one", "two", "three"}, updatedApk.features);
 | 
			
		||||
        assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
 | 
			
		||||
        assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
 | 
			
		||||
        assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
 | 
			
		||||
        assertEquals(testTime.getYear(), updatedApk.added.getYear());
 | 
			
		||||
        assertEquals(testTime.getYear(), updatedApk.added.getYear());
 | 
			
		||||
        assertEquals(testTime.getMonth(), updatedApk.added.getMonth());
 | 
			
		||||
        assertEquals(testTime.getDay(), updatedApk.added.getDay());
 | 
			
		||||
        assertEquals("i'm a hash type", updatedApk.hashType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -381,8 +393,8 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
			
		||||
        assertEquals("a hash type", apk.hashType);
 | 
			
		||||
 | 
			
		||||
        String[] projection = {
 | 
			
		||||
            Cols.Package.PACKAGE_NAME,
 | 
			
		||||
            Cols.HASH,
 | 
			
		||||
                Cols.Package.PACKAGE_NAME,
 | 
			
		||||
                Cols.HASH,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Apk apkLessFields = ApkProvider.Helper.findApkFromAnyRepo(context, "com.example", 11, null, projection);
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ import org.fdroid.fdroid.Preferences;
 | 
			
		||||
import org.fdroid.fdroid.TestUtils;
 | 
			
		||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.BeforeClass;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.robolectric.RobolectricTestRunner;
 | 
			
		||||
@ -19,6 +20,7 @@ import org.robolectric.shadows.ShadowContentResolver;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.TimeZone;
 | 
			
		||||
 | 
			
		||||
import static org.fdroid.fdroid.Assert.assertContainsOnly;
 | 
			
		||||
import static org.fdroid.fdroid.Assert.assertResultCount;
 | 
			
		||||
@ -36,6 +38,13 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
			
		||||
 | 
			
		||||
    private static final String[] PROJ = Cols.ALL;
 | 
			
		||||
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
    public static void setRandomTimeZone() {
 | 
			
		||||
        TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
 | 
			
		||||
                System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
 | 
			
		||||
        System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setup() {
 | 
			
		||||
        TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ import org.fdroid.fdroid.BuildConfig;
 | 
			
		||||
import org.fdroid.fdroid.R;
 | 
			
		||||
import org.fdroid.fdroid.Utils;
 | 
			
		||||
import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
			
		||||
import org.junit.BeforeClass;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.robolectric.RobolectricTestRunner;
 | 
			
		||||
@ -37,6 +38,7 @@ import org.robolectric.annotation.Config;
 | 
			
		||||
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.TimeZone;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertNotNull;
 | 
			
		||||
@ -48,6 +50,16 @@ public class RepoProviderTest extends FDroidProviderTest {
 | 
			
		||||
 | 
			
		||||
    private static final String[] COLS = RepoTable.Cols.ALL;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set to random time zone to make sure that the dates are properly parsed.
 | 
			
		||||
     */
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
    public static void setRandomTimeZone() {
 | 
			
		||||
        TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
 | 
			
		||||
                System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
 | 
			
		||||
        System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void countEnabledRepos() {
 | 
			
		||||
 | 
			
		||||
@ -95,7 +107,7 @@ public class RepoProviderTest extends FDroidProviderTest {
 | 
			
		||||
 | 
			
		||||
    private Repo setLastUpdate(Repo repo, Date date) {
 | 
			
		||||
        ContentValues values = new ContentValues(1);
 | 
			
		||||
        values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatDate(date, null));
 | 
			
		||||
        values.put(RepoTable.Cols.LAST_UPDATED, Utils.formatTime(date, null));
 | 
			
		||||
        RepoProvider.Helper.update(context, repo, values);
 | 
			
		||||
        return RepoProvider.Helper.findByAddress(context, repo.address);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ import org.apache.commons.io.FileUtils;
 | 
			
		||||
import org.fdroid.fdroid.BuildConfig;
 | 
			
		||||
import org.fdroid.fdroid.mock.MockRepo;
 | 
			
		||||
import org.fdroid.fdroid.mock.RepoDetails;
 | 
			
		||||
import org.junit.BeforeClass;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
import org.robolectric.RobolectricTestRunner;
 | 
			
		||||
@ -48,6 +49,7 @@ import java.util.Collections;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.TimeZone;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
@ -63,6 +65,16 @@ public class RepoXMLHandlerTest {
 | 
			
		||||
 | 
			
		||||
    private static final String FAKE_SIGNING_CERT = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"; // NOCHECKSTYLE LineLength
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set to random time zone to make sure that the dates are properly parsed.
 | 
			
		||||
     */
 | 
			
		||||
    @BeforeClass
 | 
			
		||||
    public static void setRandomTimeZone() {
 | 
			
		||||
        TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
 | 
			
		||||
                System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
 | 
			
		||||
        System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testExtendedPerms() throws IOException {
 | 
			
		||||
        Repo expectedRepo = new Repo();
 | 
			
		||||
@ -129,6 +141,12 @@ public class RepoXMLHandlerTest {
 | 
			
		||||
                "org.gege.caldavsyncadapter",
 | 
			
		||||
                "info.guardianproject.checkey",
 | 
			
		||||
        });
 | 
			
		||||
        for (App app : actualDetails.apps) {
 | 
			
		||||
            if ("org.mozilla.firefox".equals(app.packageName)) {
 | 
			
		||||
                assertEquals(1411776000000L, app.added.getTime());
 | 
			
		||||
                assertEquals(1411862400000L, app.lastUpdated.getTime());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(expected = IllegalArgumentException.class)
 | 
			
		||||
@ -897,6 +915,10 @@ public class RepoXMLHandlerTest {
 | 
			
		||||
        List<App> apps = actualDetails.apps;
 | 
			
		||||
        assertNotNull(apps);
 | 
			
		||||
        assertEquals(apps.size(), appCount);
 | 
			
		||||
        for (App app : apps) {
 | 
			
		||||
            assertTrue("Added should have been set", app.added.getTime() > 0);
 | 
			
		||||
            assertTrue("Last Updated should have been set", app.lastUpdated.getTime() > 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        List<Apk> apks = actualDetails.apks;
 | 
			
		||||
        assertNotNull(apks);
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										48
									
								
								tools/check-string-maxlength.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										48
									
								
								tools/check-string-maxlength.py
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
# Remove extra translations
 | 
			
		||||
 | 
			
		||||
import glob
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import re
 | 
			
		||||
from xml.etree import ElementTree
 | 
			
		||||
 | 
			
		||||
maxlengths = {
 | 
			
		||||
    "menu_install": 20,
 | 
			
		||||
    "menu_uninstall": 20,
 | 
			
		||||
    "nearby_splash__find_people_button": 30,
 | 
			
		||||
    "nearby_splash__request_permission": 30,
 | 
			
		||||
    "update_all": 20,
 | 
			
		||||
    "updates__hide_updateable_apps": 35,
 | 
			
		||||
    "updates__show_updateable_apps": 35,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
resdir = os.path.join(os.path.dirname(__file__), '..', 'app', 'src', 'main', 'res')
 | 
			
		||||
 | 
			
		||||
count = 0
 | 
			
		||||
 | 
			
		||||
for d in sorted(glob.glob(os.path.join(resdir, 'values-*'))):
 | 
			
		||||
    locale = d.split('/')[-1][7:]
 | 
			
		||||
 | 
			
		||||
    str_path = os.path.join(d, 'strings.xml')
 | 
			
		||||
    if not os.path.exists(str_path):
 | 
			
		||||
        continue
 | 
			
		||||
 | 
			
		||||
    with open(str_path, encoding='utf-8') as fp:
 | 
			
		||||
        fulltext = fp.read()
 | 
			
		||||
 | 
			
		||||
    tree = ElementTree.parse(str_path)
 | 
			
		||||
    root = tree.getroot()
 | 
			
		||||
 | 
			
		||||
    for e in root.findall('.//string'):
 | 
			
		||||
 | 
			
		||||
        if maxlengths.get(e.attrib['name']) is not None \
 | 
			
		||||
           and len(e.text) > maxlengths.get(e.attrib['name']):
 | 
			
		||||
            print(e.attrib['name'], locale, str(len(e.text)) + ':\t\t"' + e.text + '"')
 | 
			
		||||
 | 
			
		||||
if count > 0:
 | 
			
		||||
    print("%d over-long strings found!" % count)
 | 
			
		||||
    sys.exit(count)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										82
									
								
								tools/pick-complete-translations.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										82
									
								
								tools/pick-complete-translations.py
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
#!/usr/bin/python3
 | 
			
		||||
#
 | 
			
		||||
# cherry-pick complete translations from weblate
 | 
			
		||||
 | 
			
		||||
import git
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import requests
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_paths_tuple(locale):
 | 
			
		||||
    return (
 | 
			
		||||
        'metadata/%s/*.txt' % locale,
 | 
			
		||||
        'metadata/%s/changelogs/*.txt' % locale,
 | 
			
		||||
        'app/src/main/res/values-%s/strings.xml' % re.sub(r'-', r'-r', locale),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
projectbasedir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 | 
			
		||||
print(projectbasedir)
 | 
			
		||||
 | 
			
		||||
repo = git.Repo(projectbasedir)
 | 
			
		||||
weblate = repo.remotes.weblate
 | 
			
		||||
weblate.fetch()
 | 
			
		||||
upstream = repo.remotes.upstream
 | 
			
		||||
upstream.fetch()
 | 
			
		||||
 | 
			
		||||
url = 'https://hosted.weblate.org/exports/stats/f-droid/f-droid/?format=json'
 | 
			
		||||
r = requests.get(url)
 | 
			
		||||
r.raise_for_status()
 | 
			
		||||
app = r.json()
 | 
			
		||||
 | 
			
		||||
url = 'https://hosted.weblate.org/exports/stats/f-droid/f-droid-metadata/?format=json'
 | 
			
		||||
r = requests.get(url)
 | 
			
		||||
r.raise_for_status()
 | 
			
		||||
metadata = r.json()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#with open('f-droid-metadata.json') as fp:
 | 
			
		||||
#    metadata = json.load(fp)
 | 
			
		||||
 | 
			
		||||
app_locales = dict()
 | 
			
		||||
metadata_locales = dict()
 | 
			
		||||
 | 
			
		||||
merge_locales = []
 | 
			
		||||
for locale in app:
 | 
			
		||||
    app_locales[locale['code']] = locale
 | 
			
		||||
for locale in metadata:
 | 
			
		||||
    metadata_locales[locale['code']] = locale
 | 
			
		||||
 | 
			
		||||
for locale in sorted(app_locales.keys(), reverse=True):
 | 
			
		||||
    a = app_locales.get(locale)
 | 
			
		||||
    m = metadata_locales.get(locale)
 | 
			
		||||
    if a is not None and a['translated_percent'] == 100 and a['failing'] == 0 \
 | 
			
		||||
       and m is not None and m['translated_percent'] == 100 and m['failing'] == 0:
 | 
			
		||||
        print(locale)
 | 
			
		||||
        merge_locales.append(locale)
 | 
			
		||||
 | 
			
		||||
if not merge_locales:
 | 
			
		||||
    sys.exit()
 | 
			
		||||
 | 
			
		||||
if 'merge_weblate' in repo.heads:
 | 
			
		||||
    merge_weblate = repo.heads['merge_weblate']
 | 
			
		||||
    repo.create_tag('previous_merge_weblate', ref=merge_weblate,
 | 
			
		||||
                    message=('Automatically created by %s' % __file__))
 | 
			
		||||
else:
 | 
			
		||||
    merge_weblate = repo.create_head('merge_weblate')
 | 
			
		||||
merge_weblate.set_commit(upstream.refs.master)
 | 
			
		||||
merge_weblate.checkout()
 | 
			
		||||
 | 
			
		||||
email_pattern = re.compile(r'by (.*?) <(.*)>$')
 | 
			
		||||
 | 
			
		||||
for locale in sorted(merge_locales):
 | 
			
		||||
    commits = list(repo.iter_commits(
 | 
			
		||||
        str(weblate.refs.master) + '...' + str(upstream.refs.master),
 | 
			
		||||
        paths=get_paths_tuple(locale), max_count=10))
 | 
			
		||||
    for commit in reversed(commits):
 | 
			
		||||
        repo.git.cherry_pick(str(commit))
 | 
			
		||||
        m = email_pattern.search(commit.summary)
 | 
			
		||||
        if m:
 | 
			
		||||
            email = m.group(1) + ' <' + m.group(2) + '>'
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user