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:
Hans-Christoph Steiner 2019-05-10 18:56:47 +00:00
commit fac36457ea
21 changed files with 290 additions and 63 deletions

View File

@ -42,6 +42,7 @@
android:id="@+id/find_people_button" android:id="@+id/find_people_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxEms="16"
android:text="@string/nearby_splash__find_people_button" android:text="@string/nearby_splash__find_people_button"
style="@style/DetailsSecondaryButtonStyle" style="@style/DetailsSecondaryButtonStyle"
app:layout_constraintTop_toBottomOf="@+id/title" app:layout_constraintTop_toBottomOf="@+id/title"
@ -85,6 +86,7 @@
android:id="@+id/request_read_external_storage_button" android:id="@+id/request_read_external_storage_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxEms="16"
android:text="@string/nearby_splash__request_permission" android:text="@string/nearby_splash__request_permission"
style="@style/DetailsSecondaryButtonStyle" style="@style/DetailsSecondaryButtonStyle"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -37,6 +37,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:backgroundTint="@color/swap_light_blue" android:backgroundTint="@color/swap_light_blue"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:maxEms="10"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/menu_install" android:text="@string/menu_install"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />

View File

@ -28,13 +28,11 @@ public class DeleteCacheService extends JobIntentService {
Log.w(TAG, "Deleting all cached contents!"); Log.w(TAG, "Deleting all cached contents!");
try { try {
File cacheDir = getCacheDir(); File cacheDir = getCacheDir();
if (cacheDir != null) {
FileUtils.deleteDirectory(cacheDir); FileUtils.deleteDirectory(cacheDir);
}
for (File dir : ContextCompat.getExternalCacheDirs(this)) { for (File dir : ContextCompat.getExternalCacheDirs(this)) {
FileUtils.deleteDirectory(dir); FileUtils.deleteDirectory(dir);
} }
} catch (Exception e) { } catch (Throwable e) { // NOPMD
// ignored // ignored
} }
} }

View File

@ -414,7 +414,7 @@ public class IndexUpdater {
Utils.debugLog(TAG, "Saving new signing certificate in the database for " + repo.address); Utils.debugLog(TAG, "Saving new signing certificate in the database for " + repo.address);
ContentValues values = new ContentValues(2); 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)); values.put(RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
RepoProvider.Helper.update(context, repo, values); RepoProvider.Helper.update(context, repo, values);
} }

View File

@ -470,7 +470,7 @@ public class IndexV1Updater extends IndexUpdater {
} }
Utils.debugLog(TAG, "Saving new signing certificate to database for " + repo.address); Utils.debugLog(TAG, "Saving new signing certificate to database for " + repo.address);
ContentValues values = new ContentValues(2); 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)); values.put(Schema.RepoTable.Cols.SIGNING_CERT, Hasher.hex(rawCertFromJar));
RepoProvider.Helper.update(context, repo, values); RepoProvider.Helper.update(context, repo, values);
repo.signingCertificate = certFromJar; repo.signingCertificate = certFromJar;

View File

@ -236,7 +236,7 @@ public class UpdateService extends JobIntentService {
Utils.debugLog(TAG, "scheduling update because there is good internet"); Utils.debugLog(TAG, "scheduling update because there is good internet");
schedule(context); schedule(context);
} }
} catch (Exception e) { } catch (Throwable e) { // NOPMD
Utils.debugLog(TAG, e.getMessage()); Utils.debugLog(TAG, e.getMessage());
} }
isScheduleIfStillOnWifiRunning = false; isScheduleIfStillOnWifiRunning = false;

View File

