Compare commits

...

30 Commits

Author SHA1 Message Date
Daniel Martí
876ab253b5 Bump to 0.100 2016-06-07 21:33:17 +01:00
Peter Serwylo
3680c6b914 Use initLoader instead of resetartLoader. Call onResume after setting category.
While investigating the infamous issue #606, I noticed these two things which
were a little off. Firstly, we were doing more queries than required, because the
loader was being forceably recreated when it could instead be reused in onResume.

Also, the onResume method (which results in a cursor beign created) should be
called _after_ setting the selected category. This ensures that the query
which is run has the correct category the first time, and needn't be run again.

I am not confident that this fixes the issue, but it seems to help, and I believe
it is the correct thing to do even if it doesn't fix 606.
2016-06-07 21:17:20 +01:00
Hans-Christoph Steiner
198ad843c1 fix crash when UpdateService receives null Intent
I have no idea what would send UpdateService a null Intent, but there have
been reports from ACRA:

ANDROID_VERSION=5.1.1
APP_VERSION_NAME=0.99.2
BRAND=samsung
PHONE_MODEL=SM-G901F
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference
	at org.fdroid.fdroid.UpdateService.onHandleIntent(UpdateService.java:342)
	at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
	at android.os.Handler.dispatchMessage(Handler.java:102)
	at android.os.Looper.loop(Looper.java:135)
	at android.os.HandlerThread.run(HandlerThread.java:61)
2016-05-30 11:21:33 +02:00
Daniel Martí
01de14f84e Merge branch 'stable-v0.100' into 'stable-v0.100'
fix another random crash in WifiStateChangeService

Some of these devices do shitty things.

htc_europe HTC EVO 3D X515m

java.lang.RuntimeException: An error occured while executing doInBackground()
 at android.os.AsyncTask$3.done(AsyncTask.java:278)
 at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
 at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
 at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
 at java.util.concurrent.FutureTask.run(FutureTask.java:137)
 at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
 at java.lang.Thread.run(Thread.java:864)
Caused by: java.lang.NullPointerException
 at java.net.NetworkInterface.getNetworkInterfacesList(NetworkInterface.java:286)
 at java.net.NetworkInterface.getNetworkInterfaces(NetworkInterface.java:262)
 at org.fdroid.fdroid.net.WifiStateChangeService.setIpInfoFromNetworkInterface(WifiStateChangeService.java:202)
 at org.fdroid.fdroid.net.WifiStateChangeService.access$300(WifiStateChangeService.java:37)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:99)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:71)
 at android.os.AsyncTask$2.call(AsyncTask.java:264)
 at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
 ... 5 more
java.lang.NullPointerException
 at java.net.NetworkInterface.getNetworkInterfacesList(NetworkInterface.java:286)
 at java.net.NetworkInterface.getNetworkInterfaces(NetworkInterface.java:262)
 at org.fdroid.fdroid.net.WifiStateChangeService.setIpInfoFromNetworkInterface(WifiStateChangeService.java:202)
 at org.fdroid.fdroid.net.WifiStateChangeService.access$300(WifiStateChangeService.java:37)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:99)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:71)
 at android.os.AsyncTask$2.call(AsyncTask.java:264)
 at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
 at java.util.concurrent.FutureTask.run(FutureTask.java:137)
 at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
 at java.lang.Thread.run(Thread.java:864)

See merge request !315
2016-05-27 23:00:32 +00:00
Hans-Christoph Steiner
ae15e3a22d fix random crash in WifiStateChangeService
Some of these devices do shitty things.

htc_europe HTC EVO 3D X515m

java.lang.RuntimeException: An error occured while executing doInBackground()
 at android.os.AsyncTask$3.done(AsyncTask.java:278)
 at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
 at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
 at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
 at java.util.concurrent.FutureTask.run(FutureTask.java:137)
 at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
 at java.lang.Thread.run(Thread.java:864)
Caused by: java.lang.NullPointerException
 at java.net.NetworkInterface.getNetworkInterfacesList(NetworkInterface.java:286)
 at java.net.NetworkInterface.getNetworkInterfaces(NetworkInterface.java:262)
 at org.fdroid.fdroid.net.WifiStateChangeService.setIpInfoFromNetworkInterface(WifiStateChangeService.java:202)
 at org.fdroid.fdroid.net.WifiStateChangeService.access$300(WifiStateChangeService.java:37)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:99)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:71)
 at android.os.AsyncTask$2.call(AsyncTask.java:264)
 at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
 ... 5 more
java.lang.NullPointerException
 at java.net.NetworkInterface.getNetworkInterfacesList(NetworkInterface.java:286)
 at java.net.NetworkInterface.getNetworkInterfaces(NetworkInterface.java:262)
 at org.fdroid.fdroid.net.WifiStateChangeService.setIpInfoFromNetworkInterface(WifiStateChangeService.java:202)
 at org.fdroid.fdroid.net.WifiStateChangeService.access$300(WifiStateChangeService.java:37)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:99)
 at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:71)
 at android.os.AsyncTask$2.call(AsyncTask.java:264)
 at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
 at java.util.concurrent.FutureTask.run(FutureTask.java:137)
 at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
 at java.lang.Thread.run(Thread.java:864)
2016-05-28 00:41:58 +02:00
Daniel Martí
ba2f706166 Merge branch 'stable-v0.100' into 'stable-v0.100'
fix random crash in WifiStateChangeService

```
java.lang.NullPointerException: Attempt to invoke virtual method
'android.os.Parcelable android.content.Intent.getParcelableExtra(java.lang.String)' on a null object reference
  at org.fdroid.fdroid.net.WifiStateChangeService.onHandleIntent(WifiStateChangeService.java:56)
  at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:135)
  at android.os.HandlerThread.run(HandlerThread.java:61)
```

See merge request !314
2016-05-27 22:35:54 +00:00
Hans-Christoph Steiner
7b54c07c31 fix random crash in WifiStateChangeService
java.lang.NullPointerException: Attempt to invoke virtual method
'android.os.Parcelable android.content.Intent.getParcelableExtra(java.lang.String)' on a null object reference
  at org.fdroid.fdroid.net.WifiStateChangeService.onHandleIntent(WifiStateChangeService.java:56)
  at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:135)
  at android.os.HandlerThread.run(HandlerThread.java:61)
2016-05-28 00:13:22 +02:00
Hans-Christoph Steiner
55f4a5938e prevent crash caused by bad netmask given to WifiStateChangeService
My guess is that is from IPv6, but those should be filtered out in this
code before it gets to the crash point.  Here's the stacktrace:

java.lang.RuntimeException: An error occured while executing doInBackground()
	at android.os.AsyncTask$3.done(AsyncTask.java:300)
	at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
	at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
	at java.util.concurrent.FutureTask.run(FutureTask.java:242)
	at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
	at java.lang.Thread.run(Thread.java:841)
