Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9c1ee51c7f | ||
![]() |
7f5db2c5b9 | ||
![]() |
5340fd9350 | ||
![]() |
f2696f3ec2 | ||
![]() |
558c0a3e0e | ||
![]() |
caec0c3eaa | ||
![]() |
876ab253b5 | ||
![]() |
3680c6b914 | ||
![]() |
198ad843c1 | ||
![]() |
01de14f84e | ||
![]() |
ae15e3a22d | ||
![]() |
ba2f706166 | ||
![]() |
7b54c07c31 | ||
![]() |
55f4a5938e | ||
![]() |
550a107c9c | ||
![]() |
ad498faf92 | ||
![]() |
f5fcde9867 | ||
![]() |
57fee437ba | ||
![]() |
41d1278579 | ||
![]() |
c941440a34 | ||
![]() |
8322fd046c | ||
![]() |
4f514deca5 | ||
![]() |
cd9ad9cdbf | ||
![]() |
8eda7d0273 | ||
![]() |
5f3dde4060 | ||
![]() |
b3f79da341 | ||
![]() |
f1b09a5c43 | ||
![]() |
3dfbadc24d | ||
![]() |
3fedbdaff3 | ||
![]() |
5084982ce2 | ||
![]() |
ba42d3a507 | ||
![]() |
db9bdc315d | ||
![]() |
5f1aee8f0d | ||
![]() |
388dbbb2de | ||
![]() |
c3ae8008ba | ||
![]() |
59a533234e |
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,4 +1,14 @@
|
|||||||
### 0.100 (2016-05-??)
|
### 0.100.1 (2016-06-21)
|
||||||
|
|
||||||
|
* Fix background crash after installing or updating apps
|
||||||
|
|
||||||
|
* Fix crash if an app has a short description
|
||||||
|
|
||||||
|
* Fix background crash in the wifi state change swap service
|
||||||
|
|
||||||
|
* Fix crash if there is a problem listing the cached files to delete
|
||||||
|
|
||||||
|
### 0.100 (2016-06-07)
|
||||||
|
|
||||||
* Ability to download apps in the background
|
* Ability to download apps in the background
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.fdroid.fdroid"
|
package="org.fdroid.fdroid"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="100007"
|
android:versionCode="100150"
|
||||||
android:versionName="0.100-alpha7"
|
android:versionName="0.100.1"
|
||||||
>
|
>
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
|
@ -7,7 +7,6 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
@ -20,7 +19,6 @@ import java.io.File;
|
|||||||
* {@link FDroidApp#onCreate()}
|
* {@link FDroidApp#onCreate()}
|
||||||
*/
|
*/
|
||||||
public class CleanCacheService extends IntentService {
|
public class CleanCacheService extends IntentService {
|
||||||
private static final String TAG = "CleanCacheService";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule or cancel this service to update the app index, according to the
|
* Schedule or cancel this service to update the app index, according to the
|
||||||
@ -33,7 +31,6 @@ public class CleanCacheService extends IntentService {
|
|||||||
if (keepTime < interval) {
|
if (keepTime < interval) {
|
||||||
interval = keepTime * 1000;
|
interval = keepTime * 1000;
|
||||||
}
|
}
|
||||||
Log.i(TAG, "schedule " + keepTime + " " + interval);
|
|
||||||
|
|
||||||
Intent intent = new Intent(context, CleanCacheService.class);
|
Intent intent = new Intent(context, CleanCacheService.class);
|
||||||
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
|
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
|
||||||
@ -53,6 +50,29 @@ public class CleanCacheService extends IntentService {
|
|||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||||
Utils.clearOldFiles(Utils.getApkCacheDir(this), Preferences.get().getKeepCacheTime());
|
Utils.clearOldFiles(Utils.getApkCacheDir(this), Preferences.get().getKeepCacheTime());
|
||||||
deleteStrayIndexFiles();
|
deleteStrayIndexFiles();
|
||||||
|
deleteOldInstallerFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link org.fdroid.fdroid.installer.Installer} instances copy the APK into
|
||||||
|
* a safe place before installing. It doesn't clean up them reliably yet.
|
||||||
|
*/
|
||||||
|
private void deleteOldInstallerFiles() {
|
||||||
|
File filesDir = getFilesDir();
|
||||||
|
if (filesDir == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File[] files = filesDir.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File f : files) {
|
||||||
|
if (f.getName().startsWith("install-")) {
|
||||||
|
FileUtils.deleteQuietly(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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() {
|
public static void initWifiSettings() {
|
||||||
port = 8888;
|
port = 8888;
|
||||||
|
@ -57,7 +57,9 @@ public class RepoUpdater {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final Repo repo;
|
private final Repo repo;
|
||||||
private boolean hasChanged;
|
private boolean hasChanged;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
private ProgressListener downloadProgressListener;
|
||||||
private ProgressListener committingProgressListener;
|
private ProgressListener committingProgressListener;
|
||||||
private ProgressListener processXmlProgressListener;
|
private ProgressListener processXmlProgressListener;
|
||||||
private String cacheTag;
|
private String cacheTag;
|
||||||
@ -83,6 +85,10 @@ public class RepoUpdater {
|
|||||||
this.indexUrl = url;
|
this.indexUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDownloadProgressListener(ProgressListener progressListener) {
|
||||||
|
this.downloadProgressListener = progressListener;
|
||||||
|
}
|
||||||
|
|
||||||
public void setProcessXmlProgressListener(ProgressListener progressListener) {
|
public void setProcessXmlProgressListener(ProgressListener progressListener) {
|
||||||
this.processXmlProgressListener = progressListener;
|
this.processXmlProgressListener = progressListener;
|
||||||
}
|
}
|
||||||
@ -100,6 +106,7 @@ public class RepoUpdater {
|
|||||||
try {
|
try {
|
||||||
downloader = DownloaderFactory.create(context, indexUrl);
|
downloader = DownloaderFactory.create(context, indexUrl);
|
||||||
downloader.setCacheTag(repo.lastetag);
|
downloader.setCacheTag(repo.lastetag);
|
||||||
|
downloader.setListener(downloadProgressListener);
|
||||||
downloader.download();
|
downloader.download();
|
||||||
|
|
||||||
if (downloader.isCached()) {
|
if (downloader.isCached()) {
|
||||||
|
@ -125,10 +125,14 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
curapk.apkName = str;
|
curapk.apkName = str;
|
||||||
break;
|
break;
|
||||||
case "sdkver":
|
case "sdkver":
|
||||||
curapk.minSdkVersion = Utils.parseInt(str, 0);
|
curapk.minSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MIN_VALUE);
|
||||||
break;
|
break;
|
||||||
case "maxsdkver":
|
case "maxsdkver":
|
||||||
curapk.maxSdkVersion = Utils.parseInt(str, 0);
|
curapk.maxSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MAX_VALUE);
|
||||||
|
if (curapk.maxSdkVersion == 0) {
|
||||||
|
// before fc0df0dcf4dd0d5f13de82d7cd9254b2b48cb62d, this could be 0
|
||||||
|
curapk.maxSdkVersion = Apk.SDK_VERSION_MAX_VALUE;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "added":
|
case "added":
|
||||||
curapk.added = Utils.parseDate(str, null);
|
curapk.added = Utils.parseDate(str, null);
|
||||||
|
@ -49,8 +49,6 @@ import org.fdroid.fdroid.data.AppProvider;
|
|||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
|
||||||
import org.fdroid.fdroid.net.DownloaderService;
|
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -166,7 +164,6 @@ public class UpdateService extends IntentService {
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
notificationManager.cancel(NOTIFY_ID_UPDATING);
|
notificationManager.cancel(NOTIFY_ID_UPDATING);
|
||||||
localBroadcastManager.unregisterReceiver(downloadProgressReceiver);
|
|
||||||
localBroadcastManager.unregisterReceiver(updateStatusReceiver);
|
localBroadcastManager.unregisterReceiver(updateStatusReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,26 +192,6 @@ public class UpdateService extends IntentService {
|
|||||||
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
|
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
|
// For receiving results from the UpdateService when we've told it to
|
||||||
// update in response to a user request.
|
// update in response to a user request.
|
||||||
private final BroadcastReceiver updateStatusReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver updateStatusReceiver = new BroadcastReceiver() {
|
||||||
@ -337,8 +314,12 @@ public class UpdateService extends IntentService {
|
|||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||||
|
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
String address = intent.getStringExtra(EXTRA_ADDRESS);
|
boolean manualUpdate = false;
|
||||||
boolean manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
|
String address = null;
|
||||||
|
if (intent != null) {
|
||||||
|
address = intent.getStringExtra(EXTRA_ADDRESS);
|
||||||
|
manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// See if it's time to actually do anything yet...
|
// See if it's time to actually do anything yet...
|
||||||
@ -375,10 +356,7 @@ public class UpdateService extends IntentService {
|
|||||||
|
|
||||||
sendStatus(this, STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
|
sendStatus(this, STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));
|
||||||
RepoUpdater updater = new RepoUpdater(getBaseContext(), repo);
|
RepoUpdater updater = new RepoUpdater(getBaseContext(), repo);
|
||||||
localBroadcastManager.registerReceiver(downloadProgressReceiver,
|
setProgressListeners(updater);
|
||||||
DownloaderService.getIntentFilter(updater.indexUrl, Downloader.ACTION_PROGRESS));
|
|
||||||
updater.setProcessXmlProgressListener(processXmlProgressListener);
|
|
||||||
updater.setCommittingProgressListener(committingProgressListener);
|
|
||||||
try {
|
try {
|
||||||
updater.update();
|
updater.update();
|
||||||
if (updater.hasChanged()) {
|
if (updater.hasChanged()) {
|
||||||
@ -392,7 +370,6 @@ public class UpdateService extends IntentService {
|
|||||||
repoErrors.add(e.getMessage());
|
repoErrors.add(e.getMessage());
|
||||||
Log.e(TAG, "Error updating repository " + repo.address, e);
|
Log.e(TAG, "Error updating repository " + repo.address, e);
|
||||||
}
|
}
|
||||||
localBroadcastManager.unregisterReceiver(downloadProgressReceiver);
|
|
||||||
|
|
||||||
// now that downloading the index is done, start downloading updates
|
// now that downloading the index is done, start downloading updates
|
||||||
if (changes && fdroidPrefs.isAutoDownloadEnabled()) {
|
if (changes && fdroidPrefs.isAutoDownloadEnabled()) {
|
||||||
@ -528,7 +505,34 @@ public class UpdateService extends IntentService {
|
|||||||
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
|
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ProgressListener processXmlProgressListener = new ProgressListener() {
|
/**
|
||||||
|
* 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 = -1;
|
||||||
|
if (totalBytes > 0) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updater.setProcessXmlProgressListener(new ProgressListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
||||||
String downloadedSize = Utils.getFriendlySize(bytesRead);
|
String downloadedSize = Utils.getFriendlySize(bytesRead);
|
||||||
@ -540,13 +544,14 @@ public class UpdateService extends IntentService {
|
|||||||
String message = getString(R.string.status_processing_xml_percent, sourceUrl, downloadedSize, totalSize, percent);
|
String message = getString(R.string.status_processing_xml_percent, sourceUrl, downloadedSize, totalSize, percent);
|
||||||
sendStatus(getApplicationContext(), STATUS_INFO, message, percent);
|
sendStatus(getApplicationContext(), STATUS_INFO, message, percent);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
private final ProgressListener committingProgressListener = new ProgressListener() {
|
updater.setCommittingProgressListener(new ProgressListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
|
||||||
String message = getString(R.string.status_inserting_apps);
|
String message = getString(R.string.status_inserting_apps);
|
||||||
sendStatus(getApplicationContext(), STATUS_INFO, message);
|
sendStatus(getApplicationContext(), STATUS_INFO, message);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ import com.nostra13.universalimageloader.utils.StorageUtils;
|
|||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.fdroid.fdroid.compat.FileCompat;
|
import org.fdroid.fdroid.compat.FileCompat;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
import org.xml.sax.XMLReader;
|
import org.xml.sax.XMLReader;
|
||||||
@ -226,8 +227,8 @@ public final class Utils {
|
|||||||
|
|
||||||
/* PackageManager doesn't give us the min and max sdk versions, so we have
|
/* PackageManager doesn't give us the min and max sdk versions, so we have
|
||||||
* to parse it */
|
* to parse it */
|
||||||
private static int getMinMaxSdkVersion(Context context, String packageName,
|
private static int getSdkVersion(Context context, String packageName,
|
||||||
String attrName) {
|
String attrName, final int defaultValue) {
|
||||||
try {
|
try {
|
||||||
AssetManager am = context.createPackageContext(packageName, 0).getAssets();
|
AssetManager am = context.createPackageContext(packageName, 0).getAssets();
|
||||||
XmlResourceParser xml = am.openXmlResourceParser("AndroidManifest.xml");
|
XmlResourceParser xml = am.openXmlResourceParser("AndroidManifest.xml");
|
||||||
@ -245,15 +246,15 @@ public final class Utils {
|
|||||||
} catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
|
} catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
|
||||||
Log.e(TAG, "Could not get min/max sdk version", e);
|
Log.e(TAG, "Could not get min/max sdk version", e);
|
||||||
}
|
}
|
||||||
return 0;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getMinSdkVersion(Context context, String packageName) {
|
public static int getMinSdkVersion(Context context, String packageName) {
|
||||||
return getMinMaxSdkVersion(context, packageName, "minSdkVersion");
|
return getSdkVersion(context, packageName, "minSdkVersion", Apk.SDK_VERSION_MIN_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getMaxSdkVersion(Context context, String packageName) {
|
public static int getMaxSdkVersion(Context context, String packageName) {
|
||||||
return getMinMaxSdkVersion(context, packageName, "maxSdkVersion");
|
return getSdkVersion(context, packageName, "maxSdkVersion", Apk.SDK_VERSION_MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return a fingerprint formatted for display
|
// return a fingerprint formatted for display
|
||||||
@ -342,8 +343,12 @@ public final class Utils {
|
|||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
long olderThan = System.currentTimeMillis() - (secondsAgo * 1000L);
|
long olderThan = System.currentTimeMillis() - (secondsAgo * 1000L);
|
||||||
for (File f : dir.listFiles()) {
|
for (File f : files) {
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
clearOldFiles(f, olderThan);
|
clearOldFiles(f, olderThan);
|
||||||
f.delete();
|
f.delete();
|
||||||
|
@ -4,6 +4,7 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ public class Apk extends ValueObject implements Comparable<Apk> {
|
|||||||
|
|
||||||
// Using only byte-range keeps it only 8-bits in the SQLite database
|
// Using only byte-range keeps it only 8-bits in the SQLite database
|
||||||
public static final int SDK_VERSION_MAX_VALUE = Byte.MAX_VALUE;
|
public static final int SDK_VERSION_MAX_VALUE = Byte.MAX_VALUE;
|
||||||
|
public static final int SDK_VERSION_MIN_VALUE = 0;
|
||||||
|
|
||||||
public String packageName;
|
public String packageName;
|
||||||
public String versionName;
|
public String versionName;
|
||||||
@ -21,7 +23,7 @@ public class Apk extends ValueObject implements Comparable<Apk> {
|
|||||||
public long repo; // ID of the repo it comes from
|
public long repo; // ID of the repo it comes from
|
||||||
public String hash;
|
public String hash;
|
||||||
public String hashType;
|
public String hashType;
|
||||||
public int minSdkVersion; // 0 if unknown
|
public int minSdkVersion = SDK_VERSION_MIN_VALUE; // 0 if unknown
|
||||||
public int maxSdkVersion = SDK_VERSION_MAX_VALUE; // "infinity" if not set
|
public int maxSdkVersion = SDK_VERSION_MAX_VALUE; // "infinity" if not set
|
||||||
public Date added;
|
public Date added;
|
||||||
public Utils.CommaSeparatedList permissions; // null if empty or
|
public Utils.CommaSeparatedList permissions; // null if empty or
|
||||||
@ -49,7 +51,12 @@ public class Apk extends ValueObject implements Comparable<Apk> {
|
|||||||
public String repoAddress;
|
public String repoAddress;
|
||||||
public Utils.CommaSeparatedList incompatibleReasons;
|
public Utils.CommaSeparatedList incompatibleReasons;
|
||||||
|
|
||||||
public Apk() { }
|
public Apk() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Apk(Parcelable parcelable) {
|
||||||
|
this(new ContentValuesCursor((ContentValues) parcelable));
|
||||||
|
}
|
||||||
|
|
||||||
public Apk(Cursor cursor) {
|
public Apk(Cursor cursor) {
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import android.content.pm.FeatureInfo;
|
|||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@ -21,8 +22,12 @@ import java.io.InputStream;
|
|||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class App extends ValueObject implements Comparable<App> {
|
public class App extends ValueObject implements Comparable<App> {
|
||||||
|
|
||||||
@ -108,12 +113,21 @@ public class App extends ValueObject implements Comparable<App> {
|
|||||||
|
|
||||||
public boolean uninstallable;
|
public boolean uninstallable;
|
||||||
|
|
||||||
|
public static String getIconName(String packageName, int versionCode) {
|
||||||
|
return packageName + "_" + versionCode + ".png";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(App app) {
|
public int compareTo(App app) {
|
||||||
return name.compareToIgnoreCase(app.name);
|
return name.compareToIgnoreCase(app.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public App() { }
|
public App() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public App(Parcelable parcelable) {
|
||||||
|
this(new ContentValuesCursor((ContentValues) parcelable));
|
||||||
|
}
|
||||||
|
|
||||||
public App(Cursor cursor) {
|
public App(Cursor cursor) {
|
||||||
|
|
||||||
@ -258,8 +272,10 @@ public class App extends ValueObject implements Comparable<App> {
|
|||||||
final CharSequence appDescription = appInfo.loadDescription(pm);
|
final CharSequence appDescription = appInfo.loadDescription(pm);
|
||||||
if (TextUtils.isEmpty(appDescription)) {
|
if (TextUtils.isEmpty(appDescription)) {
|
||||||
this.summary = "(installed by " + installerPackageLabel + ")";
|
this.summary = "(installed by " + installerPackageLabel + ")";
|
||||||
} else {
|
} else if (appDescription.length() > 40) {
|
||||||
this.summary = (String) appDescription.subSequence(0, 40);
|
this.summary = (String) appDescription.subSequence(0, 40);
|
||||||
|
} else {
|
||||||
|
this.summary = (String) appDescription;
|
||||||
}
|
}
|
||||||
this.packageName = appInfo.packageName;
|
this.packageName = appInfo.packageName;
|
||||||
this.added = new Date(packageInfo.firstInstallTime);
|
this.added = new Date(packageInfo.firstInstallTime);
|
||||||
@ -273,21 +289,35 @@ public class App extends ValueObject implements Comparable<App> {
|
|||||||
+ ", last updated on " + this.lastUpdated + ")</p>";
|
+ ", last updated on " + this.lastUpdated + ")</p>";
|
||||||
|
|
||||||
this.name = (String) appInfo.loadLabel(pm);
|
this.name = (String) appInfo.loadLabel(pm);
|
||||||
|
this.icon = getIconName(packageName, packageInfo.versionCode);
|
||||||
|
|
||||||
final SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir);
|
|
||||||
final Apk apk = new Apk();
|
final Apk apk = new Apk();
|
||||||
apk.versionName = packageInfo.versionName;
|
apk.versionName = packageInfo.versionName;
|
||||||
apk.versionCode = packageInfo.versionCode;
|
apk.versionCode = packageInfo.versionCode;
|
||||||
apk.hashType = "sha256";
|
|
||||||
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
|
|
||||||
apk.added = this.added;
|
apk.added = this.added;
|
||||||
apk.minSdkVersion = Utils.getMinSdkVersion(context, packageName);
|
apk.minSdkVersion = Utils.getMinSdkVersion(context, packageName);
|
||||||
apk.maxSdkVersion = Utils.getMaxSdkVersion(context, packageName);
|
apk.maxSdkVersion = Utils.getMaxSdkVersion(context, packageName);
|
||||||
apk.packageName = this.packageName;
|
apk.packageName = this.packageName;
|
||||||
apk.installedFile = apkFile;
|
|
||||||
apk.permissions = Utils.CommaSeparatedList.make(packageInfo.requestedPermissions);
|
apk.permissions = Utils.CommaSeparatedList.make(packageInfo.requestedPermissions);
|
||||||
apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk";
|
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<String> abis = new HashSet<>(3);
|
||||||
|
Pattern pattern = Pattern.compile("^lib/([a-z0-9-]+)/.*");
|
||||||
|
for (Enumeration<JarEntry> 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;
|
final FeatureInfo[] features = packageInfo.reqFeatures;
|
||||||
if (features != null && features.length > 0) {
|
if (features != null && features.length > 0) {
|
||||||
final String[] featureNames = new String[features.length];
|
final String[] featureNames = new String[features.length];
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
package org.fdroid.fdroid.data;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.AbstractCursor;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to keep {@link App#App(Cursor)} and {@link Apk#Apk(Cursor)} as
|
||||||
|
* efficient as possible, this wrapper class is used to instantiate {@code App}
|
||||||
|
* and {@code Apk} from {@link App#toContentValues()} and
|
||||||
|
* {@link Apk#toContentValues()} included as extras {@link Bundle}s in the
|
||||||
|
* {@link android.content.Intent} that starts
|
||||||
|
* {@link org.fdroid.fdroid.installer.InstallManagerService}
|
||||||
|
* <p>
|
||||||
|
* This implemented to throw an {@link IllegalArgumentException} if the types
|
||||||
|
* do not match what they are expected to be so that things fail fast. So that
|
||||||
|
* means only types used in {@link App#toContentValues()} and
|
||||||
|
* {@link Apk#toContentValues()} are implemented.
|
||||||
|
*/
|
||||||
|
public class ContentValuesCursor extends AbstractCursor {
|
||||||
|
|
||||||
|
private final String[] keys;
|
||||||
|
private final Object[] values;
|
||||||
|
|
||||||
|
public ContentValuesCursor(ContentValues contentValues) {
|
||||||
|
super();
|
||||||
|
keys = new String[contentValues.size()];
|
||||||
|
values = new Object[contentValues.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, Object> entry : contentValues.valueSet()) {
|
||||||
|
keys[i] = entry.getKey();
|
||||||
|
values[i] = entry.getValue();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
moveToFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getColumnNames() {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(int i) {
|
||||||
|
return (String) values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(int i) {
|
||||||
|
if (values[i] instanceof Long) {
|
||||||
|
return ((Long) values[i]).intValue();
|
||||||
|
} else if (values[i] instanceof Integer) {
|
||||||
|
return (int) values[i];
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(int i) {
|
||||||
|
throw new IllegalArgumentException("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getShort(int i) {
|
||||||
|
throw new IllegalArgumentException("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloat(int i) {
|
||||||
|
throw new IllegalArgumentException("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDouble(int i) {
|
||||||
|
throw new IllegalArgumentException("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNull(int i) {
|
||||||
|
return values[i] == null;
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,6 @@ import android.support.v4.app.NotificationCompat;
|
|||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.AppDetails;
|
import org.fdroid.fdroid.AppDetails;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
@ -36,10 +35,12 @@ import java.util.Set;
|
|||||||
* requests an APK to be installed. It handles checking whether the APK is cached,
|
* requests an APK to be installed. It handles checking whether the APK is cached,
|
||||||
* downloading it, putting up and maintaining a {@link Notification}, and more.
|
* downloading it, putting up and maintaining a {@link Notification}, and more.
|
||||||
* <p>
|
* <p>
|
||||||
* Data is sent via {@link Intent}s so that Android handles the message queuing
|
* The {@link App} and {@link Apk} instances are sent via
|
||||||
* and {@link Service} lifecycle for us, although it adds one layer of redirection
|
* {@link Intent#putExtra(String, android.os.Bundle)}
|
||||||
* between the static method to send the {@code Intent} and the method to
|
* so that Android handles the message queuing and {@link Service} lifecycle for us.
|
||||||
* actually process it.
|
* For example, if this {@code InstallManagerService} gets killed, Android will cache
|
||||||
|
* and then redeliver the {@link Intent} for us, which includes all of the data needed
|
||||||
|
* for {@code InstallManagerService} to do its job for the whole lifecycle of an install.
|
||||||
* <p>
|
* <p>
|
||||||
* The full URL for the APK file to download is also used as the unique ID to
|
* The full URL for the APK file to download is also used as the unique ID to
|
||||||
* represent the download itself throughout F-Droid. This follows the model
|
* represent the download itself throughout F-Droid. This follows the model
|
||||||
@ -59,7 +60,10 @@ import java.util.Set;
|
|||||||
public class InstallManagerService extends Service {
|
public class InstallManagerService extends Service {
|
||||||
public static final String TAG = "InstallManagerService";
|
public static final String TAG = "InstallManagerService";
|
||||||
|
|
||||||
private static final String ACTION_INSTALL = "org.fdroid.fdroid.InstallManagerService.action.INSTALL";
|
private static final String ACTION_INSTALL = "org.fdroid.fdroid.installer.action.INSTALL";
|
||||||
|
|
||||||
|
private static final String EXTRA_APP = "org.fdroid.fdroid.installer.extra.APP";
|
||||||
|
private static final String EXTRA_APK = "org.fdroid.fdroid.installer.extra.APK";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The collection of {@link Apk}s that are actively going through this whole process,
|
* The collection of {@link Apk}s that are actively going through this whole process,
|
||||||
@ -135,12 +139,31 @@ public class InstallManagerService extends Service {
|
|||||||
Utils.debugLog(TAG, "onStartCommand " + intent);
|
Utils.debugLog(TAG, "onStartCommand " + intent);
|
||||||
|
|
||||||
if (!ACTION_INSTALL.equals(intent.getAction())) {
|
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;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
String urlString = intent.getDataString();
|
String urlString = intent.getDataString();
|
||||||
Apk apk = ACTIVE_APKS.get(urlString);
|
if (TextUtils.isEmpty(urlString)) {
|
||||||
|
Utils.debugLog(TAG, "empty urlString, nothing to do");
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!intent.hasExtra(EXTRA_APP) || !intent.hasExtra(EXTRA_APK)) {
|
||||||
|
Utils.debugLog(TAG, urlString + " did not include both an App and Apk instance, ignoring");
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((flags & START_FLAG_REDELIVERY) == START_FLAG_REDELIVERY
|
||||||
|
&& !DownloaderService.isQueuedOrActive(urlString)) {
|
||||||
|
Utils.debugLog(TAG, urlString + " finished downloading while InstallManagerService was killed.");
|
||||||
|
cancelNotification(urlString);
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
App app = new App(intent.getParcelableExtra(EXTRA_APP));
|
||||||
|
Apk apk = new Apk(intent.getParcelableExtra(EXTRA_APK));
|
||||||
|
addToActive(urlString, app, apk);
|
||||||
|
|
||||||
Notification notification = createNotification(intent.getDataString(), apk).build();
|
Notification notification = createNotification(intent.getDataString(), apk).build();
|
||||||
notificationManager.notify(urlString.hashCode(), notification);
|
notificationManager.notify(urlString.hashCode(), notification);
|
||||||
@ -331,8 +354,18 @@ public class InstallManagerService extends Service {
|
|||||||
TEMP_HACK_APP_NAMES.put(urlString, app.name); // TODO delete me once InstallerService exists
|
TEMP_HACK_APP_NAMES.put(urlString, app.name); // TODO delete me once InstallerService exists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the {@link App} and {@Apk} instances that are associated with
|
||||||
|
* {@code urlString} from the {@link Map} of active apps. This can be
|
||||||
|
* called after this service has been destroyed and recreated based on the
|
||||||
|
* {@link BroadcastReceiver}s, in which case {@code urlString} would not
|
||||||
|
* find anything in the active maps.
|
||||||
|
*/
|
||||||
private static Apk removeFromActive(String urlString) {
|
private static Apk removeFromActive(String urlString) {
|
||||||
Apk apk = ACTIVE_APKS.remove(urlString);
|
Apk apk = ACTIVE_APKS.remove(urlString);
|
||||||
|
if (apk == null) {
|
||||||
|
return new Apk();
|
||||||
|
}
|
||||||
ACTIVE_APPS.remove(apk.packageName);
|
ACTIVE_APPS.remove(apk.packageName);
|
||||||
return apk;
|
return apk;
|
||||||
}
|
}
|
||||||
@ -346,10 +379,11 @@ public class InstallManagerService extends Service {
|
|||||||
public static void queue(Context context, App app, Apk apk) {
|
public static void queue(Context context, App app, Apk apk) {
|
||||||
String urlString = apk.getUrl();
|
String urlString = apk.getUrl();
|
||||||
Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString);
|
Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString);
|
||||||
addToActive(urlString, app, apk);
|
|
||||||
Intent intent = new Intent(context, InstallManagerService.class);
|
Intent intent = new Intent(context, InstallManagerService.class);
|
||||||
intent.setAction(ACTION_INSTALL);
|
intent.setAction(ACTION_INSTALL);
|
||||||
intent.setData(Uri.parse(urlString));
|
intent.setData(Uri.parse(urlString));
|
||||||
|
intent.putExtra(EXTRA_APP, app.toContentValues());
|
||||||
|
intent.putExtra(EXTRA_APK, apk.toContentValues());
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ public abstract class Installer {
|
|||||||
*/
|
*/
|
||||||
public void installPackage(File apkFile, String packageName, String urlString)
|
public void installPackage(File apkFile, String packageName, String urlString)
|
||||||
throws InstallFailedException {
|
throws InstallFailedException {
|
||||||
SanitizedFile apkToInstall;
|
SanitizedFile apkToInstall = null;
|
||||||
try {
|
try {
|
||||||
Map<String, Object> attributes = AndroidXMLDecompress.getManifestHeaderAttributes(apkFile.getAbsolutePath());
|
Map<String, Object> attributes = AndroidXMLDecompress.getManifestHeaderAttributes(apkFile.getAbsolutePath());
|
||||||
|
|
||||||
@ -232,6 +232,22 @@ public abstract class Installer {
|
|||||||
throw new InstallFailedException(e);
|
throw new InstallFailedException(e);
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new InstallFailedException("F-Droid Privileged can only be updated using an activity!");
|
throw new InstallFailedException("F-Droid Privileged can only be updated using an activity!");
|
||||||
|
} finally {
|
||||||
|
// 20 minutes the start of the install process, delete the file
|
||||||
|
final File apkToDelete = apkToInstall;
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
|
||||||
|
try {
|
||||||
|
Thread.sleep(1200000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
FileUtils.deleteQuietly(apkToDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package org.fdroid.fdroid.localrepo;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@ -24,6 +23,7 @@ import org.fdroid.fdroid.Hasher;
|
|||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
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.Apk;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
@ -237,6 +237,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() {
|
public void deleteRepo() {
|
||||||
deleteContents(repoDir);
|
deleteContents(repoDir);
|
||||||
}
|
}
|
||||||
@ -267,8 +274,6 @@ public final class LocalRepoManager {
|
|||||||
if (!app.isValid()) {
|
if (!app.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
|
|
||||||
app.icon = getIconFile(packageName, packageInfo.versionCode).getName();
|
|
||||||
} catch (PackageManager.NameNotFoundException | CertificateEncodingException | IOException e) {
|
} catch (PackageManager.NameNotFoundException | CertificateEncodingException | IOException e) {
|
||||||
Log.e(TAG, "Error adding app to local repo", e);
|
Log.e(TAG, "Error adding app to local repo", e);
|
||||||
return;
|
return;
|
||||||
@ -277,10 +282,6 @@ public final class LocalRepoManager {
|
|||||||
apps.put(packageName, app);
|
apps.put(packageName, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getApps() {
|
|
||||||
return new ArrayList<>(apps.keySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyIconsToRepo() {
|
public void copyIconsToRepo() {
|
||||||
ApplicationInfo appInfo;
|
ApplicationInfo appInfo;
|
||||||
for (final App app : apps.values()) {
|
for (final App app : apps.values()) {
|
||||||
@ -321,7 +322,7 @@ public final class LocalRepoManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private File getIconFile(String packageName, int versionCode) {
|
private File getIconFile(String packageName, int versionCode) {
|
||||||
return new File(iconsDir, packageName + "_" + versionCode + ".png");
|
return new File(iconsDir, App.getIconName(packageName, versionCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -451,11 +452,16 @@ public final class LocalRepoManager {
|
|||||||
tagHash(app);
|
tagHash(app);
|
||||||
tag("sig", app.installedApk.sig.toLowerCase(Locale.US));
|
tag("sig", app.installedApk.sig.toLowerCase(Locale.US));
|
||||||
tag("size", app.installedApk.installedFile.length());
|
tag("size", app.installedApk.installedFile.length());
|
||||||
tag("sdkver", app.installedApk.minSdkVersion);
|
|
||||||
tag("maxsdkver", app.installedApk.maxSdkVersion);
|
|
||||||
tag("added", app.installedApk.added);
|
tag("added", app.installedApk.added);
|
||||||
|
if (app.installedApk.minSdkVersion > Apk.SDK_VERSION_MIN_VALUE) {
|
||||||
|
tag("sdkver", app.installedApk.minSdkVersion);
|
||||||
|
}
|
||||||
|
if (app.installedApk.maxSdkVersion < Apk.SDK_VERSION_MAX_VALUE) {
|
||||||
|
tag("maxsdkver", app.installedApk.maxSdkVersion);
|
||||||
|
}
|
||||||
tagFeatures(app);
|
tagFeatures(app);
|
||||||
tagPermissions(app);
|
tagPermissions(app);
|
||||||
|
tagNativecode(app);
|
||||||
|
|
||||||
serializer.endTag("", "package");
|
serializer.endTag("", "package");
|
||||||
}
|
}
|
||||||
@ -485,6 +491,14 @@ public final class LocalRepoManager {
|
|||||||
serializer.endTag("", "features");
|
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 {
|
private void tagHash(App app) throws IOException {
|
||||||
serializer.startTag("", "hash");
|
serializer.startTag("", "hash");
|
||||||
serializer.attribute("", "type", app.installedApk.hashType);
|
serializer.attribute("", "type", app.installedApk.hashType);
|
||||||
|
@ -145,7 +145,7 @@ public abstract class Downloader {
|
|||||||
totalBytes = totalDownloadSize();
|
totalBytes = totalDownloadSize();
|
||||||
byte[] buffer = new byte[bufferSize];
|
byte[] buffer = new byte[bufferSize];
|
||||||
|
|
||||||
timer.scheduleAtFixedRate(progressTask, 0, 100);
|
timer.scheduleAtFixedRate(progressTask, 0, 500);
|
||||||
|
|
||||||
// Getting the total download size could potentially take time, depending on how
|
// Getting the total download size could potentially take time, depending on how
|
||||||
// it is implemented, so we may as well check this before we proceed.
|
// it is implemented, so we may as well check this before we proceed.
|
||||||
|
@ -30,7 +30,6 @@ import android.os.Looper;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.PatternMatcher;
|
import android.os.PatternMatcher;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.support.v4.content.IntentCompat;
|
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -147,8 +146,7 @@ public class DownloaderService extends Service {
|
|||||||
public static PendingIntent getCancelPendingIntent(Context context, String urlString) {
|
public static PendingIntent getCancelPendingIntent(Context context, String urlString) {
|
||||||
Intent cancelIntent = new Intent(context.getApplicationContext(), DownloaderService.class)
|
Intent cancelIntent = new Intent(context.getApplicationContext(), DownloaderService.class)
|
||||||
.setData(Uri.parse(urlString))
|
.setData(Uri.parse(urlString))
|
||||||
.setAction(ACTION_CANCEL)
|
.setAction(ACTION_CANCEL);
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
|
|
||||||
return PendingIntent.getService(context.getApplicationContext(),
|
return PendingIntent.getService(context.getApplicationContext(),
|
||||||
urlString.hashCode(),
|
urlString.hashCode(),
|
||||||
cancelIntent,
|
cancelIntent,
|
||||||
|
@ -36,6 +36,12 @@ import java.util.Locale;
|
|||||||
* which is how it can be triggered by code, or it came in from the system
|
* which is how it can be triggered by code, or it came in from the system
|
||||||
* via {@link org.fdroid.fdroid.receiver.WifiStateChangeReceiver}, in
|
* via {@link org.fdroid.fdroid.receiver.WifiStateChangeReceiver}, in
|
||||||
* which case an instance of {@link NetworkInfo} is included.
|
* which case an instance of {@link NetworkInfo} is included.
|
||||||
|
*
|
||||||
|
* The work is done in a {@link Thread} so that new incoming {@code Intents}
|
||||||
|
* are not blocked by processing. A new {@code Intent} immediately nullifies
|
||||||
|
* the current state because it means that something about the wifi has
|
||||||
|
* changed. Having the {@code Thread} also makes it easy to kill work
|
||||||
|
* that is in progress.
|
||||||
*/
|
*/
|
||||||
public class WifiStateChangeService extends IntentService {
|
public class WifiStateChangeService extends IntentService {
|
||||||
private static final String TAG = "WifiStateChangeService";
|
private static final String TAG = "WifiStateChangeService";
|
||||||
@ -52,8 +58,11 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
@Override
|
@Override
|
||||||
protected void onHandleIntent(Intent intent) {
|
protected void onHandleIntent(Intent intent) {
|
||||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
|
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
|
||||||
|
if (intent == null) {
|
||||||
|
Utils.debugLog(TAG, "received null Intent, ignoring");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Utils.debugLog(TAG, "WiFi change service started, clearing info about wifi state until we have figured it out again.");
|
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);
|
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
|
||||||
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
|
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
|
||||||
int wifiState = wifiManager.getWifiState();
|
int wifiState = wifiManager.getWifiState();
|
||||||
@ -79,6 +88,7 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
public void run() {
|
public void run() {
|
||||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
|
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
|
||||||
try {
|
try {
|
||||||
|
FDroidApp.initWifiSettings();
|
||||||
Utils.debugLog(TAG, "Checking wifi state (in background thread).");
|
Utils.debugLog(TAG, "Checking wifi state (in background thread).");
|
||||||
WifiInfo wifiInfo = null;
|
WifiInfo wifiInfo = null;
|
||||||
|
|
||||||
@ -95,7 +105,12 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
if (dhcpInfo != null) {
|
if (dhcpInfo != null) {
|
||||||
String netmask = formatIpAddress(dhcpInfo.netmask);
|
String netmask = formatIpAddress(dhcpInfo.netmask);
|
||||||
if (!TextUtils.isEmpty(FDroidApp.ipAddressString) && netmask != null) {
|
if (!TextUtils.isEmpty(FDroidApp.ipAddressString) && netmask != null) {
|
||||||
|
try {
|
||||||
FDroidApp.subnetInfo = new SubnetUtils(FDroidApp.ipAddressString, netmask).getInfo();
|
FDroidApp.subnetInfo = new SubnetUtils(FDroidApp.ipAddressString, netmask).getInfo();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// catch this mystery error: "java.lang.IllegalArgumentException: Could not parse [null/24]"
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|
||||||
@ -185,7 +200,11 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
@TargetApi(9)
|
@TargetApi(9)
|
||||||
private void setIpInfoFromNetworkInterface() {
|
private void setIpInfoFromNetworkInterface() {
|
||||||
try {
|
try {
|
||||||
for (Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements();) {
|
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||||
|
if (networkInterfaces == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (networkInterfaces.hasMoreElements()) {
|
||||||
NetworkInterface netIf = networkInterfaces.nextElement();
|
NetworkInterface netIf = networkInterfaces.nextElement();
|
||||||
|
|
||||||
for (Enumeration<InetAddress> inetAddresses = netIf.getInetAddresses(); inetAddresses.hasMoreElements();) {
|
for (Enumeration<InetAddress> inetAddresses = netIf.getInetAddresses(); inetAddresses.hasMoreElements();) {
|
||||||
@ -202,9 +221,15 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
}
|
}
|
||||||
// the following methods were not added until android-9/Gingerbread
|
// the following methods were not added until android-9/Gingerbread
|
||||||
for (InterfaceAddress address : netIf.getInterfaceAddresses()) {
|
for (InterfaceAddress address : netIf.getInterfaceAddresses()) {
|
||||||
|
short networkPrefixLength = address.getNetworkPrefixLength();
|
||||||
|
if (networkPrefixLength > 32) {
|
||||||
|
// something is giving a "/64" netmask, IPv6?
|
||||||
|
// java.lang.IllegalArgumentException: Value [64] not in range [0,32]
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (inetAddress.equals(address.getAddress()) && !TextUtils.isEmpty(FDroidApp.ipAddressString)) {
|
if (inetAddress.equals(address.getAddress()) && !TextUtils.isEmpty(FDroidApp.ipAddressString)) {
|
||||||
String cidr = String.format(Locale.ENGLISH, "%s/%d",
|
String cidr = String.format(Locale.ENGLISH, "%s/%d",
|
||||||
FDroidApp.ipAddressString, address.getNetworkPrefixLength());
|
FDroidApp.ipAddressString, networkPrefixLength);
|
||||||
FDroidApp.subnetInfo = new SubnetUtils(cidr).getInfo();
|
FDroidApp.subnetInfo = new SubnetUtils(cidr).getInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ public class BluetoothConnection {
|
|||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||||
public void open() throws IOException {
|
public void open() throws IOException {
|
||||||
if (!socket.isConnected()) {
|
if (Build.VERSION.SDK_INT >= 14 && !socket.isConnected()) {
|
||||||
// Server sockets will already be connected when they are passed to us,
|
// Server sockets will already be connected when they are passed to us,
|
||||||
// client sockets require us to call connect().
|
// client sockets require us to call connect().
|
||||||
socket.connect();
|
socket.connect();
|
||||||
|
@ -22,7 +22,6 @@ public abstract class AppListAdapter extends CursorAdapter {
|
|||||||
private DisplayImageOptions displayImageOptions;
|
private DisplayImageOptions displayImageOptions;
|
||||||
private String upgradeFromTo;
|
private String upgradeFromTo;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public AppListAdapter(Context context, Cursor c) {
|
public AppListAdapter(Context context, Cursor c) {
|
||||||
super(context, c);
|
super(context, c);
|
||||||
init(context);
|
init(context);
|
||||||
|
@ -2,18 +2,10 @@ package org.fdroid.fdroid.views;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
public class AvailableAppListAdapter extends AppListAdapter {
|
public class AvailableAppListAdapter extends AppListAdapter {
|
||||||
|
|
||||||
public static AvailableAppListAdapter create(Context context, Cursor cursor, int flags) {
|
public AvailableAppListAdapter(Context context, Cursor c) {
|
||||||
if (Build.VERSION.SDK_INT >= 11) {
|
|
||||||
return new AvailableAppListAdapter(context, cursor, flags);
|
|
||||||
}
|
|
||||||
return new AvailableAppListAdapter(context, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AvailableAppListAdapter(Context context, Cursor c) {
|
|
||||||
super(context, c);
|
super(context, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +13,7 @@ public class AvailableAppListAdapter extends AppListAdapter {
|
|||||||
super(context, c, autoRequery);
|
super(context, c, autoRequery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AvailableAppListAdapter(Context context, Cursor c, int flags) {
|
public AvailableAppListAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,10 @@ package org.fdroid.fdroid.views;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
public class CanUpdateAppListAdapter extends AppListAdapter {
|
public class CanUpdateAppListAdapter extends AppListAdapter {
|
||||||
|
|
||||||
public static CanUpdateAppListAdapter create(Context context, Cursor cursor, int flags) {
|
public CanUpdateAppListAdapter(Context context, Cursor c) {
|
||||||
if (Build.VERSION.SDK_INT >= 11) {
|
|
||||||
return new CanUpdateAppListAdapter(context, cursor, flags);
|
|
||||||
}
|
|
||||||
return new CanUpdateAppListAdapter(context, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CanUpdateAppListAdapter(Context context, Cursor c) {
|
|
||||||
super(context, c);
|
super(context, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +13,7 @@ public class CanUpdateAppListAdapter extends AppListAdapter {
|
|||||||
super(context, c, autoRequery);
|
super(context, c, autoRequery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CanUpdateAppListAdapter(Context context, Cursor c, int flags) {
|
public CanUpdateAppListAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,10 @@ package org.fdroid.fdroid.views;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
public class InstalledAppListAdapter extends AppListAdapter {
|
public class InstalledAppListAdapter extends AppListAdapter {
|
||||||
|
|
||||||
public static InstalledAppListAdapter create(Context context, Cursor cursor, int flags) {
|
public InstalledAppListAdapter(Context context, Cursor c) {
|
||||||
if (Build.VERSION.SDK_INT >= 11) {
|
|
||||||
return new InstalledAppListAdapter(context, cursor, flags);
|
|
||||||
}
|
|
||||||
return new InstalledAppListAdapter(context, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private InstalledAppListAdapter(Context context, Cursor c) {
|
|
||||||
super(context, c);
|
super(context, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +13,7 @@ public class InstalledAppListAdapter extends AppListAdapter {
|
|||||||
super(context, c, autoRequery);
|
super(context, c, autoRequery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InstalledAppListAdapter(Context context, Cursor c, int flags) {
|
public InstalledAppListAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -767,7 +767,7 @@ public class ManageReposActivity extends ActionBarActivity {
|
|||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
repoAdapter = RepoAdapter.create(getActivity(), null, 0);
|
repoAdapter = new RepoAdapter(getActivity(), null);
|
||||||
repoAdapter.setEnabledListener(this);
|
repoAdapter.setEnabledListener(this);
|
||||||
setListAdapter(repoAdapter);
|
setListAdapter(repoAdapter);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package org.fdroid.fdroid.views;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Build;
|
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -23,14 +22,7 @@ public class RepoAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
private EnabledListener enabledListener;
|
private EnabledListener enabledListener;
|
||||||
|
|
||||||
public static RepoAdapter create(Context context, Cursor cursor, int flags) {
|
public RepoAdapter(Context context, Cursor c, int flags) {
|
||||||
if (Build.VERSION.SDK_INT >= 11) {
|
|
||||||
return new RepoAdapter(context, cursor, flags);
|
|
||||||
}
|
|
||||||
return new RepoAdapter(context, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RepoAdapter(Context context, Cursor c, int flags) {
|
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
inflater = LayoutInflater.from(context);
|
inflater = LayoutInflater.from(context);
|
||||||
}
|
}
|
||||||
@ -40,8 +32,7 @@ public class RepoAdapter extends CursorAdapter {
|
|||||||
inflater = LayoutInflater.from(context);
|
inflater = LayoutInflater.from(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
public RepoAdapter(Context context, Cursor c) {
|
||||||
private RepoAdapter(Context context, Cursor c) {
|
|
||||||
super(context, c);
|
super(context, c);
|
||||||
inflater = LayoutInflater.from(context);
|
inflater = LayoutInflater.from(context);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ public abstract class AppListFragment extends ListFragment implements
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
//Starts a new or restarts an existing Loader in this manager
|
//Starts a new or restarts an existing Loader in this manager
|
||||||
getLoaderManager().restartLoader(0, null, this);
|
getLoaderManager().initLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -59,7 +59,7 @@ public class AvailableAppsFragment extends AppListFragment implements
|
|||||||
@Override
|
@Override
|
||||||
protected AppListAdapter getAppListAdapter() {
|
protected AppListAdapter getAppListAdapter() {
|
||||||
if (adapter == null) {
|
if (adapter == null) {
|
||||||
final AppListAdapter a = AvailableAppListAdapter.create(getActivity(), null, 0);
|
final AppListAdapter a = new AvailableAppListAdapter(getActivity(), null);
|
||||||
Preferences.get().registerUpdateHistoryListener(new Preferences.ChangeListener() {
|
Preferences.get().registerUpdateHistoryListener(new Preferences.ChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPreferenceChange() {
|
public void onPreferenceChange() {
|
||||||
@ -205,7 +205,6 @@ public class AvailableAppsFragment extends AppListFragment implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
|
||||||
/* restore the saved Category Spinner position */
|
/* restore the saved Category Spinner position */
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
SharedPreferences p = activity.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
|
SharedPreferences p = activity.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
|
||||||
@ -221,6 +220,7 @@ public class AvailableAppsFragment extends AppListFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
setCurrentCategory(currentCategory);
|
setCurrentCategory(currentCategory);
|
||||||
|
super.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,7 +15,7 @@ public class CanUpdateAppsFragment extends AppListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AppListAdapter getAppListAdapter() {
|
protected AppListAdapter getAppListAdapter() {
|
||||||
return CanUpdateAppListAdapter.create(getActivity(), null, 0);
|
return new CanUpdateAppListAdapter(getActivity(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,7 +15,7 @@ public class InstalledAppsFragment extends AppListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AppListAdapter getAppListAdapter() {
|
protected AppListAdapter getAppListAdapter() {
|
||||||
return InstalledAppListAdapter.create(getActivity(), null, 0);
|
return new InstalledAppListAdapter(getActivity(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,7 +26,7 @@ import android.widget.CompoundButton;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.ScrollView;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
@ -42,7 +42,7 @@ import cc.mvdan.accesspoint.WifiApControl;
|
|||||||
import rx.Subscriber;
|
import rx.Subscriber;
|
||||||
import rx.Subscription;
|
import rx.Subscription;
|
||||||
|
|
||||||
public class StartSwapView extends ScrollView implements SwapWorkflowActivity.InnerView {
|
public class StartSwapView extends RelativeLayout implements SwapWorkflowActivity.InnerView {
|
||||||
|
|
||||||
private static final String TAG = "StartSwapView";
|
private static final String TAG = "StartSwapView";
|
||||||
|
|
||||||
|
@ -245,6 +245,9 @@ public class SwapAppsView extends ListView implements
|
|||||||
private final BroadcastReceiver downloadProgressReceiver = new BroadcastReceiver() {
|
private final BroadcastReceiver downloadProgressReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (progressView.getVisibility() != View.VISIBLE) {
|
||||||
|
showProgress();
|
||||||
|
}
|
||||||
int read = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
|
int read = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
|
||||||
int total = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0);
|
int total = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0);
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
|
@ -376,7 +376,10 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
getService().swapWith(null);
|
getService().swapWith(null);
|
||||||
|
|
||||||
if (!getService().isEnabled()) {
|
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);
|
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
|
* 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:
|
* show the next screen to the user. This will be one of two things:
|
||||||
|
7
app/src/main/res/layout-ldpi/start_swap_header.xml
Normal file
7
app/src/main/res/layout-ldpi/start_swap_header.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true">
|
||||||
|
</RelativeLayout>
|
7
app/src/main/res/layout-small/start_swap_header.xml
Normal file
7
app/src/main/res/layout-small/start_swap_header.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true">
|
||||||
|
</RelativeLayout>
|
32
app/src/main/res/layout/start_swap_header.xml
Normal file
32
app/src/main/res/layout/start_swap_header.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="130dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
tools:showIn="@layout/swap_blank">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/swap_start_header" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:paddingLeft="30dp"
|
||||||
|
android:paddingRight="30dp"
|
||||||
|
android:paddingEnd="30dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:text="@string/swap_intro"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -8,63 +8,19 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/header"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="130dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="centerCrop"
|
tools:context=".views.swap.SwapWorkflowActivity">
|
||||||
android:src="@drawable/swap_start_header"/>
|
|
||||||
|
|
||||||
<!--
|
<!-- Misc header -->
|
||||||
Removed for now, because there is no UI to hook this up to.
|
<include layout="@layout/start_swap_header" />
|
||||||
However, the general principle of having a help screen made accessible from the
|
|
||||||
start swap screen is still desirable, and so should be revisited in the future.
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:src="@drawable/ic_info_white"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginRight="10dp"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
/>-->
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:padding="20dp"
|
|
||||||
android:paddingLeft="30dp"
|
|
||||||
android:paddingRight="30dp"
|
|
||||||
android:paddingEnd="30dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:text="@string/swap_intro"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:ignore="UnusedAttribute" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
|
<!-- Bluetooth swap status + toggle -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/bluetooth_info"
|
android:id="@+id/bluetooth_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/header"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
@ -107,9 +63,12 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- WiFi swap status + toggle -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/wifi_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/bluetooth_info"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
@ -160,9 +119,12 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Feedback for "searching for nearby people..." -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/feedback_searching"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/wifi_info"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="20dp"
|
||||||
android:paddingEnd="20dp"
|
android:paddingEnd="20dp"
|
||||||
android:paddingBottom="5dp"
|
android:paddingBottom="5dp"
|
||||||
@ -184,28 +146,14 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ListView
|
|
||||||
android:id="@+id/list_people_nearby"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
</ListView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/swap_cant_find_peers"
|
|
||||||
android:paddingLeft="20dp"
|
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingRight="20dp"
|
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingTop="20dp"
|
|
||||||
android:textColor="@color/swap_light_text" />
|
|
||||||
|
|
||||||
|
<!-- Buttons to help the user when they can't find any peers. Shown at bottom of relative layout -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/cant_find_peers"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btn_send_fdroid"
|
android:id="@+id/btn_send_fdroid"
|
||||||
@ -235,6 +183,28 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<!-- Heading for "can't find peers" -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/header_cant_find_peers"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_above="@id/cant_find_peers"
|
||||||
|
android:text="@string/swap_cant_find_peers"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingRight="20dp"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:textColor="@color/swap_light_text" />
|
||||||
|
|
||||||
|
<!-- List of all currently known peers (i.e. bluetooth and wifi devices that have been identified -->
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/list_people_nearby"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/feedback_searching"
|
||||||
|
android:layout_above="@id/header_cant_find_peers">
|
||||||
|
|
||||||
|
</ListView>
|
||||||
|
|
||||||
</org.fdroid.fdroid.views.swap.StartSwapView>
|
</org.fdroid.fdroid.views.swap.StartSwapView>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="SignatureMismatch">La versión nueva ta roblada con una clave diferente. Pa instalar la versión nueva, tien de desinstalase primero la vieya. Por favor, failo ya inténtalo de nueves. (Decátate que la desinstalación desaniciará cualesquier datu internu atroxáu pola aplicación)</string>
|
<string name="SignatureMismatch">La versión nueva ta roblada con una clave diferente. Pa instalar la versión nueva, tien de desinstalase primero la vieya. Por favor, failo y volvi tentalo. (Decátate que la desinstalación desaniciará cualesquier datu internu atroxáu pola aplicación)</string>
|
||||||
<string name="installIncompatible">Paez qu\'esti paquete nun ye compatible col to preséu. ¿Quies intentar instalalu de toes toes?</string>
|
<string name="installIncompatible">Paez qu\'esti paquete nun ye compatible col to preséu. ¿Quies tentar d\'instalalu de toes toes?</string>
|
||||||
<string name="installDowngrade">Tas intentando baxar de versión esta aplicación. Eso quiciabes faiga que l\'aplicación furrule mal o incluso, pierdas los tos datos. ¿Quies intentalo y baxala de versión de toes toes?</string>
|
<string name="installDowngrade">Tas tentando de baxar de versión esta aplicación. Eso quiciabes faiga que l\'aplicación furrule mal o incluso, pierdas los tos datos. ¿Quies tentalo y baxala de versión de toes toes?</string>
|
||||||
<string name="version">Versión</string>
|
<string name="version">Versión</string>
|
||||||
<string name="delete">Desaniciar</string>
|
<string name="delete">Desaniciar</string>
|
||||||
<string name="enable_nfc_send">Habilitar unviu per NFC…</string>
|
<string name="enable_nfc_send">Habilitar unviu per NFC…</string>
|
||||||
@ -135,18 +135,18 @@
|
|||||||
<string name="repo_name">Nome</string>
|
<string name="repo_name">Nome</string>
|
||||||
<string name="unsigned_description">Esto quier dicir que nun pudo verificase\'l llistáu d\'aplicaciones.
|
<string name="unsigned_description">Esto quier dicir que nun pudo verificase\'l llistáu d\'aplicaciones.
|
||||||
Deberíes tener procuru con aplicaciones baxaes dende índices ensin roblar.</string>
|
Deberíes tener procuru con aplicaciones baxaes dende índices ensin roblar.</string>
|
||||||
<string name="repo_not_yet_updated">Entá nun s\'usó esti repositoriu. Necesites anovalu pa ver les aplicaciones que forne.</string>
|
<string name="repo_not_yet_updated">Entá nun s\'usó esti repositoriu. Precises anovalu pa ver les aplicaciones que forne.</string>
|
||||||
<string name="unknown">Desconocíu</string>
|
<string name="unknown">Desconocíu</string>
|
||||||
<string name="repo_confirm_delete_title">¿Desaniciar repositoriu?</string>
|
<string name="repo_confirm_delete_title">¿Desaniciar repositoriu?</string>
|
||||||
<string name="repo_confirm_delete_body">Desaniciar un repositoriu quier dicir que les aplicaciones d\'elli nun tarán disponibles dende F-Droid.
|
<string name="repo_confirm_delete_body">Desaniciar un repositoriu quier dicir que les aplicaciones d\'elli nun tarán disponibles dende F-Droid.
|
||||||
\n\nNota: Toles aplicaciones instalaes d\'enantes quedaránse nel preséu.</string>
|
\n\nNota: Toles aplicaciones instalaes d\'enantes quedaránse nel preséu.</string>
|
||||||
<string name="repo_disabled_notification">Deshabilitóse %1$s.\n\nNecesitarás rehabilitar esti repositoriu pa instalar aplicaciones dende elli.</string>
|
<string name="repo_disabled_notification">Deshabilitóse %1$s.\n\nPrecisarás rehabilitar esti repositoriu pa instalar aplicaciones dende elli.</string>
|
||||||
<string name="repo_added">Guardóse\'l repositoriu F-Droid %1$s.</string>
|
<string name="repo_added">Guardóse\'l repositoriu F-Droid %1$s.</string>
|
||||||
<string name="repo_searching_address">Guetando repositorios F-Droid en\n%1$s</string>
|
<string name="repo_searching_address">Guetando repositorios F-Droid en\n%1$s</string>
|
||||||
<string name="minsdk_or_later">%s ó posterior</string>
|
<string name="minsdk_or_later">%s ó posterior</string>
|
||||||
<string name="up_to_maxsdk">Fasta %s</string>
|
<string name="up_to_maxsdk">Fasta %s</string>
|
||||||
<string name="minsdk_up_to_maxsdk">De %1$s fasta %2$s</string>
|
<string name="minsdk_up_to_maxsdk">De %1$s fasta %2$s</string>
|
||||||
<string name="not_on_same_wifi">¡El to preséu nun ta na mesma rede Wi-Fi que\'l repositoriu llocal que tas acabante d\'amestar! Intenta xunite a esta rede: %s</string>
|
<string name="not_on_same_wifi">¡El to preséu nun ta na mesma rede Wi-Fi que\'l repositoriu llocal que tas acabante d\'amestar! Tenta de xunite a esta rede: %s</string>
|
||||||
<string name="requires_features">Rique: %1$s</string>
|
<string name="requires_features">Rique: %1$s</string>
|
||||||
<string name="app_icon">Iconu d\'aplicación</string>
|
<string name="app_icon">Iconu d\'aplicación</string>
|
||||||
<string name="category_Development">Desendolcu</string>
|
<string name="category_Development">Desendolcu</string>
|
||||||
@ -229,9 +229,9 @@ Deberíes tener procuru con aplicaciones baxaes dende índices ensin roblar.</st
|
|||||||
<string name="category_Time">Tiempu</string>
|
<string name="category_Time">Tiempu</string>
|
||||||
<string name="category_Writing">Escritura</string>
|
<string name="category_Writing">Escritura</string>
|
||||||
|
|
||||||
<string name="empty_installed_app_list">Nun hai aplicaciones instalaes.\n\nHai aplicaciones nel to preséu pero nun tán disponibles dende F-Droid. Esto quiciabes sía porque necesites anovar los tos repositorios o porque los repositorios nun tengan daveres les tos aplicaciones disponibles.</string>
|
<string name="empty_installed_app_list">Nun hai aplicaciones instalaes.\n\nHai aplicaciones nel to preséu pero nun tán disponibles dende F-Droid. Esto quiciabes sía porque precises anovar los tos repositorios o porque los repositorios nun tengan daveres les tos aplicaciones disponibles.</string>
|
||||||
<string name="empty_available_app_list">Nun hai aplicaciones nesta estaya.\n\nIntenta esbillar una estaya distinta o anovar los tos repositorios pa consiguir un llistáu frescu d\'aplicaciones.</string>
|
<string name="empty_available_app_list">Nun hai aplicaciones nesta estaya.\n\nTenta d\'esbillar una estaya distinta o anovar los tos repositorios pa consiguir un llistáu frescu d\'aplicaciones.</string>
|
||||||
<string name="empty_can_update_app_list">Tán anovaes toles aplicaciones.\n\n¡Norabona! Toles tos aplicaciones tán anovaes (o los tos repositorios tán ensin anovar).</string>
|
<string name="empty_can_update_app_list">Anováronse toles aplicaciones.\n\n¡Norabona! Toles tos aplicaciones tán anovaes (o los tos repositorios tán ensin anovar).</string>
|
||||||
|
|
||||||
<string name="install_error_title">Fallu d\'instalación</string>
|
<string name="install_error_title">Fallu d\'instalación</string>
|
||||||
<string name="install_error_unknown">Fallu al instalar pola mor d\'un fallu desconocíu</string>
|
<string name="install_error_unknown">Fallu al instalar pola mor d\'un fallu desconocíu</string>
|
||||||
@ -255,7 +255,7 @@ Deberíes tener procuru con aplicaciones baxaes dende índices ensin roblar.</st
|
|||||||
<string name="swap_nfc_title">Toca pa intercambiar</string>
|
<string name="swap_nfc_title">Toca pa intercambiar</string>
|
||||||
<string name="swap_join_same_wifi_desc">Pa intercambiar usando W-FI asegúrate que tais na mesma rede. Si nun tienéis accesu a la mesma rede, ún de vós pues crear una fastera Wi-Fi.</string>
|
<string name="swap_join_same_wifi_desc">Pa intercambiar usando W-FI asegúrate que tais na mesma rede. Si nun tienéis accesu a la mesma rede, ún de vós pues crear una fastera Wi-Fi.</string>
|
||||||
<string name="swap_join_this_hotspot">Ayuda al to collaciu a coneutalu cola to fastera Wi-Fi</string>
|
<string name="swap_join_this_hotspot">Ayuda al to collaciu a coneutalu cola to fastera Wi-Fi</string>
|
||||||
<string name="swap_scan_or_type_url">Una persona necesita escaniar el códigu o escribir la URL nel restolador d\'otru.</string>
|
<string name="swap_scan_or_type_url">Una persona precisa escaniar el códigu o escribir la URL nel restolador d\'otru.</string>
|
||||||
<string name="swap_choose_apps">Escoyer aplicaciones</string>
|
<string name="swap_choose_apps">Escoyer aplicaciones</string>
|
||||||
<string name="swap_scan_qr">Escaniar códigu QR</string>
|
<string name="swap_scan_qr">Escaniar códigu QR</string>
|
||||||
<string name="swap_people_nearby">Xente averao</string>
|
<string name="swap_people_nearby">Xente averao</string>
|
||||||
@ -266,7 +266,7 @@ Deberíes tener procuru con aplicaciones baxaes dende índices ensin roblar.</st
|
|||||||
<string name="swap_setting_up_wifi">Configurando Wi-Fi…</string>
|
<string name="swap_setting_up_wifi">Configurando Wi-Fi…</string>
|
||||||
<string name="swap_not_visible_wifi">Nun ye visible pente Wi-Fi</string>
|
<string name="swap_not_visible_wifi">Nun ye visible pente Wi-Fi</string>
|
||||||
<string name="swap_no_peers_nearby">Nun pudo alcontrase xente averao cola qu\'intercambiar.</string>
|
<string name="swap_no_peers_nearby">Nun pudo alcontrase xente averao cola qu\'intercambiar.</string>
|
||||||
<string name="swap_attempt_install">Intentar instalación</string>
|
<string name="swap_attempt_install">Tentar d\'instalar</string>
|
||||||
<string name="swap_not_enabled_description">Enantes d\'intercambiar, el to preséu ha tar visible.</string>
|
<string name="swap_not_enabled_description">Enantes d\'intercambiar, el to preséu ha tar visible.</string>
|
||||||
|
|
||||||
<string name="install_confirm">¿Quies instalar esta aplicación? Tendrá accesu a:</string>
|
<string name="install_confirm">¿Quies instalar esta aplicación? Tendrá accesu a:</string>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<string name="version">Versjon</string>
|
<string name="version">Versjon</string>
|
||||||
<string name="delete">Slett</string>
|
<string name="delete">Slett</string>
|
||||||
<string name="enable_nfc_send">Skru på NFC-sending…</string>
|
<string name="enable_nfc_send">Skru på NFC-sending…</string>
|
||||||
<string name="cache_downloaded">Lagre nedlastede programmer i mellomlager</string>
|
<string name="cache_downloaded">Behold mellomlagrede programmer</string>
|
||||||
<string name="updates">Oppdateringer</string>
|
<string name="updates">Oppdateringer</string>
|
||||||
<string name="other">Andre</string>
|
<string name="other">Andre</string>
|
||||||
<string name="update_interval">Intervall for automatisk oppdatering</string>
|
<string name="update_interval">Intervall for automatisk oppdatering</string>
|
||||||
@ -344,4 +344,11 @@
|
|||||||
|
|
||||||
<string name="downloading_apk">Laster ned %1$s</string>
|
<string name="downloading_apk">Laster ned %1$s</string>
|
||||||
|
|
||||||
|
<string name="keep_hour">én time</string>
|
||||||
|
<string name="keep_day">én dag</string>
|
||||||
|
<string name="keep_week">ei uke</string>
|
||||||
|
<string name="keep_month">én måned</string>
|
||||||
|
<string name="keep_year">ett år</string>
|
||||||
|
<string name="keep_forever">For alltid</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<string name="version">Versie</string>
|
<string name="version">Versie</string>
|
||||||
<string name="delete">Verwijderen</string>
|
<string name="delete">Verwijderen</string>
|
||||||
<string name="enable_nfc_send">Versturen via NFC aanzetten…</string>
|
<string name="enable_nfc_send">Versturen via NFC aanzetten…</string>
|
||||||
<string name="cache_downloaded">Bewaar gedownloade apps</string>
|
<string name="cache_downloaded">Bewaar gecachete apps</string>
|
||||||
<string name="updates">Updates</string>
|
<string name="updates">Updates</string>
|
||||||
<string name="other">Overig</string>
|
<string name="other">Overig</string>
|
||||||
<string name="update_interval">Automatische vernieuwingsinterval</string>
|
<string name="update_interval">Automatische vernieuwingsinterval</string>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<string name="version">Versione</string>
|
<string name="version">Versione</string>
|
||||||
<string name="delete">Burra</string>
|
<string name="delete">Burra</string>
|
||||||
<string name="enable_nfc_send">Abìlita imbiu NFC…</string>
|
<string name="enable_nfc_send">Abìlita imbiu NFC…</string>
|
||||||
<string name="cache_downloaded">Pachetos cache</string>
|
<string name="cache_downloaded">Mantènne sas aplicatziones in sa cache</string>
|
||||||
<string name="updates">Agiornamentos</string>
|
<string name="updates">Agiornamentos</string>
|
||||||
<string name="other">Àteru</string>
|
<string name="other">Àteru</string>
|
||||||
<string name="update_interval">Intervallu agiornamentu automàticu</string>
|
<string name="update_interval">Intervallu agiornamentu automàticu</string>
|
||||||
@ -346,4 +346,13 @@
|
|||||||
|
|
||||||
<string name="downloading_apk">Iscarrighende %1$s</string>
|
<string name="downloading_apk">Iscarrighende %1$s</string>
|
||||||
|
|
||||||
|
<string name="system_install_not_supported">S\'installatzione de s\'estensione F-Droid cun permissos de sistema no est, pro como, suportada pro Android 5.1 o prus nou.</string>
|
||||||
|
|
||||||
|
<string name="keep_hour">1 Ora</string>
|
||||||
|
<string name="keep_day">1 Die</string>
|
||||||
|
<string name="keep_week">1 Chida</string>
|
||||||
|
<string name="keep_month">1 Mese</string>
|
||||||
|
<string name="keep_year">1 Annu</string>
|
||||||
|
<string name="keep_forever">Pro semper</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="SignatureMismatch">Нова версія підписана не тим ключем, що стара. Перш ніж встановити нову версію, самостійно зітріть стару. (Зауважте, що стирання програми призведе до знищення всіх даних цієї програми)</string>
|
<string name="SignatureMismatch">Нова версія підписана не тим ключем, що стара. Перш ніж встановити нову версію, самостійно зітріть стару. (Зауважте, що стирання програми призведе до знищення всіх даних цієї програми)</string>
|
||||||
<string name="version">Версія</string>
|
<string name="version">Версія</string>
|
||||||
<string name="cache_downloaded">Зберігати пакунки</string>
|
<string name="cache_downloaded">Зберігати кешовані пакунки</string>
|
||||||
<string name="updates">Оновлення</string>
|
<string name="updates">Оновлення</string>
|
||||||
<string name="notify">Сповіщення про оновлення</string>
|
<string name="notify">Сповіщення про оновлення</string>
|
||||||
<string name="about_title">Про F-Droid</string>
|
<string name="about_title">Про F-Droid</string>
|
||||||
@ -352,4 +352,15 @@
|
|||||||
<string name="update_auto_download_summary">Завантажувати файли оновлення у фоновому режимі</string>
|
<string name="update_auto_download_summary">Завантажувати файли оновлення у фоновому режимі</string>
|
||||||
<string name="tap_to_install_format">Торкніться для встановлення %s</string>
|
<string name="tap_to_install_format">Торкніться для встановлення %s</string>
|
||||||
<string name="tap_to_update_format">Торкніться для оновлення %s</string>
|
<string name="tap_to_update_format">Торкніться для оновлення %s</string>
|
||||||
|
<string name="download_pending">Очікування початку завантаження…</string>
|
||||||
|
|
||||||
|
<string name="downloading_apk">Завантаження %1$s</string>
|
||||||
|
|
||||||
|
<string name="keep_hour">1 година</string>
|
||||||
|
<string name="keep_day">1 день</string>
|
||||||
|
<string name="keep_week">1 тиждень</string>
|
||||||
|
<string name="keep_month">1 місяць</string>
|
||||||
|
<string name="keep_year">1 рік</string>
|
||||||
|
<string name="keep_forever">Завжди</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -322,4 +322,6 @@
|
|||||||
<string name="swap_view_available_networks">點擊打開可用的網路</string>
|
<string name="swap_view_available_networks">點擊打開可用的網路</string>
|
||||||
<string name="swap_switch_to_wifi">點擊以切換到一個 Wi-Fi 網路</string>
|
<string name="swap_switch_to_wifi">點擊以切換到一個 Wi-Fi 網路</string>
|
||||||
<string name="swap_confirm_connect">您現在想要從 %1$s 取得應用程式嗎?</string>
|
<string name="swap_confirm_connect">您現在想要從 %1$s 取得應用程式嗎?</string>
|
||||||
|
<string name="swap_nearby">近距交換</string>
|
||||||
|
<string name="swap_intro">與您附近的人連結並且交換應用程式。</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user