@ -79,6 +79,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -96,6 +97,8 @@ public final class Utils {
private static final SimpleDateFormat TIME_FORMAT = private static final SimpleDateFormat TIME_FORMAT =
new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.ENGLISH); 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 = { private static final String[] FRIENDLY_SIZE_FORMAT = {
"%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB", "%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB",
}; };
@ -384,7 +387,7 @@ public final class Utils {
} }
ret = formatter.toString(); ret = formatter.toString();
formatter.close(); formatter.close();
} catch (Exception e) { } catch (Throwable e) { // NOPMD
Log.w(TAG, "Unable to get certificate fingerprint", e); Log.w(TAG, "Unable to get certificate fingerprint", e);
} }
return ret; return ret;
@ -583,6 +586,7 @@ public final class Utils {
} }
Date result; Date result;
try { try {
format.setTimeZone(UTC);
result = format.parse(str); result = format.parse(str);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException | ParseException e) { } catch (ArrayIndexOutOfBoundsException | NumberFormatException | ParseException e) {
e.printStackTrace(); e.printStackTrace();
@ -595,21 +599,34 @@ public final class Utils {
if (date == null) { if (date == null) {
return fallback; return fallback;
} }
format.setTimeZone(UTC);
return format.format(date); return format.format(date);
} }
/**
* Parses a date string into UTC time
*/
public static Date parseDate(String str, Date fallback) { public static Date parseDate(String str, Date fallback) {
return parseDateFormat(DATE_FORMAT, str, fallback); return parseDateFormat(DATE_FORMAT, str, fallback);
} }
/**
* Formats UTC time into a date string
*/
public static String formatDate(Date date, String fallback) { public static String formatDate(Date date, String fallback) {
return formatDateFormat(DATE_FORMAT, date, fallback); return formatDateFormat(DATE_FORMAT, date, fallback);
} }
/**
* Parses a date/time string into UTC time
*/
public static Date parseTime(String str, Date fallback) { public static Date parseTime(String str, Date fallback) {
return parseDateFormat(TIME_FORMAT, str, fallback); return parseDateFormat(TIME_FORMAT, str, fallback);
} }
/**
* Formats UTC time into a date/time string
*/
public static String formatTime(Date date, String fallback) { public static String formatTime(Date date, String fallback) {
return formatDateFormat(TIME_FORMAT, date, fallback); return formatDateFormat(TIME_FORMAT, date, fallback);
} }

View File

@ -165,7 +165,8 @@ public class Repo extends ValueObject {
inuse = cursor.getInt(i) == 1; inuse = cursor.getInt(i) == 1;
break; break;
case Cols.LAST_UPDATED: 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; break;
case Cols.MAX_AGE: case Cols.MAX_AGE:
maxage = cursor.getInt(i); maxage = cursor.getInt(i);
@ -296,7 +297,7 @@ public class Repo extends ValueObject {
if (values.containsKey(Cols.LAST_UPDATED)) { if (values.containsKey(Cols.LAST_UPDATED)) {
final String dateString = values.getAsString(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)) { if (values.containsKey(Cols.MAX_AGE)) {

View File

@ -11,7 +11,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.fdroid.fdroid.AppUpdateStatusManager; import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.RepoTable; import org.fdroid.fdroid.data.Schema.RepoTable;
@ -278,7 +277,8 @@ public class RepoProvider extends FDroidProvider {
if (cursor != null) { if (cursor != null) {
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
cursor.moveToFirst(); cursor.moveToFirst();
lastUpdate = Utils.parseDate(cursor.getString(0), null); String dateString = cursor.getString(0);
lastUpdate = Utils.parseTime(dateString, Utils.parseDate(dateString, null));
} }
cursor.close(); cursor.close();
} }

View File

@ -265,7 +265,7 @@ public class PrivilegedInstaller extends Installer {
PackageManager pm = context.getPackageManager(); PackageManager pm = context.getPackageManager();
try { try {
pm.getPackageInfo(PRIVILEGED_EXTENSION_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); pm.getPackageInfo(PRIVILEGED_EXTENSION_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
return true; return pm.getApplicationInfo(PRIVILEGED_EXTENSION_PACKAGE_NAME, 0).enabled;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
return false; return false;
} }

View File

@ -555,11 +555,16 @@ public class AppDetailsRecyclerViewAdapter
} }
updateAntiFeaturesWarning(); 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.setText(R.string.menu_install);
buttonPrimaryView.setVisibility(versions.size() > 0 ? View.VISIBLE : View.GONE); 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()) { if (callbacks.isAppDownloading()) {
buttonPrimaryView.setText(R.string.downloading); buttonPrimaryView.setText(R.string.downloading);
buttonPrimaryView.setEnabled(false); buttonPrimaryView.setEnabled(false);
@ -568,22 +573,37 @@ public class AppDetailsRecyclerViewAdapter
} else if (!app.isInstalled(context) && suggestedApk != null) { } else if (!app.isInstalled(context) && suggestedApk != null) {
// Check count > 0 due to incompatible apps resulting in an empty list. // Check count > 0 due to incompatible apps resulting in an empty list.
callbacks.disableAndroidBeam(); callbacks.disableAndroidBeam();
progressLayout.setVisibility(View.GONE);
// Set Install button and hide second button // Set Install button and hide second button
buttonPrimaryView.setText(R.string.menu_install); buttonPrimaryView.setText(R.string.menu_install);
buttonPrimaryView.setOnClickListener(onInstallClickListener);
buttonPrimaryView.setEnabled(true); buttonPrimaryView.setEnabled(true);
buttonLayout.setVisibility(View.VISIBLE); 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)) { } else if (app.isInstalled(context)) {
callbacks.enableAndroidBeam(); callbacks.enableAndroidBeam();
if (app.canAndWantToUpdate(context) && suggestedApk != null) { if (app.canAndWantToUpdate(context) && suggestedApk != null) {
buttonPrimaryView.setText(R.string.menu_upgrade); buttonPrimaryView.setText(R.string.menu_upgrade);
buttonPrimaryView.setOnClickListener(onUpgradeClickListener); buttonPrimaryView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callbacks.installApk();
}
});
} else { } else {
Apk mediaApk = app.getMediaApkifInstalled(context); Apk mediaApk = app.getMediaApkifInstalled(context);
if (context.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) { if (context.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
buttonPrimaryView.setText(R.string.menu_launch); 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) { } else if (!app.isApk && mediaApk != null) {
final File installedFile = new File(mediaApk.getMediaInstallPath(context), mediaApk.apkName); final File installedFile = new File(mediaApk.getMediaInstallPath(context), mediaApk.apkName);
if (!installedFile.toString().startsWith(context.getApplicationInfo().dataDir)) { 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) { private boolean uriIsSetAndCanBeOpened(String s) {
if (TextUtils.isEmpty(s)) { if (TextUtils.isEmpty(s)) {
return false; return false;

View File

@ -143,6 +143,9 @@
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginRight="4dp" android:layout_marginRight="4dp"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:maxEms="10"
android:ellipsize="end"
android:singleLine="true"
tools:text="@string/menu_install"/> tools:text="@string/menu_install"/>
<Button <Button

View File

@ -42,6 +42,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:maxEms="10"
android:text="@string/update_all" android:text="@string/update_all"
style="@style/DetailsPrimaryButtonStyle" style="@style/DetailsPrimaryButtonStyle"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -54,6 +55,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:ellipsize="middle"
android:singleLine="true"
tools:text="Show apps" tools:text="Show apps"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -10,6 +10,8 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.io.File; import java.io.File;
import java.util.Date;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -77,9 +79,9 @@ public class UtilsTest {
assertEquals("three", tripleValue[2]); assertEquals("three", tripleValue[2]);
assertNull(Utils.serializeCommaSeparatedString(null)); assertNull(Utils.serializeCommaSeparatedString(null));
assertNull(Utils.serializeCommaSeparatedString(new String[] {})); assertNull(Utils.serializeCommaSeparatedString(new String[]{}));
assertEquals("Single", Utils.serializeCommaSeparatedString(new String[] {"Single"})); assertEquals("Single", Utils.serializeCommaSeparatedString(new String[]{"Single"}));
assertEquals("One,TWO,three", Utils.serializeCommaSeparatedString(new String[] {"One", "TWO", "three"})); assertEquals("One,TWO,three", Utils.serializeCommaSeparatedString(new String[]{"One", "TWO", "three"}));
} }
@Test @Test
@ -192,4 +194,25 @@ public class UtilsTest {
} }
// TODO write tests that work with a Certificate // 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());
}
}
}
} }

View File

@ -7,10 +7,12 @@ import android.net.Uri;
import org.fdroid.fdroid.Assert; import org.fdroid.fdroid.Assert;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.ApkTable.Cols; import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
import org.fdroid.fdroid.data.Schema.RepoTable; import org.fdroid.fdroid.data.Schema.RepoTable;
import org.fdroid.fdroid.mock.MockApk; import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockRepo; import org.fdroid.fdroid.mock.MockRepo;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
@ -18,6 +20,7 @@ import org.robolectric.annotation.Config;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone;
import static org.fdroid.fdroid.Assert.assertCantDelete; import static org.fdroid.fdroid.Assert.assertCantDelete;
import static org.fdroid.fdroid.Assert.assertResultCount; import static org.fdroid.fdroid.Assert.assertResultCount;
@ -34,6 +37,13 @@ public class ApkProviderTest extends FDroidProviderTest {
private static final String[] PROJ = Cols.ALL; 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 @Test
public void testAppApks() { public void testAppApks() {
App fdroidApp = insertApp(context, "org.fdroid.fdroid", "F-Droid"); App fdroidApp = insertApp(context, "org.fdroid.fdroid", "F-Droid");
@ -153,7 +163,7 @@ public class ApkProviderTest extends FDroidProviderTest {
@Test @Test
public void testCount() { public void testCount() {
String[] projectionCount = new String[] {Cols._COUNT}; String[] projectionCount = new String[]{Cols._COUNT};
for (int i = 0; i < 13; i++) { for (int i = 0; i < 13; i++) {
Assert.insertApk(context, "com.example", i); Assert.insertApk(context, "com.example", i);
@ -315,12 +325,13 @@ public class ApkProviderTest extends FDroidProviderTest {
assertNull(apk.added); assertNull(apk.added);
assertNull(apk.hashType); assertNull(apk.hashType);
apk.antiFeatures = new String[] {"KnownVuln", "Other anti feature"}; apk.antiFeatures = new String[]{"KnownVuln", "Other anti feature"};
apk.features = new String[] {"one", "two", "three" }; apk.features = new String[]{"one", "two", "three"};
long dateTimestamp = System.currentTimeMillis();
apk.added = new Date(dateTimestamp);
apk.hashType = "i'm a hash type"; 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); ApkProvider.Helper.update(context, apk);
// Should not have inserted anything else, just updated the already existing 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[]{"KnownVuln", "Other anti feature"}, updatedApk.antiFeatures);
assertArrayEquals(new String[]{"one", "two", "three"}, updatedApk.features); assertArrayEquals(new String[]{"one", "two", "three"}, updatedApk.features);
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear()); assertEquals(testTime.getYear(), updatedApk.added.getYear());
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth()); assertEquals(testTime.getYear(), updatedApk.added.getYear());
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay()); assertEquals(testTime.getMonth(), updatedApk.added.getMonth());
assertEquals(testTime.getDay(), updatedApk.added.getDay());
assertEquals("i'm a hash type", updatedApk.hashType); assertEquals("i'm a hash type", updatedApk.hashType);
} }

View File

@ -11,6 +11,7 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils; import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
@ -19,6 +20,7 @@ import org.robolectric.shadows.ShadowContentResolver;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.TimeZone;
import static org.fdroid.fdroid.Assert.assertContainsOnly; import static org.fdroid.fdroid.Assert.assertContainsOnly;
import static org.fdroid.fdroid.Assert.assertResultCount; import static org.fdroid.fdroid.Assert.assertResultCount;
@ -36,6 +38,13 @@ public class AppProviderTest extends FDroidProviderTest {
private static final String[] PROJ = Cols.ALL; 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 @Before
public void setup() { public void setup() {
TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class);

View File

@ -30,6 +30,7 @@ import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.RepoTable; import org.fdroid.fdroid.data.Schema.RepoTable;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
@ -37,6 +38,7 @@ import org.robolectric.annotation.Config;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@ -48,6 +50,16 @@ public class RepoProviderTest extends FDroidProviderTest {
private static final String[] COLS = RepoTable.Cols.ALL; 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 @Test
public void countEnabledRepos() { public void countEnabledRepos() {
@ -95,7 +107,7 @@ public class RepoProviderTest extends FDroidProviderTest {
private Repo setLastUpdate(Repo repo, Date date) { private Repo setLastUpdate(Repo repo, Date date) {
ContentValues values = new ContentValues(1); 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); RepoProvider.Helper.update(context, repo, values);
return RepoProvider.Helper.findByAddress(context, repo.address); return RepoProvider.Helper.findByAddress(context, repo.address);
} }

View File

@ -29,6 +29,7 @@ import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.mock.MockRepo; import org.fdroid.fdroid.mock.MockRepo;
import org.fdroid.fdroid.mock.RepoDetails; import org.fdroid.fdroid.mock.RepoDetails;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
@ -48,6 +49,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -63,6 +65,16 @@ public class RepoXMLHandlerTest {
private static final String FAKE_SIGNING_CERT = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"; // NOCHECKSTYLE LineLength 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 @Test
public void testExtendedPerms() throws IOException { public void testExtendedPerms() throws IOException {
Repo expectedRepo = new Repo(); Repo expectedRepo = new Repo();
@ -129,6 +141,12 @@ public class RepoXMLHandlerTest {
"org.gege.caldavsyncadapter", "org.gege.caldavsyncadapter",
"info.guardianproject.checkey", "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) @Test(expected = IllegalArgumentException.class)
@ -897,6 +915,10 @@ public class RepoXMLHandlerTest {
List<App> apps = actualDetails.apps; List<App> apps = actualDetails.apps;
assertNotNull(apps); assertNotNull(apps);
assertEquals(apps.size(), appCount); 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; List<Apk> apks = actualDetails.apks;
assertNotNull(apks); assertNotNull(apks);

File diff suppressed because one or more lines are too long

48
tools/check-string-maxlength.py Executable file
View 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)

View 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) + '>'