This should be reverted once #698 is fixed. If execution has gotten this
far into InstallManagerService, there should always be App and Apk
instances. That is enforced when Intents are received by this Service.
This was saying that the Privileged Extension is enabled but not properly
configured. This is because the preference logic changed to default to on
unless the user explicitly disabled it. So using the Privileged
Extension based on whether its installed and whether the user has disabled
it.
related to ea0700d406101b7ed6907b1dbd2918dbc214f435
I guess APKs could disappear, or perhaps not be readable.
closes#699
Here's the stacktrace:
java.io.FileNotFoundException:
/system/priv-app/ATT_Ready2Go/ATT_Ready2Go.apk: open failed: ENOENT (No such file or directory)
at org.fdroid.fdroid.Utils.getBinaryHash(Utils.java:405)
at org.fdroid.fdroid.data.InstalledAppProviderService.onHandleIntent(InstalledAppProviderService.java:164)
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)
This totally changed the logic of the "Use Privileged Extension" preference
making it based on whether the Privileged Extension is installed and usable
rather than storing the user's selection. This code now only stores when
the user manually disables using the Privileged Extension even when it is
installed.
closes#729
The root install method that is available via the "Expert" preferences does
not work on newer than android-19. So when that's the case, this hides
that item from the preferences screen entirely.
Move code causing verify error into separate helper class
Fixes#748.
I'm not 100% sure on how the `@TargetApi` and `VerifyError` work
together. However it is something along the lines of:
* Class loader needs `CleanCacheService`.
* At this point, it loads the bytecode for that class and verifies
that it all makes sense.
* The bytecode within the method targeted at API 21 is not understood
by earlier APIs, because the entire `Os` class was introduced in 21.
* By putting it into a different class, that class is only loaded
at runtime on devices with API of 21 or higher.
Previously, `@TargetApi` + the relevant guard condition to check
the build version at runtime suffices to prevent this. However it seems
that if the entire class does not even exist on earlier APIs, then it
is no longer good enough.
See merge request !379
I'm not 100% sure on how the `@TargetApi` and `VerifyError` work
together. However it is something along the lines of:
* Class loader needs `CleanCacheService`.
* At this point, it loads the bytecode for that class and verifies
that it all makes sense.
* The bytecode within the method targeted at API 21 is not understood
by earlier APIs, because the entire `Os` class was introduced in 21.
* By putting it into a different class, that class is only loaded
at runtime on devices with API of 21 or higher.
Previously, `@TargetApi` + the relevant guard condition to check
the build version at runtime suffices to prevent this. However it seems
that if the entire class does not even exist on earlier APIs, then it
is no longer good enough.
At time of writing (and for some time before), fdroidserver has forced
a description of "No description available" for apps which don't have
descriptions at all:
* https://gitlab.com/fdroid/fdroidserver/blob/0.6.0/fdroidserver/metadata.py#L876
However, if the description is not set for whatever reason, it should not
crash the client.
The icon files are downloaded for each version of the app. Over time, old
versions will pile up. This cleans out the ones that have not been used in
over a year.
On < android-21, this will delete icons that were downloaded over a year
ago even if they are still in use because it is only possible to check
mtime, not atime.
If CleanCacheService runs while an APK is being installed, it should not
delete the APK that is in the process of being installed. This does that
by only deleting those files if they are older than an hour. Same goes for
the index files.
#738
It was passing the wrong time value in the recursion, which made for a
really old "olderThan" time. This also then flipped the logic on the
next round through the recursion, causing files to be deleted even if
"Keep Cache Time" was set to "Forever".
closes#719closes#736
Before, CleanCacheService was only scheduled at app start for once a day.
If the user selects a time less than a day, then CleanCacheService should
run more frequently.
closes#719
In android-21, they exposed the formerly internal method for getting stat
structs of files. From that, we can get the last access time, which is a
much better way to determine which files to delete rather than last
modified time.
closes#644
By catching the exception here and returning null, the problem is then
passed on further down the line where it is harder to debug. The hash is
required wherever this method is called, so this should fail immediately.
#699
ApkVerifier Tests
This are some tests for ApkVerifier. More will follow when we merge https://gitlab.com/fdroid/fdroidserver/merge_requests/150 and implement parsing of permissions with min and max sdk versions.
NOTE: This androidTest cannot run as a Robolectric test because the required methods from PackageManger are not included in Robolectric's Android API.
The corresponding exception by robolectric:
```
org.fdroid.fdroid.installer.ApkVerifierTest > testVerifier FAILED
00:31:18.241 [DEBUG] [TestEventLogger] java.lang.NoClassDefFoundError: java/util/jar/StrictJarFile
00:31:18.241 [DEBUG] [TestEventLogger] at java.lang.Class.getDeclaredMethods0(Native Method)
00:31:18.241 [DEBUG] [TestEventLogger] at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
00:31:18.241 [DEBUG] [TestEventLogger] at java.lang.Class.getDeclaredMethod(Class.java:2128)
00:31:18.241 [DEBUG] [TestEventLogger] at org.robolectric.util.ReflectionHelpers.callStaticMethod(ReflectionHelpers.java:224)
00:31:18.241 [DEBUG] [TestEventLogger] at org.robolectric.internal.bytecode.RobolectricInternals.performStaticInitialization(RobolectricInternals.java:54)
00:31:18.241 [DEBUG] [TestEventLogger] at org.robolectric.internal.bytecode.ShadowWrangler.classInitializing(ShadowWrangler.java:119)
00:31:18.241 [DEBUG] [TestEventLogger] at org.robolectric.internal.bytecode.RobolectricInternals.classInitializing(RobolectricInternals.java:18)
00:31:18.241 [DEBUG] [TestEventLogger] at android.content.pm.PackageParser.<clinit>(PackageParser.java)
00:31:18.241 [DEBUG] [TestEventLogger] at android.content.pm.PackageManager.getPackageArchiveInfo(PackageManager.java:3545)
00:31:18.241 [DEBUG] [TestEventLogger] at org.fdroid.fdroid.installer.ApkVerifier.verifyApk(ApkVerifier.java:56)
00:31:18.241 [DEBUG] [TestEventLogger] at org.fdroid.fdroid.installer.ApkVerifierTest.testVerifier(ApkVerifierTest.java:78)
00:31:18.242 [DEBUG] [TestEventLogger] at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
[...]
00:31:18.244 [DEBUG] [TestEventLogger]
00:31:18.244 [DEBUG] [TestEventLogger] Caused by:
00:31:18.245 [DEBUG] [TestEventLogger] java.lang.ClassNotFoundException: java.util.jar.StrictJarFile
00:31:18.245 [DEBUG] [TestEventLogger] at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
00:31:18.245 [DEBUG] [TestEventLogger] at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
00:31:18.245 [DEBUG] [TestEventLogger] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
00:31:18.245 [DEBUG] [TestEventLogger] at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
00:31:18.245 [DEBUG] [TestEventLogger] at org.robolectric.internal.bytecode.InstrumentingClassLoader.loadClass(InstrumentingClassLoader.java:124)
00:31:18.245 [DEBUG] [TestEventLogger] at java.lang.Class.getDeclaredMethods0(Native Method)
00:31:18.245 [DEBUG] [TestEventLogger] at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
00:31:18.245 [DEBUG] [TestEventLogger] at java.lang.Class.getDeclaredMethod(Class.java:2128)
00:31:18.245 [DEBUG] [TestEventLogger] at org.robolectric.util.ReflectionHelpers.callStaticMethod(ReflectionHelpers.java:224)
00:31:18.245 [DEBUG] [TestEventLogger] at org.robolectric.internal.bytecode.RobolectricInternals.performStaticInitialization(RobolectricInternals.java:54)
00:31:18.245 [DEBUG] [TestEventLogger] at org.robolectric.internal.bytecode.ShadowWrangler.classInitializing(ShadowWrangler.java:119)
00:31:18.245 [DEBUG] [TestEventLogger] at org.robolectric.internal.bytecode.RobolectricInternals.classInitializing(RobolectricInternals.java:18)
00:31:18.245 [DEBUG] [TestEventLogger] at android.content.pm.PackageParser.<clinit>(PackageParser.java)
00:31:18.245 [DEBUG] [TestEventLogger] at android.content.pm.PackageManager.$$robo$$getPackageArchiveInfo(PackageManager.java:3545)
00:31:18.245 [DEBUG] [TestEventLogger] at android.content.pm.PackageManager.getPackageArchiveInfo(PackageManager.java)
00:31:18.245 [DEBUG] [TestEventLogger] at org.fdroid.fdroid.installer.ApkVerifier.verifyApk(ApkVerifier.java:56)
00:31:18.246 [DEBUG] [TestEventLogger] at org.fdroid.fdroid.installer.ApkVerifierTest.testVerifier(ApkVerifierTest.java:78)
00:31:18.246 [DEBUG] [TestEventLogger] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[...]
```
See merge request !367
More misc code cleanup around database code
I'm pulling out the final bit of unrelated code from my database refactor branch in the hope of making the final diff easier. This cleans up a few switch statements with only one option, closes some cursors, and removes some dead code. Comments in the commits explain the dead code.
See merge request !374
AS picked up that the statement is always false, so the body of the if is
never executed. This is indeed the case, because the constructor assigns
the object which is being checked for null.
The code only existed so that it could be used in a test. Subsequently,
a further test was written to test this code (used by the first test).
Since none of the code is actually used in the app, it has been removed.
See #511 for details. This is in prepration for having an even more normalized
`fdroid_package` table. That table will be the authoritative reference of what
"packages" are known about in the client. The "app" table (now thought of as "app metadata") will
be specific to each repository which provides different metadata about that app.
The test was using a `findIgnored` method in `AppProvider`, which only
existed for the purpose of testing. The test has been changed to instead
check for apps which would end up in the "can update" list (which is really
where the "ignored" apps are useful).
In the process, realised that using appId as a foreign key is worse than
packageName, because appId can get removed and added again, but it will
be different when the same app is inserted a second time. In order to
maintain the association of which apps have preferences stored against
them, they need to be stored against something with a bit more semantic
meaning. Thus, join onto package name instead.
This is a more concise syntax to say the same thing, and avoids an
OR clause in the where - which is often the cause of slowness in
many queries. Not sure if it was problematic in these cases, however
this COALESCE syntax is still more consise.
With no indexes at all, a join between X and Y tables would require a full
table scan of Y for each row in X. With an index on the relevant field in
Y, it would require an index lookup on the join field in Y for each row in
X, which contains a pointer to the row of interest in Y. This row is then
looked up and the relevant value extracted. By using a covering index (one
which includes all fields required to satisfy the query, with the first field
being the one which is looked up in the join), then once the index has been
searched, there is no need to then go to table Y because all the relevant
data is already in the index.
This offers a marginal performance improvement.