Caused by: java.lang.IllegalArgumentException: Value [64] not in range [0,32]
	at org.apache.commons.net.util.SubnetUtils.rangeCheck(SubnetUtils.java:339)
	at org.apache.commons.net.util.SubnetUtils.calculate(SubnetUtils.java:264)
	at org.apache.commons.net.util.SubnetUtils.<init>(SubnetUtils.java:51)
	at org.fdroid.fdroid.net.WifiStateChangeService.setIpInfoFromNetworkInterface(WifiStateChangeService.java:222)
	at org.fdroid.fdroid.net.WifiStateChangeService.access$300(WifiStateChangeService.java:37)
	at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:99)
	at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:71)
	at android.os.AsyncTask$2.call(AsyncTask.java:288)
	at java.util.concurrent.FutureTask.run(FutureTask.java:237)
	... 4 more
java.lang.IllegalArgumentException: Value [64] not in range [0,32]
	at org.apache.commons.net.util.SubnetUtils.rangeCheck(SubnetUtils.java:339)
	at org.apache.commons.net.util.SubnetUtils.calculate(SubnetUtils.java:264)
	at org.apache.commons.net.util.SubnetUtils.<init>(SubnetUtils.java:51)
	at org.fdroid.fdroid.net.WifiStateChangeService.setIpInfoFromNetworkInterface(WifiStateChangeService.java:222)
	at org.fdroid.fdroid.net.WifiStateChangeService.access$300(WifiStateChangeService.java:37)
	at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:99)
	at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:71)
	at android.os.AsyncTask$2.call(AsyncTask.java:288)
	at java.util.concurrent.FutureTask.run(FutureTask.java:237)
	at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
	at java.lang.Thread.run(Thread.java:841)
2016-05-24 22:47:17 +02:00
Hans-Christoph Steiner
550a107c9c fix crash when InstallManager is killed while actively downloading
This is very related to #660 but this time, I can't see any other way to
solve it but a null guard.  I don't think it is possible to guarantee that
the Downloader.ACTION_INTERRUPTED receiver will be unregistered since
onDestroy() might not even be called.

java.lang.NullPointerException
	at org.fdroid.fdroid.installer.InstallManagerService.removeFromActive(InstallManagerService.java:328)
	at org.fdroid.fdroid.installer.InstallManagerService.access$400(InstallManagerService.java:58)
	at org.fdroid.fdroid.installer.InstallManagerService$4.onReceive(InstallManagerService.java:212)
	at android.support.v4.content.LocalBroadcastManager.executePendingBroadcasts(LocalBroadcastManager.java:297)
	at android.support.v4.content.LocalBroadcastManager.access$000(LocalBroadcastManager.java:46)
	at android.support.v4.content.LocalBroadcastManager$1.handleMessage(LocalBroadcastManager.java:116)
	at android.os.Handler.dispatchMessage(Handler.java:110)
	at android.os.Looper.loop(Looper.java:193)
	at android.app.ActivityThread.main(ActivityThread.java:5353)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:830)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:646)
	at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
	at dalvik.system.NativeStart.main(Native Method)
2016-05-24 22:47:17 +02:00
Hans-Christoph Steiner
ad498faf92 fix crash with Bluetooth on android-10
android.bluetooth.BluetoothSocket.isConnected() is only 14+

java.lang.NoSuchMethodError: android.bluetooth.BluetoothSocket.isConnected
  at org.fdroid.fdroid.net.bluetooth.BluetoothConnection.open(BluetoothConnection.java:37)
  at org.fdroid.fdroid.net.bluetooth.BluetoothClient.openConnection(BluetoothClient.java:31)
  at org.fdroid.fdroid.net.BluetoothDownloader.<init>(BluetoothDownloader.java:30)
  at org.fdroid.fdroid.net.DownloaderFactory.create(DownloaderFactory.java:56)
  at org.fdroid.fdroid.RepoUpdater.downloadIndex(RepoUpdater.java:97)
  at org.fdroid.fdroid.RepoUpdater.update(RepoUpdater.java:131)
  at org.fdroid.fdroid.UpdateService.onHandleIntent(UpdateService.java:377)
  at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:59)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:130)
  at android.os.HandlerThread.run(HandlerThread.java:60)
2016-05-24 22:47:16 +02:00
Daniel Martí
f5fcde9867 Bump to 0.100-alpha8 2016-05-23 17:38:32 +01:00
Daniel Martí
57fee437ba Merge branch 'stable-v0.100' into 'stable-v0.100'
more stable-v0.100 fixes

a swap bug fix and the translations update from master.

See merge request !308
2016-05-23 16:23:56 +00:00
F-Droid Translatebot
41d1278579 Pull translation updates from Weblate
Translators:

Ajeje Brazorf         Sardinian
Allan Nordhøy         Norwegian Bokmål
Enol P                Asturian
ezjerry liao          Traditional Chinese
Olexandr Nesterenko   Ukrainian
Tijmen Ennik          Dutch
2016-05-23 18:13:52 +02:00
Hans-Christoph Steiner
c941440a34 when parsing APKs for the local repo, correctly set maxSdkVersion
The original logic had maxSdkVersion=0 meaning infinity. That was changed
to be a very large value SDK_VERSION_MAX_VALUE, but getMinMaxSdkVersion()
was still returning 0 for APKs where maxSdkVersion was not set.

This is a follow up on fc0df0dcf4dd0d5f13de82d7cd9254b2b48cb62d
2016-05-23 16:15:52 +02:00
Hans-Christoph Steiner
8322fd046c remove unused import from b3f79da341dce582a8a45126fa221ec494c7f3e1 2016-05-23 11:06:55 +02:00
Hans-Christoph Steiner
4f514deca5 fix issue where first time installs do not work
New installs where being caught up in the logic to check whether a download
is still in progress after InstallManagerService got killed. Also checking
whether Intent was just redelivered lets the new installs through while
screening out the inactive Intents that were redelivered.  This logic also
cancels the notification for any download that was in progress when the
InstallManagerService was killed.  This was introduced in 5f3dde4060f5d472c

#660
2016-05-23 10:49:05 +02:00
Hans-Christoph Steiner
cd9ad9cdbf prevent divide-by-zero errors when showing update download progress 2016-05-23 09:04:52 +02:00
Hans-Christoph Steiner
8eda7d0273 delete the APK copy that Installer instances make
Installer instances always copy the APK to a safe place to run the install
from.  That copy needs to be deleted.  Until we have the whole lifecycle in
InstallManagerService, we need this hack. It should be handled on the
broadcast from InstallerService to say that its complete.

