Service crash fixes
3 relatively simple crash fixes. The two related to `WifiStateChangeService` have already be included in `stable-0.100`, 7385d320b42af960be63c9c179e1cbf186c1398a should be cherry-picked into `stable-0.100` after this is merged.
I already have 198ad843c1fabc8cf57ffe85c77230288cd6d7a4 ready in my stable-0.100 branch
See merge request !317
Historically the providers were responsible for notifying about inserts/deletes
for this table. However this is no longer the case with the new service responsible
for throttling the rate with which these notifications occur.
The `parseApp` method was previously accepting an `Intent`, which could
have been anything. Given it was only used once, this now pushed the
creation of that `Intent` into the `parseApp` method, and also reduced the
visibility of the method as it is only used once at time of writing.
This should not be a particularly expensive opperation,. Also, at time of
writing it is only used in a background thread, and only used once in that
thread (i.e. not in a loop or anything like that).
Now that we have RX as a dependency, it can be used as a nice concise way to
achieve certain tasks. Rate limiting is one thing it does well - via the
`debounce` mechanism:
http://reactivex.io/documentation/operators/debounce.html
The semantics of this code is the same as before, limiting content change notifications
to one per second.
Since refactoring the installed app cache stuff, these methods are no longer
required for testing purposes. This is because the tests directly ask the
content provider to insert relevant apps, rather than testing the broadcast
receiving functionality.
caught java.lang.StringIndexOutOfBoundsException:
at java.lang.String.substring(String.java:1651)
at java.lang.String.subSequence(String.java:2040)
at org.fdroid.fdroid.data.App.setFromPackageInfo(App.java:298)
at org.fdroid.fdroid.data.App.<init>(App.java:268)
at org.fdroid.fdroid.localrepo.CacheSwapAppsService.onHandleIntent(CacheSwapAppsService.java:78)
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)
This adds a check of whether the database has the current APK in it, based
on PackageInfo's lastUpdateTime field. This avoids recalculating the hash
of the whole APK, which is quite time and resource intensive.
The APK hash is useful for comparing whether something is exactly the same
file as something else. For example, to compare whether the installed APK
matches something that f-droid.org hosts. The "last update time" is a fast
way to check whether the information is current.
InstallAppProviderService now processes install and delete events one at a
time, where InstalledAppCacheUpdater made a batch of changes which it ran
all at once. This means that InstallAppProviderService will send out a
flood of notifications when first initializing, since it will index every
single installed app and send a notification for each one. This makes the
GUI lock up. This commit puts a rate limit on those notifications if they
start coming fast. They are limited to one per second.
InstalledAppCacheUpdater was a custom Service-like thing with some
threading issues. InstalledAppProviderService is an IntentService that
relies on the built-in queue and threading of the IntentService to make
sure that things are processed nicely in the background and one at a time.
This changes the announcing so that each app added/changed/deleted triggers
a new annoucement. This keeps the UI more updated, and makes the Installed
tab show something as soon as possible, rather than waiting for the all of
the install apps to be processed. This becomes more important as more
stuff is added to InstalledAppProvider, like the hash of the APK.
This also strips down and simplifies the related BroadcastReceivers.
BroadcastReceivers work on the UI thread, so they should do as little work
as possible. PackageManagerReceiver just rebadges the incoming Intent and
sends it off to InstalledAppProviderService for processing.
The SwapService is the central container for all things swap. If anything
at all related to swap is active, then SwapService needs to be running.
That also means that stopping SwapService should stop all things related to
swapping, including any screens or notifications.
fixes#258https://gitlab.com/fdroid/fdroidclient/issues/258
Since it takes a chunk of time to generate and write the app index.jar when
swapping apps, this service starts running in the background immediately
when SwapService starts. It first indexes the installed apps that were not
cached, then caches apps based PACKAGE_ADDED broadcasts. It does not index
system apps, since there are many and they are rarely swapped.
If the install process is interrupted, then InstallManagerService is no
longer managing it. It will make the announcements and set the
notification, then forget about that APK.
InstallManagerService and DownloaderService both use the download URL as
the unique ID to represent a given APK install through the whole lifecycle
of the install and download process. This converts the installer stuff to
use the same semantics. A Uri instance is mostly used there because its
the most useful format, but ultimately, the String, Uri, and int all derive
from the exact same URL. This then removes the local APK URI from use in
the installer broadcasts.
While I normally think reusing terms from Android is the best thing to do,
"originating URI" drives me nuts because it is almost nonsense English.
"Originating" is a verb in the continuous form, meaning that it is an
action that is ongoing. A URI is a static thing, and in this case, a URI
that points to a file that is completely downloaded. I left the term in
place for DefaultInstaller because it wraps PackageManager, which is where
that term originates.
This handles "Use strings instead of Uris in InstallManagerService for
urlString" as listed in #680
IntentServices can get a null Intent if they are restarted after being
killed. So this should be properly handled.
"[The intent] may be null if the service is being restarted after its
process has gone away, and it had previously returned anything except
START_STICKY_COMPATIBILITY."
https://developer.android.com/reference/android/app/IntentService.html#onStartCommand(android.content.Intent,%20int,%20int)
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)
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)
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)
fixes#559
The onConnected callback of ServiceConnection is always
executed on the main looper of the context that is used
to create the service binding. Thus the old code resulted
in a deadlock and then in a timeout of the Thread.wait()
method.
The check for permissions is now called inside install and
uninstall callbacks, where it works asynchronously.