#611 !300
2016-05-23 09:04:33 +02:00
Hans-Christoph Steiner
5f3dde4060 include all needed data in install Intents
Including the App and Apk instances in the Intent that starts
InstallManagerService ensures that the needed data is present in the
Service no matter what happens outside of the Service.  For example, if the
index is updated or cleared while an install is in progress, the install
process still needs to know the name and packageName of the app to update
the Notification.

A cleaner but more labor-intensive way to implement this would be to make
App and Apk properly implement the full Parcelable interface.  That would
require tests to check that the Parcelable methods have all the same fields
as toContentValues() and the database.

closes #660 https://gitlab.com/fdroid/fdroidclient/issues/660
2016-05-23 09:04:14 +02:00
Peter Serwylo
b3f79da341 Slow down progress events to ensure notification action button can be pressed.
After much consternation and testing, it became apparant that there
was nothing really wrong with our PendingIntent setup or notification
setup. The only problem was the rapidness with which the notification
was being updated. There is something about rapidly updated notificaitons
which makes it not possible to hit the action button. This explains why
the button _would indeed work sometimes_, because the user may have hit
it just in that sweet spot.

This change increases the time between progress events from 100ms to 500ms
which seems to do the job on my Moto X 2nd gen. Perhaps this can be changed
to a larger number if required. When it was set to 300 ms, it _mostly_ worked,
but there was still a few times where hitting the button wouldn't work at all.

Fixes #652.
2016-05-23 00:13:16 +10:00
Hans-Christoph Steiner
f1b09a5c43 safely handle nulls that start InstallManagerService
For some odd reason, something is sending a URL to be downloaded that then
results in a null Apk instance.  My first guess was because it was being
canceled, but the interrupted receiver is not even registered yet. My
second thought is that something is sending a download and cancel Intent at
the same time.  In any case, its something to keep in mind when reworking
InstallManagerService once InstallerService comes along.

#660 https://gitlab.com/fdroid/fdroidclient/issues/660
2016-05-19 23:49:31 +02:00
Hans-Christoph Steiner
3dfbadc24d always set App.icon when instantiating from installed app
The App(Context context, PackageManager pm, String packageName) constructor
was not setting App.icon, which is required for lots of things.  This makes
it always get set, since its just a standard file name, and it does not
have to even exist yet.
2016-05-19 13:05:31 +02:00
Hans-Christoph Steiner
3fedbdaff3 only generate basic swap index.jar if none exists
Let's keep the index.jar around as a cache of parsed information.

LocalRepoManager.getApps() was totally unused
2016-05-19 13:05:28 +02:00
Hans-Christoph Steiner
5084982ce2 fixes #633 update download progress regression
When reworking this in 7f10be18c6dd0b69e2fdbae98d09b197e60af443, I confused
the "Processing" with the "Downloading", probably because I thought those
steps were combined, but they are not. Also, I forgot that Downloader
instances do not broadcast status. So its just a matter of setting up the
right ProgressListeners.

https://gitlab.com/fdroid/fdroidclient/issues/633
2016-05-19 13:05:19 +02:00
Hans-Christoph Steiner
ba42d3a507 parse APK for <nativecode> info in local repos
This parses the APKs for swapping, looking for what kinds of native code it
includes.  This is used in the compatibility check.

closes #30 https://gitlab.com/fdroid/fdroidclient/issues/30
2016-05-19 13:05:13 +02:00
Hans-Christoph Steiner
db9bdc315d only update static WiFi settings var from WifiInfoThread
Since Intents can come in any time, whether WifiInfoThread is running or
not, the global static vars for storing the WiFi settings info should only
be updated from the WifiInfoThread.  Otherwise, the WiFi settings could be
nulled out between the time of the null guard and the execution in code
like this:

if (!TextUtils.isEmpty(FDroidApp.ipAddressString) && netmask != null) {
  FDroidApp.subnetInfo = new SubnetUtils(FDroidApp.ipAddressString, netmask).getInfo();

fixes #589 https://gitlab.com/fdroid/fdroidclient/issues/589

java.lang.RuntimeException: An error occured while executing doInBackground()
        at android.os.AsyncTask$3.done(AsyncTask.java:304)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
        at java.util.concurrent.FutureTask.run(FutureTask.java:242)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.IllegalArgumentException: Could not parse [null/24]
        at org.apache.commons.net.util.SubnetUtils.calculate(SubnetUtils.java:275)
        at org.apache.commons.net.util.SubnetUtils.<init>(SubnetUtils.java:62)
        at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:89)
        at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:70)
        at android.os.AsyncTask$2.call(AsyncTask.java:292)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        ... 4 more
java.lang.IllegalArgumentException: Could not parse [null/24]
        at org.apache.commons.net.util.SubnetUtils.calculate(SubnetUtils.java:275)
        at org.apache.commons.net.util.SubnetUtils.<init>(SubnetUtils.java:62)
        at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:89)
        at org.fdroid.fdroid.net.WifiStateChangeService$WaitForWifiAsyncTask.doInBackground(WifiStateChangeService.java:70)
        at android.os.AsyncTask$2.call(AsyncTask.java:292)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
        at java.lang.Thread.run(Thread.java:818)
2016-05-19 13:05:07 +02:00
Hans-Christoph Steiner
5f1aee8f0d fix download progress when installing apps via swap
fixes issue that came from all the #601 !278 changes
2016-05-19 13:04:22 +02:00
Peter Serwylo
388dbbb2de Correctly expand list of nearby people to the entire height of the screen.
If the device is small, then the "Conenct and trade apps with people near you"
header takes up too much space and we end up not being able to see any nearby
people at all, even if they are in the list. As such, this also removes that
header for "small" and "ldpi" devices. During testing I found that "small" was
not enough, because a 240x400 screen is considered "medium" and there is not
enough space. ldpi seems to be a reasonable metric for "that header is going to
be taking valuable space and should not be shown then".

All larger devices retain the header and seem to look nice.

This also pushes the "Can't find what you're looking for?" message and associated
buttons right to the bottom of the screen. This is more in line with the original
design.

Fixes #604.
!291 https://gitlab.com/fdroid/fdroidclient/merge_requests/291
2016-05-17 13:38:29 +02:00
Daniel Martí
c3ae8008ba Merge branch 'revert-cursor-stuff-for-stable' into 'stable-v0.100'
Revert "Fixed Cursor initialization deprecation"

**NOTE: This has a sister MR which is targeting the `master` and thus should *not* be cherry-picked across to that branch.**

This reverts commit df9954ba0b927183d8fa4765e51f5b0033ff6168.

See discussion in #606 for why this was done.

See merge request !295
2016-05-16 23:02:06 +00:00
Peter Serwylo
59a533234e Revert "Fixed Cursor initialization deprecation"
This reverts commit df9954ba0b927183d8fa4765e51f5b0033ff6168.

The revert is to fix #606 for the 0.100 stable release.
2016-05-16 23:45:17 +10:00
41 changed files with 634 additions and 382 deletions

View File

@ -1,4 +1,4 @@
### 0.100 (2016-05-??)
### 0.100 (2016-06-07)
* Ability to download apps in the background

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.fdroid.fdroid"
android:installLocation="auto"
android:versionCode="100007"
android:versionName="0.100-alpha7"
android:versionCode="100050"
android:versionName="0.100"
>
<uses-sdk

View File

@ -7,7 +7,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
import org.apache.commons.io.FileUtils;
@ -20,7 +19,6 @@ import java.io.File;
* {@link FDroidApp#onCreate()}
*/
public class CleanCacheService extends IntentService {
private static final String TAG = "CleanCacheService";
/**
* 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) {
interval = keepTime * 1000;
}
Log.i(TAG, "schedule " + keepTime + " " + interval);
Intent intent = new Intent(context, CleanCacheService.class);
PendingIntent pending = PendingIntent.getService(context, 0, intent, 0);
@ -53,6 +50,29 @@ public class CleanCacheService extends IntentService {
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
Utils.clearOldFiles(Utils.getApkCacheDir(this), Preferences.get().getKeepCacheTime());
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);
}
}
}
/**

View File

@ -139,7 +139,9 @@ public class FDroidApp extends Application {
}
/**
* Initialize the settings needed to run a local swap repo.
* Initialize the settings needed to run a local swap repo. This should
* only ever be called in {@link org.fdroid.fdroid.net.WifiStateChangeService.WifiInfoThread},
* after the single init call in {@link FDroidApp#onCreate()}.
*/
public static void initWifiSettings() {
port = 8888;

View File

@ -57,7 +57,9 @@ public class RepoUpdater {
@NonNull
private final Repo repo;
private boolean hasChanged;
@Nullable
private ProgressListener downloadProgressListener;
private ProgressListener committingProgressListener;
private ProgressListener processXmlProgressListener;
private String cacheTag;
@ -83,6 +85,10 @@ public class RepoUpdater {
this.indexUrl = url;
}
public void setDownloadProgressListener(ProgressListener progressListener) {
this.downloadProgressListener = progressListener;
}
public void setProcessXmlProgressListener(ProgressListener progressListener) {
this.processXmlProgressListener = progressListener;
}
@ -100,6 +106,7 @@ public class RepoUpdater {
try {
downloader = DownloaderFactory.create(context, indexUrl);
downloader.setCacheTag(repo.lastetag);
downloader.setListener(downloadProgressListener);
downloader.download();
if (downloader.isCached()) {

View File

@ -125,10 +125,14 @@ public class RepoXMLHandler extends DefaultHandler {
curapk.apkName = str;
break;
case "sdkver":
curapk.minSdkVersion = Utils.parseInt(str, 0);
curapk.minSdkVersion = Utils.parseInt(str, Apk.SDK_VERSION_MIN_VALUE);
break;
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;
case "added":
curapk.added = Utils.parseDate(str, null);

View File

@ -49,8 +49,6 @@ import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.DownloaderService;
import java.net.URL;
import java.util.ArrayList;
@ -166,7 +164,6 @@ public class UpdateService extends IntentService {
public void onDestroy() {
super.onDestroy();
notificationManager.cancel(NOTIFY_ID_UPDATING);
localBroadcastManager.unregisterReceiver(downloadProgressReceiver);
localBroadcastManager.unregisterReceiver(updateStatusReceiver);
}
@ -195,26 +192,6 @@ public class UpdateService extends IntentService {
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
private final BroadcastReceiver downloadProgressReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String repoAddress = intent.getDataString();
int downloadedSize = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, -1);
String downloadedSizeFriendly = Utils.getFriendlySize(downloadedSize);
int totalSize = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, -1);
int percent = (int) ((double) downloadedSize / totalSize * 100);
String message;
if (totalSize == -1) {
message = getString(R.string.status_download_unknown_size, repoAddress, downloadedSizeFriendly);
percent = -1;
} else {
String totalSizeFriendly = Utils.getFriendlySize(totalSize);
message = getString(R.string.status_download, repoAddress, downloadedSizeFriendly, totalSizeFriendly, percent);
}
sendStatus(context, STATUS_INFO, message, percent);
}
};
// For receiving results from the UpdateService when we've told it to
// update in response to a user request.
private final BroadcastReceiver updateStatusReceiver = new BroadcastReceiver() {
@ -337,8 +314,12 @@ public class UpdateService extends IntentService {
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
final long startTime = System.currentTimeMillis();
String address = intent.getStringExtra(EXTRA_ADDRESS);
boolean manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
boolean manualUpdate = false;
String address = null;
if (intent != null) {
address = intent.getStringExtra(EXTRA_ADDRESS);
manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false);
}
try {
// 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));
RepoUpdater updater = new RepoUpdater(getBaseContext(), repo);
localBroadcastManager.registerReceiver(downloadProgressReceiver,
DownloaderService.getIntentFilter(updater.indexUrl, Downloader.ACTION_PROGRESS));
updater.setProcessXmlProgressListener(processXmlProgressListener);
updater.setCommittingProgressListener(committingProgressListener);
setProgressListeners(updater);
try {
updater.update();
if (updater.hasChanged()) {
@ -392,7 +370,6 @@ public class UpdateService extends IntentService {
repoErrors.add(e.getMessage());
Log.e(TAG, "Error updating repository " + repo.address, e);
}
localBroadcastManager.unregisterReceiver(downloadProgressReceiver);
// now that downloading the index is done, start downloading updates
if (changes && fdroidPrefs.isAutoDownloadEnabled()) {
@ -528,25 +505,53 @@ public class UpdateService extends IntentService {
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
}
private final ProgressListener processXmlProgressListener = new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
String downloadedSize = Utils.getFriendlySize(bytesRead);
String totalSize = Utils.getFriendlySize(totalBytes);
int percent = -1;
if (totalBytes > 0) {
percent = (int) ((double) bytesRead / totalBytes * 100);
/**
* Set up the various {@link ProgressListener}s needed to get feedback to the UI.
* Note: {@code ProgressListener}s do not need to be unregistered, they can just
* be set again for each download.
*/
private void setProgressListeners(RepoUpdater updater) {
updater.setDownloadProgressListener(new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
Log.i(TAG, "downloadProgressReceiver " + sourceUrl);
String downloadedSizeFriendly = Utils.getFriendlySize(bytesRead);
int percent = -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);
}
String message = getString(R.string.status_processing_xml_percent, sourceUrl, downloadedSize, totalSize, percent);
sendStatus(getApplicationContext(), STATUS_INFO, message, percent);
}
};
});
private final ProgressListener committingProgressListener = new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
String message = getString(R.string.status_inserting_apps);
sendStatus(getApplicationContext(), STATUS_INFO, message);
}
};
updater.setProcessXmlProgressListener(new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
String downloadedSize = Utils.getFriendlySize(bytesRead);
String totalSize = Utils.getFriendlySize(totalBytes);
int percent = -1;
if (totalBytes > 0) {
percent = (int) ((double) bytesRead / totalBytes * 100);
}
String message = getString(R.string.status_processing_xml_percent, sourceUrl, downloadedSize, totalSize, percent);
sendStatus(getApplicationContext(), STATUS_INFO, message, percent);
}
});
updater.setCommittingProgressListener(new ProgressListener() {
@Override
public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
String message = getString(R.string.status_inserting_apps);
sendStatus(getApplicationContext(), STATUS_INFO, message);
}
});
}
}

View File

@ -39,6 +39,7 @@ import com.nostra13.universalimageloader.utils.StorageUtils;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.compat.FileCompat;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.SanitizedFile;
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
* to parse it */
private static int getMinMaxSdkVersion(Context context, String packageName,
String attrName) {
private static int getSdkVersion(Context context, String packageName,
String attrName, final int defaultValue) {
try {
AssetManager am = context.createPackageContext(packageName, 0).getAssets();
XmlResourceParser xml = am.openXmlResourceParser("AndroidManifest.xml");
@ -245,15 +246,15 @@ public final class Utils {
} catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
Log.e(TAG, "Could not get min/max sdk version", e);
}
return 0;
return defaultValue;
}
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) {
return getMinMaxSdkVersion(context, packageName, "maxSdkVersion");
return getSdkVersion(context, packageName, "maxSdkVersion", Apk.SDK_VERSION_MAX_VALUE);
}
// return a fingerprint formatted for display

View File

@ -4,6 +4,7 @@ import android.annotation.TargetApi;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Build;
import android.os.Parcelable;
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
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 versionName;
@ -21,7 +23,7 @@ public class Apk extends ValueObject implements Comparable<Apk> {
public long repo; // ID of the repo it comes from
public String hash;
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 Date added;
public Utils.CommaSeparatedList permissions; // null if empty or
@ -49,7 +51,12 @@ public class Apk extends ValueObject implements Comparable<Apk> {
public String repoAddress;
public Utils.CommaSeparatedList incompatibleReasons;
public Apk() { }
public Apk() {
}
public Apk(Parcelable parcelable) {
this(new ContentValuesCursor((ContentValues) parcelable));
}
public Apk(Cursor cursor) {

View File

@ -8,6 +8,7 @@ import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
@ -21,8 +22,12 @@ import java.io.InputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class App extends ValueObject implements Comparable<App> {
@ -108,12 +113,21 @@ public class App extends ValueObject implements Comparable<App> {
public boolean uninstallable;
public static String getIconName(String packageName, int versionCode) {
return packageName + "_" + versionCode + ".png";
}
@Override
public int compareTo(App app) {
return name.compareToIgnoreCase(app.name);
}
public App() { }
public App() {
}
public App(Parcelable parcelable) {
this(new ContentValuesCursor((ContentValues) parcelable));
}
public App(Cursor cursor) {
@ -273,21 +287,35 @@ public class App extends ValueObject implements Comparable<App> {
+ ", last updated on " + this.lastUpdated + ")</p>";
this.name = (String) appInfo.loadLabel(pm);
this.icon = getIconName(packageName, packageInfo.versionCode);
final SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir);
final Apk apk = new Apk();
apk.versionName = packageInfo.versionName;
apk.versionCode = packageInfo.versionCode;
apk.hashType = "sha256";
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
apk.added = this.added;
apk.minSdkVersion = Utils.getMinSdkVersion(context, packageName);
apk.maxSdkVersion = Utils.getMaxSdkVersion(context, packageName);
apk.packageName = this.packageName;
apk.installedFile = apkFile;
apk.permissions = Utils.CommaSeparatedList.make(packageInfo.requestedPermissions);
apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk";
final SanitizedFile apkFile = SanitizedFile.knownSanitized(appInfo.publicSourceDir);
apk.hashType = "sha256";
apk.hash = Utils.getBinaryHash(apkFile, apk.hashType);
apk.installedFile = apkFile;
JarFile jarFile = new JarFile(apkFile);
HashSet<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;
if (features != null && features.length > 0) {
final String[] featureNames = new String[features.length];

View File

@ -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;
}
}

View File

@ -15,7 +15,6 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.AppDetails;
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,
* downloading it, putting up and maintaining a {@link Notification}, and more.
* <p>
* Data is sent via {@link Intent}s so that Android handles the message queuing
* and {@link Service} lifecycle for us, although it adds one layer of redirection
* between the static method to send the {@code Intent} and the method to
* actually process it.
* The {@link App} and {@link Apk} instances are sent via
* {@link Intent#putExtra(String, android.os.Bundle)}
* so that Android handles the message queuing and {@link Service} lifecycle for us.
* 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>
* 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
@ -59,7 +60,10 @@ import java.util.Set;
public class InstallManagerService extends Service {
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,
@ -135,12 +139,31 @@ public class InstallManagerService extends Service {
Utils.debugLog(TAG, "onStartCommand " + intent);
if (!ACTION_INSTALL.equals(intent.getAction())) {
Log.i(TAG, "Ignoring " + intent + " as it is not an " + ACTION_INSTALL + " intent");
Utils.debugLog(TAG, "Ignoring " + intent + " as it is not an " + ACTION_INSTALL + " intent");
return START_NOT_STICKY;
}
String urlString = intent.getDataString();
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();
notificationManager.notify(urlString.hashCode(), notification);
@ -331,9 +354,18 @@ public class InstallManagerService extends Service {
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) {
Apk apk = ACTIVE_APKS.remove(urlString);
ACTIVE_APPS.remove(apk.packageName);
if (apk != null) {
ACTIVE_APPS.remove(apk.packageName);
}
return apk;
}
@ -346,10 +378,11 @@ public class InstallManagerService extends Service {
public static void queue(Context context, App app, Apk apk) {
String urlString = apk.getUrl();
Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString);
addToActive(urlString, app, apk);
Intent intent = new Intent(context, InstallManagerService.class);
intent.setAction(ACTION_INSTALL);
intent.setData(Uri.parse(urlString));
intent.putExtra(EXTRA_APP, app.toContentValues());
intent.putExtra(EXTRA_APK, apk.toContentValues());
context.startService(intent);
}

View File

@ -170,7 +170,7 @@ public abstract class Installer {
*/
public void installPackage(File apkFile, String packageName, String urlString)
throws InstallFailedException {
SanitizedFile apkToInstall;
SanitizedFile apkToInstall = null;
try {
Map<String, Object> attributes = AndroidXMLDecompress.getManifestHeaderAttributes(apkFile.getAbsolutePath());
@ -232,6 +232,22 @@ public abstract class Installer {
throw new InstallFailedException(e);
} catch (ClassCastException e) {
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();
}
}

View File

@ -3,7 +3,6 @@ package org.fdroid.fdroid.localrepo;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
@ -24,6 +23,7 @@ import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.SanitizedFile;
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() {
deleteContents(repoDir);
}
@ -267,8 +274,6 @@ public final class LocalRepoManager {
if (!app.isValid()) {
return;
}
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
app.icon = getIconFile(packageName, packageInfo.versionCode).getName();
} catch (PackageManager.NameNotFoundException | CertificateEncodingException | IOException e) {
Log.e(TAG, "Error adding app to local repo", e);
return;
@ -277,10 +282,6 @@ public final class LocalRepoManager {
apps.put(packageName, app);
}
public List<String> getApps() {
return new ArrayList<>(apps.keySet());
}
public void copyIconsToRepo() {
ApplicationInfo appInfo;
for (final App app : apps.values()) {
@ -321,7 +322,7 @@ public final class LocalRepoManager {
}
private File getIconFile(String packageName, int versionCode) {
return new File(iconsDir, packageName + "_" + versionCode + ".png");
return new File(iconsDir, App.getIconName(packageName, versionCode));
}
/**
@ -451,11 +452,16 @@ public final class LocalRepoManager {
tagHash(app);
tag("sig", app.installedApk.sig.toLowerCase(Locale.US));
tag("size", app.installedApk.installedFile.length());
tag("sdkver", app.installedApk.minSdkVersion);
tag("maxsdkver", app.installedApk.maxSdkVersion);
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);
tagPermissions(app);
tagNativecode(app);
serializer.endTag("", "package");
}
@ -485,6 +491,14 @@ public final class LocalRepoManager {
serializer.endTag("", "features");
}
private void tagNativecode(App app) throws IOException {
if (app.installedApk.nativecode != null) {
serializer.startTag("", "nativecode");
serializer.text(Utils.CommaSeparatedList.str(app.installedApk.nativecode));
serializer.endTag("", "nativecode");
}
}
private void tagHash(App app) throws IOException {
serializer.startTag("", "hash");
serializer.attribute("", "type", app.installedApk.hashType);

View File

@ -145,7 +145,7 @@ public abstract class Downloader {
totalBytes = totalDownloadSize();
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
// it is implemented, so we may as well check this before we proceed.

View File

@ -30,7 +30,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.PatternMatcher;
import android.os.Process;
import android.support.v4.content.IntentCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
@ -147,8 +146,7 @@ public class DownloaderService extends Service {
public static PendingIntent getCancelPendingIntent(Context context, String urlString) {
Intent cancelIntent = new Intent(context.getApplicationContext(), DownloaderService.class)
.setData(Uri.parse(urlString))
.setAction(ACTION_CANCEL)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
.setAction(ACTION_CANCEL);
return PendingIntent.getService(context.getApplicationContext(),
urlString.hashCode(),
cancelIntent,

View File

@ -36,6 +36,12 @@ import java.util.Locale;
* which is how it can be triggered by code, or it came in from the system
* via {@link org.fdroid.fdroid.receiver.WifiStateChangeReceiver}, in
* 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 {
private static final String TAG = "WifiStateChangeService";
@ -52,8 +58,11 @@ public class WifiStateChangeService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
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.");
FDroidApp.initWifiSettings();
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
int wifiState = wifiManager.getWifiState();
@ -79,6 +88,7 @@ public class WifiStateChangeService extends IntentService {
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
try {
FDroidApp.initWifiSettings();
Utils.debugLog(TAG, "Checking wifi state (in background thread).");
WifiInfo wifiInfo = null;
@ -185,7 +195,11 @@ public class WifiStateChangeService extends IntentService {
@TargetApi(9)
private void setIpInfoFromNetworkInterface() {
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();
for (Enumeration<InetAddress> inetAddresses = netIf.getInetAddresses(); inetAddresses.hasMoreElements();) {
@ -202,9 +216,15 @@ public class WifiStateChangeService extends IntentService {
}
// the following methods were not added until android-9/Gingerbread
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)) {
String cidr = String.format(Locale.ENGLISH, "%s/%d",
FDroidApp.ipAddressString, address.getNetworkPrefixLength());
FDroidApp.ipAddressString, networkPrefixLength);
FDroidApp.subnetInfo = new SubnetUtils(cidr).getInfo();
break;
}

View File

@ -34,7 +34,7 @@ public class BluetoothConnection {
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
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,
// client sockets require us to call connect().
socket.connect();

View File

@ -22,7 +22,6 @@ public abstract class AppListAdapter extends CursorAdapter {
private DisplayImageOptions displayImageOptions;
private String upgradeFromTo;
@SuppressWarnings("deprecation")
public AppListAdapter(Context context, Cursor c) {
super(context, c);
init(context);

View File

@ -2,18 +2,10 @@ package org.fdroid.fdroid.views;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
public class AvailableAppListAdapter extends AppListAdapter {
public static AvailableAppListAdapter create(Context context, Cursor cursor, int flags) {
if (Build.VERSION.SDK_INT >= 11) {
return new AvailableAppListAdapter(context, cursor, flags);
}
return new AvailableAppListAdapter(context, cursor);
}
private AvailableAppListAdapter(Context context, Cursor c) {
public AvailableAppListAdapter(Context context, Cursor c) {
super(context, c);
}
@ -21,7 +13,7 @@ public class AvailableAppListAdapter extends AppListAdapter {
super(context, c, autoRequery);
}
private AvailableAppListAdapter(Context context, Cursor c, int flags) {
public AvailableAppListAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}

View File

@ -2,18 +2,10 @@ package org.fdroid.fdroid.views;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
public class CanUpdateAppListAdapter extends AppListAdapter {
public static CanUpdateAppListAdapter create(Context context, Cursor cursor, int flags) {
if (Build.VERSION.SDK_INT >= 11) {
return new CanUpdateAppListAdapter(context, cursor, flags);
}
return new CanUpdateAppListAdapter(context, cursor);
}
private CanUpdateAppListAdapter(Context context, Cursor c) {
public CanUpdateAppListAdapter(Context context, Cursor c) {
super(context, c);
}
@ -21,7 +13,7 @@ public class CanUpdateAppListAdapter extends AppListAdapter {
super(context, c, autoRequery);
}
private CanUpdateAppListAdapter(Context context, Cursor c, int flags) {
public CanUpdateAppListAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}

View File

@ -2,18 +2,10 @@ package org.fdroid.fdroid.views;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
public class InstalledAppListAdapter extends AppListAdapter {
public static InstalledAppListAdapter create(Context context, Cursor cursor, int flags) {
if (Build.VERSION.SDK_INT >= 11) {
return new InstalledAppListAdapter(context, cursor, flags);
}
return new InstalledAppListAdapter(context, cursor);
}
private InstalledAppListAdapter(Context context, Cursor c) {
public InstalledAppListAdapter(Context context, Cursor c) {
super(context, c);
}
@ -21,7 +13,7 @@ public class InstalledAppListAdapter extends AppListAdapter {
super(context, c, autoRequery);
}
private InstalledAppListAdapter(Context context, Cursor c, int flags) {
public InstalledAppListAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}

View File

@ -767,7 +767,7 @@ public class ManageReposActivity extends ActionBarActivity {
setRetainInstance(true);
setHasOptionsMenu(true);
repoAdapter = RepoAdapter.create(getActivity(), null, 0);
repoAdapter = new RepoAdapter(getActivity(), null);
repoAdapter.setEnabledListener(this);
setListAdapter(repoAdapter);
}

View File

@ -2,7 +2,6 @@ package org.fdroid.fdroid.views;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -23,14 +22,7 @@ public class RepoAdapter extends CursorAdapter {
private EnabledListener enabledListener;
public static RepoAdapter create(Context context, Cursor cursor, 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) {
public RepoAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
inflater = LayoutInflater.from(context);
}
@ -40,8 +32,7 @@ public class RepoAdapter extends CursorAdapter {
inflater = LayoutInflater.from(context);
}
@SuppressWarnings("deprecation")
private RepoAdapter(Context context, Cursor c) {
public RepoAdapter(Context context, Cursor c) {
super(context, c);
inflater = LayoutInflater.from(context);
}

View File

@ -117,7 +117,7 @@ public abstract class AppListFragment extends ListFragment implements
super.onResume();
//Starts a new or restarts an existing Loader in this manager
getLoaderManager().restartLoader(0, null, this);
getLoaderManager().initLoader(0, null, this);
}
@Override

View File

@ -59,7 +59,7 @@ public class AvailableAppsFragment extends AppListFragment implements
@Override
protected AppListAdapter getAppListAdapter() {
if (adapter == null) {
final AppListAdapter a = AvailableAppListAdapter.create(getActivity(), null, 0);
final AppListAdapter a = new AvailableAppListAdapter(getActivity(), null);
Preferences.get().registerUpdateHistoryListener(new Preferences.ChangeListener() {
@Override
public void onPreferenceChange() {
@ -205,7 +205,6 @@ public class AvailableAppsFragment extends AppListFragment implements
@Override
public void onResume() {
super.onResume();
/* restore the saved Category Spinner position */
Activity activity = getActivity();
SharedPreferences p = activity.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
@ -221,6 +220,7 @@ public class AvailableAppsFragment extends AppListFragment implements
}
setCurrentCategory(currentCategory);
super.onResume();
}
@Override

View File

@ -15,7 +15,7 @@ public class CanUpdateAppsFragment extends AppListFragment {
@Override
protected AppListAdapter getAppListAdapter() {
return CanUpdateAppListAdapter.create(getActivity(), null, 0);
return new CanUpdateAppListAdapter(getActivity(), null);
}
@Override

View File

@ -15,7 +15,7 @@ public class InstalledAppsFragment extends AppListFragment {
@Override
protected AppListAdapter getAppListAdapter() {
return InstalledAppListAdapter.create(getActivity(), null, 0);
return new InstalledAppListAdapter(getActivity(), null);
}
@Override

View File

@ -26,7 +26,7 @@ import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp;
@ -42,7 +42,7 @@ import cc.mvdan.accesspoint.WifiApControl;
import rx.Subscriber;
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";

View File

@ -245,6 +245,9 @@ public class SwapAppsView extends ListView implements
private final BroadcastReceiver downloadProgressReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (progressView.getVisibility() != View.VISIBLE) {
showProgress();
}
int read = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
int total = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0);
if (total > 0) {

View File

@ -376,7 +376,10 @@ public class SwapWorkflowActivity extends AppCompatActivity {
getService().swapWith(null);
if (!getService().isEnabled()) {
prepareInitialRepo();
if (!LocalRepoManager.get(this).getIndexJar().exists()) {
Utils.debugLog(TAG, "Preparing initial repo with only F-Droid, until we have allowed the user to configure their own repo.");
new PrepareInitialSwapRepo().execute();
}
}
inflateInnerView(R.layout.swap_blank);
@ -452,16 +455,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
}
private void prepareInitialRepo() {
// TODO: Make it so that this and updateSwappableAppsTask (the _real_ swap repo task)
// don't stomp on eachothers toes. The other one should wait for this to finish, or cancel
// this, but this should never take precedence over the other.
// TODO: Also don't allow this to run multiple times (e.g. if a user keeps navigating back
// to the main screen.
Utils.debugLog(TAG, "Preparing initial repo with only F-Droid, until we have allowed the user to configure their own repo.");
new PrepareInitialSwapRepo().execute();
}
/**
* Once the UpdateAsyncTask has finished preparing our repository index, we can
* show the next screen to the user. This will be one of two things:

View 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>

View 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>

View 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>

View File

@ -9,232 +9,202 @@
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".views.swap.SwapWorkflowActivity">
<!-- Misc header -->
<include layout="@layout/start_swap_header" />
<!-- Bluetooth swap status + toggle -->
<LinearLayout
android:id="@+id/bluetooth_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_below="@id/header"
android:padding="10dp"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="130dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:tint="@color/swap_grey_icon"
android:src="@drawable/ic_bluetooth_white" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/swap_start_header"/>
<!--
Removed for now, because there is no UI to hook this up to.
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"
/>-->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingStart="15dp"
android:layout_weight="1.00"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/bluetooth_visible"
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>
<LinearLayout
android:id="@+id/bluetooth_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:tint="@color/swap_grey_icon"
android:src="@drawable/ic_bluetooth_white" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingStart="15dp"
android:layout_weight="1.00"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/bluetooth_visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/swap_visible_bluetooth"
android:textSize="18sp" />
<TextView
android:id="@+id/device_id_bluetooth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="SP-120"
android:textColor="@color/swap_light_text" />
</LinearLayout>
<android.support.v7.widget.SwitchCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:checked="true"
android:id="@+id/switch_bluetooth" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:tint="@color/swap_grey_icon"
android:src="@drawable/ic_network_wifi_white" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingStart="15dp"
android:layout_weight="1.00"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/wifi_visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/swap_not_visible_wifi"
android:textSize="18sp" />
<TextView
android:id="@+id/device_id_wifi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/swap_wifi_device_name"
android:textColor="@color/swap_light_text" />
<TextView
android:id="@+id/wifi_network"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="wifi network name"
android:textColor="@color/swap_bright_blue"
android:textSize="16sp" />
</LinearLayout>
<android.support.v7.widget.SwitchCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:checked="false"
android:id="@+id/switch_wifi" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingBottom="5dp"
android:paddingTop="20dp">
tools:text="@string/swap_visible_bluetooth"
android:textSize="18sp" />
<TextView
android:id="@+id/text_people_nearby"
android:layout_width="0dp"
android:id="@+id/device_id_bluetooth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/swap_people_nearby"
android:textColor="@color/swap_light_text"
android:layout_weight="1.00"/>
<ProgressBar
android:id="@+id/searching_people_nearby"
android:layout_width="24dp"
android:layout_height="24dp"
android:indeterminate="true" />
tools:text="SP-120"
android:textColor="@color/swap_light_text" />
</LinearLayout>
<ListView
android:id="@+id/list_people_nearby"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
<TextView
<android.support.v7.widget.SwitchCompat
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" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btn_send_fdroid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_fdroid_grey"
android:drawableStart="@drawable/ic_fdroid_grey"
android:text="@string/swap_send_fdroid"
android:drawablePadding="10dp"
android:paddingLeft="25dp"
android:paddingRight="25dp"
android:paddingEnd="25dp"
android:background="@android:color/transparent" />
<Button
android:id="@+id/btn_qr_scanner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_qr_grey"
android:drawableStart="@drawable/ic_qr_grey"
android:text="@string/swap_scan_qr"
android:drawablePadding="10dp"
android:paddingLeft="25dp"
android:paddingRight="25dp"
android:paddingEnd="25dp"
android:background="@android:color/transparent" />
</LinearLayout>
tools:checked="true"
android:id="@+id/switch_bluetooth" />
</LinearLayout>
<!-- WiFi swap status + toggle -->
<LinearLayout
android:id="@+id/wifi_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/bluetooth_info"
android:padding="10dp"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:tint="@color/swap_grey_icon"
android:src="@drawable/ic_network_wifi_white" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingStart="15dp"
android:layout_weight="1.00"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/wifi_visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/swap_not_visible_wifi"
android:textSize="18sp" />
<TextView
android:id="@+id/device_id_wifi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/swap_wifi_device_name"
android:textColor="@color/swap_light_text" />
<TextView
android:id="@+id/wifi_network"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="wifi network name"
android:textColor="@color/swap_bright_blue"
android:textSize="16sp" />
</LinearLayout>
<android.support.v7.widget.SwitchCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:checked="false"
android:id="@+id/switch_wifi" />
</LinearLayout>
<!-- Feedback for "searching for nearby people..." -->
<LinearLayout
android:id="@+id/feedback_searching"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/wifi_info"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingBottom="5dp"
android:paddingTop="20dp">
<TextView
android:id="@+id/text_people_nearby"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/swap_people_nearby"
android:textColor="@color/swap_light_text"
android:layout_weight="1.00"/>
<ProgressBar
android:id="@+id/searching_people_nearby"
android:layout_width="24dp"
android:layout_height="24dp"
android:indeterminate="true" />
</LinearLayout>
<!-- Buttons to help the user when they can't find any peers. Shown at bottom of relative layout -->
<LinearLayout
android:id="@+id/cant_find_peers"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<Button
android:id="@+id/btn_send_fdroid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_fdroid_grey"
android:drawableStart="@drawable/ic_fdroid_grey"
android:text="@string/swap_send_fdroid"
android:drawablePadding="10dp"
android:paddingLeft="25dp"
android:paddingRight="25dp"
android:paddingEnd="25dp"
android:background="@android:color/transparent" />
<Button
android:id="@+id/btn_qr_scanner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_qr_grey"
android:drawableStart="@drawable/ic_qr_grey"
android:text="@string/swap_scan_qr"
android:drawablePadding="10dp"
android:paddingLeft="25dp"
android:paddingRight="25dp"
android:paddingEnd="25dp"
android:background="@android:color/transparent" />
</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>

View File

@ -1,8 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<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="installIncompatible">Paez qu\'esti paquete nun ye compatible col to preséu. ¿Quies intentar 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="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 tentar d\'instalalu 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="delete">Desaniciar</string>
<string name="enable_nfc_send">Habilitar unviu per NFC…</string>
@ -135,18 +135,18 @@
<string name="repo_name">Nome</string>
<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>
<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="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.
\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_searching_address">Guetando repositorios F-Droid en\n%1$s</string>
<string name="minsdk_or_later">%s ó posterior</string>
<string name="up_to_maxsdk">Fasta %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="app_icon">Iconu d\'aplicación</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_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_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_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_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\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">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_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_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_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_scan_qr">Escaniar códigu QR</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_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_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="install_confirm">¿Quies instalar esta aplicación? Tendrá accesu a:</string>

View File

@ -6,7 +6,7 @@
<string name="version">Versjon</string>
<string name="delete">Slett</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="other">Andre</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="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>

View File

@ -6,7 +6,7 @@
<string name="version">Versie</string>
<string name="delete">Verwijderen</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="other">Overig</string>
<string name="update_interval">Automatische vernieuwingsinterval</string>

View File

@ -6,7 +6,7 @@
<string name="version">Versione</string>
<string name="delete">Burra</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="other">Àteru</string>
<string name="update_interval">Intervallu agiornamentu automàticu</string>
@ -346,4 +346,13 @@
<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>

View File

@ -2,7 +2,7 @@
<resources>
<string name="SignatureMismatch">Нова версія підписана не тим ключем, що стара. Перш ніж встановити нову версію, самостійно зітріть стару. (Зауважте, що стирання програми призведе до знищення всіх даних цієї програми)</string>
<string name="version">Версія</string>
<string name="cache_downloaded">Зберігати пакунки</string>
<string name="cache_downloaded">Зберігати кешовані пакунки</string>
<string name="updates">Оновлення</string>
<string name="notify">Сповіщення про оновлення</string>
<string name="about_title">Про F-Droid</string>
@ -352,4 +352,15 @@
<string name="update_auto_download_summary">Завантажувати файли оновлення у фоновому режимі</string>
<string name="tap_to_install_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>

View File

@ -322,4 +322,6 @@
<string name="swap_view_available_networks">點擊打開可用的網路</string>
<string name="swap_switch_to_wifi">點擊以切換到一個 Wi-Fi 網路</string>
<string name="swap_confirm_connect">您現在想要從 %1$s 取得應用程式嗎?</string>
<string name="swap_nearby">近距交換</string>
<string name="swap_intro">與您附近的人連結並且交換應用程式。</string>
</resources>