Compare commits

..

No commits in common. "master" and "v0.100-alpha4" have entirely different histories.

2107 changed files with 68030 additions and 103164 deletions

1
.gitattributes vendored
View File

@ -1 +0,0 @@
*.gpg binary

6
.gitignore vendored
View File

@ -16,6 +16,7 @@ build.xml
# Gradle files
.gradle/
build/
gradle.properties
# Local configuration file (sdk path, etc)
local.properties
@ -42,8 +43,3 @@ extern/*/*/libs/
# Tests
junit-report.xml
# Screen dumps from Android Studio/DDMS
captures/
/fdroid/

View File

@ -1,142 +1,71 @@
stages:
- test
- deploy
image: mvdan/fdroid-ci:client-20160413
.base:
image: registry.gitlab.com/fdroid/ci-images-client:latest
before_script:
- export GRADLE_USER_HOME=$PWD/.gradle
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
- alias sdkmanager="sdkmanager --no_https"
- echo y | sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null
# limit RAM usage for all gradle runs
- export maxmem=$(expr $(sed -n 's,^MemAvailable:[^0-9]*\([0-9][0-9]*\)[^0-9]*$,\1,p' /proc/meminfo) / 1024 / 2 / 1024 \* 1024)
- printf "\norg.gradle.jvmargs=-Xmx${maxmem}m -XX:MaxPermSize=${maxmem}m\norg.gradle.daemon=false\norg.gradle.parallel=false\n" >> gradle.properties
after_script:
# this file changes every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
cache:
paths:
- .gradle/wrapper
- .gradle/caches
cache:
paths:
- .gradle/wrapper
- .gradle/caches
.test-template: &test-template
extends: .base
stage: test
artifacts:
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
paths:
- kernel.log
- logcat.txt
- app/core*
- app/*.log
- app/build/reports
- app/build/outputs/*ml
- app/build/outputs/apk
expire_in: 1 week
when: on_failure
after_script:
- echo "Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs"
variables:
AVD_SDK: "17"
SKIN: "QVGA"
# Run the most important first. Then we can decide whether to ignore
# the style tests if the rest of the more meaningful tests pass.
test_lint_pmd_checkstyle:
<<: *test-template
gradle:
script:
- export EXITVALUE=0
- function set_error() { export EXITVALUE=1; printf "\x1b[31mERROR `history|tail -2|head -1|cut -b 6-500`\x1b[0m\n"; }
- ./gradlew assemble
- echo y | android update sdk --no-ui --filter android-$AVD_SDK
- echo y | android update sdk --no-ui --all --filter sys-img-armeabi-v7a-android-$AVD_SDK
- export GRADLE_USER_HOME=$PWD/.gradle
# always report on lint errors to the build log
- sed -i -e 's,textReport .*,textReport true,' app/build.gradle
- ./gradlew testFullDebugUnitTest || set_error
- ./gradlew lint || set_error
- ./gradlew pmd || set_error
- ./gradlew checkstyle || set_error
- ./tools/check-format-strings.py || set_error
- ./tools/check-fastlane-whitespace.py || set_error
- ./tools/remove-unused-and-blank-translations.py || set_error
- echo "These are unused or blank translations that should be removed:"
- git --no-pager diff --ignore-all-space --name-only --exit-code app/src/*/res/values*/strings.xml || set_error
# 'build' means assemble and check
- ./gradlew build || {
for log in app/build/reports/*ests/*/*ml; do
echo "read $log here:"
cat "$log" | curl --silent -F 'clbin=<-' https://clbin.com;
done;
exit 1;
}
# emulators will only start if they have tiny amounts of RAM
- sed -i -e 's,^hw.ramSize=.*,hw.ramSize=384,'
-e 's,^vm.heapSize=.*,vm.heapSize=48,'
-e 's,^hw.gpu.enabled.*,hw.gpu.enabled = false,'
$ANDROID_HOME/platforms/android-$AVD_SDK/skins/$SKIN/hardware.ini
- echo "hw.gpu.enabled = false" >>
$ANDROID_HOME/platforms/android-$AVD_SDK/skins/$SKIN/hardware.ini
- echo no | android --verbose create avd
--force
--name fcl-test
--skin $SKIN
--target android-$AVD_SDK
- emulator -force-32bit -avd fcl-test -no-skin -no-audio -no-window &
- ./tools/wait-for-emulator
- adb shell input keyevent 82
- export EXITVALUE=0
- ADB_INSTALL_TIMEOUT=8 ./gradlew connectedCheck || {
adb -e logcat -d '*:E';
echo "get the full logcat here:";
adb -e logcat -d | curl --silent -F 'clbin=<-' https://clbin.com;
export EXITVALUE=1;
}
- for log in app/build/reports/*ests/*/*ml
app/build/outputs/*results*/connected/*.xml; do
echo "read $log here:";
cat "$log" | curl --silent -F 'clbin=<-' https://clbin.com;
done
- sed -n 's/.*"ctr2">\([0-9]*\)%<.*/Coverage - \1.0% covered\n/p' app/build/reports/coverage/debug/index.html
- exit $EXITVALUE
errorprone:
extends: .base
stage: test
pmd:
script:
- apt-get update
- apt-get install -t stretch-backports openjdk-11-jdk-headless
- update-java-alternatives --set java-1.11.0-openjdk-amd64
- export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64
- cat config/errorprone.gradle >> app/build.gradle
- ./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug
- export GRADLE_USER_HOME=$PWD/.gradle
- ./gradlew pmd
# Run the tests in the emulator. Each step is broken out to run on
# its own since the CI runner can have limited RAM, and the emulator
# can take a while to start.
#
# once these prove stable, the task should be switched to
# connectedCheck to test all the build flavors
.connected-template: &connected-template
extends: .base
checkstyle:
script:
- ./gradlew assembleFullDebug
- export AVD_SDK=`echo $CI_JOB_NAME | awk '{print $2}'`
- export AVD_TAG=`echo $CI_JOB_NAME | awk '{print $3}'`
- export AVD_ARCH=`echo $CI_JOB_NAME | awk '{print $4}'`
- export AVD_PACKAGE="system-images;android-${AVD_SDK};${AVD_TAG};${AVD_ARCH}"
- echo $AVD_PACKAGE
- export GRADLE_USER_HOME=$PWD/.gradle
- ./gradlew checkstyle
- alias sdkmanager
- ls -l ~/.android
- adb start-server
- start-emulator
- wait-for-emulator
- adb devices
- adb shell input keyevent 82 &
- ./gradlew installFullDebug
- adb shell am start -n org.fdroid.fdroid.debug/org.fdroid.fdroid.views.main.MainActivity
- if [ $AVD_SDK -lt 25 ] || ! emulator -accel-check; then
export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest;
fi
- ./gradlew connectedFullDebugAndroidTest $FLAG
no-accel 22 default x86:
<<: *test-template
<<: *connected-template
.kvm-template: &kvm-template
tags:
- fdroid
- kvm
only:
variables:
- $RUN_KVM_JOBS
<<: *test-template
<<: *connected-template
kvm 29 microg x86_64:
<<: *kvm-template
deploy_nightly:
extends: .base
stage: deploy
only:
- master
tools:
script:
- test -z "$DEBUG_KEYSTORE" && exit 0
- sed -i
's,<string name="app_name">.*</string>,<string name="app_name">F-Nightly</string>,'
app/src/main/res/values*/strings.xml
# add this nightly repo as a enabled repo
- sed -i -e '/<\/string-array>/d' -e '/<\/resources>/d' app/src/main/res/values/default_repos.xml
- echo "<item>${CI_PROJECT_PATH}-nightly</item>" >> app/src/main/res/values/default_repos.xml
- echo "<item>${CI_PROJECT_URL}-nightly/raw/master/fdroid/repo</item>" >> app/src/main/res/values/default_repos.xml
- cat config/nightly-repo/repo.xml >> app/src/main/res/values/default_repos.xml
- export DB=`sed -n 's,.*DB_VERSION *= *\([0-9][0-9]*\).*,\1,p' app/src/main/java/org/fdroid/fdroid/data/DBHelper.java`
- export versionCode=`printf '%d%05d' $DB $(date '+%s'| cut -b4-8)`
- sed -i "s,^\(\s*versionCode\) *[0-9].*,\1 $versionCode," app/build.gradle
# build the APKs!
- ./gradlew assembleDebug
- fdroid nightly -v
- cd app
- ./tools/langs-list-check.py
- ./tools/check-string-format.py

View File

@ -1,3 +0,0 @@
[weblate]
url = https://hosted.weblate.org/api/
translation = f-droid/f-droid

View File

@ -10,7 +10,7 @@ fdroid_root := $(LOCAL_PATH)
fdroid_dir := app
fdroid_out := $(PWD)/$(OUT_DIR)/target/common/obj/APPS/$(LOCAL_MODULE)_intermediates
fdroid_build := $(fdroid_root)/$(fdroid_dir)/build
fdroid_apk := build/outputs/apk/full/release/$(fdroid_dir)-full-release-unsigned.apk
fdroid_apk := build/outputs/apk/$(fdroid_dir)-release-unsigned.apk
$(fdroid_root)/$(fdroid_dir)/$(fdroid_apk):
rm -Rf $(fdroid_build)

View File

@ -1,628 +1,4 @@
### 1.13-alpha1 (2021-06-02)
* Stop repeated updates of Trichrome Library
* More changes to follow Material Design (@proletarius101)
* Improve OpenCollective badge (@ConnyDuck)
### 1.13-alpha0 (2021-04-22)
* Theme support tied to built-in Android themes (@proletarius101)
* New top banner notifications: "No Internet" and "No Data or WiFi enabled"
* Improved handling of USB-OTG and SD Card repos and mirrors
### 1.12.1 (2021-04-12)
* Fix trove4j verification error
### 1.12 (2021-04-06)
* Sync translations
### 1.12-alpha3 (2021-03-10)
* Opt-in F-Droid Metrics
### 1.12-alpha2 (2021-03-03)
* Overhaul clean up of cached files
* Support updating "shared library packages" like Trichrome (@uldiniad)
### 1.12-alpha1 (2021-02-25)
* Add extra sanitation to search terms to prevent vulnerabilities.
* Fix Nearby Swap's close button (@proletarius101)
* Bump to compileSdkVersion 29 to support Java8
* Set up WorkManager on demand to avoid slowing down starts
* Prefer system keys when APKs are signed by them (@glennmen)
### 1.12-alpha0 (2021-02-08)
* App description localization now fully respects lists of languages in Android
Language Settings
* Latest Tab lists results based on the Language Settings
* Latest Tab now shows results ordered newest first (@TheLastProject @IzzySoft)
* Theme support modernized and tied to the built-in Android themes (@proletarius101)
* Search results greatly improved (@Tvax @gcbrown76)
* Let Android efficiently schedule background cache cleanup operations (@Isira-Seneviratne)
* Overhaul repo URL parsing for reliable repo adding (@projectgus)
### 1.11 (2020-12-29)
* Improved linkifying of URLs in app descriptions
* Improved handling of SDCards and USG-OTG in Nearby
* Modernized code and switched PNGs to vectors (thanks @isira-seneviratne!)
* Recognize longer repo URLs to support GitCDN/RawGit/etc mirrors
### 1.10 (2020-10-20)
* Improved language selection with multiple locales
(thanks @spacecowboy and @bubu!)
### 1.10-alpha1 (2020-09-29)
* use notification channels for fine-grained control (@Isira-Seneviratne)
### 1.10-alpha0 (2020-07-20)
* Latest Tab will show better results on non-English devices
* updates to core libraries (Jackson, androidx, gradle, etc)
* use Gradle's new dependency verification
* polish whitelabeling support
### 1.9 (2020-06-25)
* Removed "Android App Link" support since it cannot work with
F-Droid, and it was triggering DNS leaks.
* Archive Repos are now lower priority than the Repo (higher on the
Manage Repos screen), fixing issues where it looked for icons,
screenshots and other information in the Archive rather than the
Repo itself.
* Fixed hopefully all occurrences where F-Droid client couldn't show an icon.
The remaining cases of missing icons are now caused either by
icons not included in upstream repo or by temporary network failures.
(After updating this requires one additional repo update to take effect.)
* Fixed a problem where repository updates would never trigger
when either "Over Data" or "Over Wifi" were disabled.
* Support OpenCollective donation option and highlight
free software donation platforms
* Fix for when the app update button wasn't showing up or working
in some cases (thanks @di72nn)
* Stop cropping feature header image (thanks @ByteHamster!)
* Make navigation bar match dark mode (thanks @MatthieuB!)
* Cleaned out obsolete code (thanks @Isira-Seneviratne!)
### 1.8-alpha2 (2020-02-04)
* stop showing Unknown Sources with Privileged Extension on Android 10 #1833
* add standard ripple effect to links on app details activity
* fix displaying default icon for apps without icons
### 1.8-alpha1 (2020-01-10)
* handle Android 10 permission config to stop Unknown Sources prompts
* keyboard opens when search is cleared
* translation sync with Android strings
* force common repo domains to HTTPS (GitLab, GitHub, Amazon)
### 1.8-alpha0 (2019-11-20)
* fix seekbar preference on recent Android versions (thanks @dkanada)
* handle API 29 split-permissions: fine location now implies coarse location
* define backup rules to avoid saving the swap repo
### 1.7.1 (2019-07-31)
* fix crashes from ACRA report emails
### 1.7 (2019-07-06)
* fix crash in Panic Settings
* catch random crashes related to WifiApControl
### 1.7-alpha2 (2019-06-18)
* USB OTG flash drives can be used as nearby repos and mirrors
### 1.7-alpha1 (2019-06-14)
* overhauled nearby swap using the device's hotspot AP
* add new panic responses: app uninstalls and reset repos to default
* fix proxy support on first start
### 1.7-alpha0 (2019-05-20)
* major refactor of "Nearby" UI code, to prepare for rewriting guts
* show "undo" after swiping away items from the Updates tab (thanks @Hocuri!)
* fix ETag handling when connecting to nginx mirrors #1737
* fix issues with "Latest" display caused by mishandling time zones #1757
* ignore all unimportant crashes in background services
* do not use Privileged Extension if it was disabled in Settings
### 1.6.2 (2019-05-20)
* fixed issue where cached indexes were wrongly redownloaded (#1737),
thanks to @amiraliakbari for tracking it down!
* fixed wrong string for the translated title of the Updates Tab (#1785)
* fixed crashes on very low memory when starting
### 1.6.1 (2019-05-10)
* Updated translations
* fixed button size issues #1678
* stopped random background crashes
### 1.6 (2019-04-10)
* update F-Droid after all other updates (#1556)
* Improve adding repos from the clipboard (e.g. Firefox Klar)
* swap usability improvements
* many crash fixes in swap and background services
### 1.6-alpha2 (2019-03-28)
* Latest Tab now highlights apps that provide descriptions,
translations, screenshots
* Auto-download from mirrors, to speed up downloads and reduce load on
f-droid.org
* More efficient download caching (per-repo; across different
webservers #1708)
* Fix problems canceling downloads (#1727, #1736, #1742)
* Fix downloading OBB files from repos (#1403)
### 1.6-alpha1 (2019-02-20)
* add switches in RepoDetails to disable any or all mirrors (#1696)
* choose random mirror for each package/APK download
* make all APK downloads be cached per-repo, not per-mirror
* handle Apache and Nginx ETags when checking if index is current (#1708)
### 1.6-alpha0 (2019-02-15)
* handle implied READ_EXTERNAL_STORAGE permissions, which trigger a
permissions prompt on installs with Privileged Extension (#1702)
* sanitize index data to reduce the threats from the server
* set Read Timeout to trigger mirror use when reads are slow
* fix missing icons for those who do not use WiFi (#1592)
* use separate titles for Updates pref and Updates tab, so that they
can be better translated
* UI fixes from @ConnyDuck (#1636, #1618)
### 1.5.1 (2019-01-07)
* Removed incomplete translations that were accidentally added in 1.5
* Fix screenshot background on dark themes (#1618)
### 1.5 (2018-12-26)
* Nearby swap bug fixes and improvements
* update language and translations about Nearby and swap
* Fix displaying of icons for self-built apps (#1108)
### 1.5-alpha2 (2018-12-21)
* support swapping via SD Cards
* display versionCode in expanded Versions list entries in Expert Mode
### 1.5-alpha1 (2018-12-12)
* UX and language cleanup of App Details
### 1.5-alpha0 (2018-10-19)
* add repos via additional_repos.xml from ROM, OEM, Vendor.
### 1.4 (2018-09-12)
* polish up new "Versions" list and other UI fixes
### 1.4-alpha1 (2018-08-30)
* huge overhaul of the "Versions" list in the App Details screen, and
many other UI improvements, thanks to new contributor @wsdfhjxc
* fixes to allow keyboard/d-pad navigation in more places, thanks to
new contributor @doeffinger
### 1.4-alpha0 (2018-08-17)
* show "Open" button when media is installed and viewable
* retry index downloads from mirrors
* add Share button to "Installed Apps" to export CSV list
* add clickable list of APKs to the swap HTML index page
### 1.3.1 (2018-08-07)
* big overhaul of core nearby/swap plumbing
* TLSv1.3 support, when the device supports it
### 1.3 (2018-07-31)
* large overhaul to make status updates more reliable
* fixed many bugs around the wrong button showing
### 1.3-alpha5 (2018-07-21)
* overhaul install button logic to avoid false presses
* improved first time run experience
* export install/uninstall history
* more whitelabeling improvements
### 1.3-alpha4 (2018-07-13)
* fix Data/WiFi preferences to properly schedule Updats
* fix Install/Uninstall events for clearer feedback
* track pending installs properly, stop fake repeating updates
* add support for Repo Push Requests when using Index V1
* support NoSourceSince anti-feature
* share menu item for repos
* fix a few crasher bugs
### 1.3-alpha3 (2018-06-27)
* fix bug that disabled Privileged Extension
* prevent crash loop after rapid install/uninstall cycling
* add expert option to send debug version/UUID on each HTTP download
* allow user to disable ACRA entirely with a preference
* basic Install History viewer, available only when logging is enabled
### 1.3-alpha2 (2018-06-25)
* Settings improvements
* new Expert Setting for disabling all notifications
* huge improvements for custom "whitelabel" F-Droid versions
### 1.3-alpha1 (2018-06-15)
* improved Settings for controlling data usage
* support push install/uninstall requests in index-v1
### 1.3-alpha0 (2018-04-25)
* more battery conscious background operation on Android 5.0 and newer
* make Anti-Features list in App Details clickable
* new Settings for controlling data usage
* switch Settings to Material style
* bumped minimum supported version to Android 4.0 (14)
### 1.2.2 (2018-04-23)
* fix crasher bug on devices running on Android 4.2 or older #1424
### 1.2.1 (2018-04-18)
* improved automatic mirror selection
* more swap/nearby bug fixes and improvements
### 1.2 (2018-04-13)
* lots of swap/nearby bug fixes and improvements
* fix one cause of reoccuring update notifications (#1271)
* make F-Droid recognize fdroid nightly URLs from GitLab
### 1.2-alpha1 (2018-04-06)
* fix Privileged Extension install with apps with uses-permision-sdk-23
* automatically trim or delete cache when storage space is low
* improved performance on low memory devices
* make all downloads respect "Only on Wi-Fi" preference
### 1.2-alpha0 (2018-03-30)
* add custom mirrors to any repo by clicking links, scanning QR codes, etc.
* reduce memory usage when device is running low
### 1.1 (2018-03-21)
* fix some problems with items Updates reappearing
* fix failback install method when permissions aren't in sync #1310
### 1.1-alpha4 (2018-03-09)
* fix the most popular ACRA crash reports
* UI layout improvements
* warn users when scanning QR with camera without autofocus
### 1.1-alpha3 (2018-02-13)
* add sort button to Search view: alpha or most recent
* fix bugs: #1305 #1306 #1325
* add more detail to ACRA crash reports
### 1.1-alpha2 (2018-02-06)
* reload index after system locale change or OS upgrade
* add "panic responder" support
### 1.1-alpha1 (2018-01-26)
* provision new repos via a provisioning file
* "Android App Links" handling aka "Digital Asset Links"
* new privacy prefs: disable screenshots; exit on panic
### 1.1-alpha0 (2017-11-09)
* automatically choose between official repo mirrors
* fullscreen, swipeable app screenshot navigation
* new preference to prevent screenshots/recents
* fix crasher bug #1203
### 1.0.1 (2017-10-23)
* fixed index update failure on Android 5.0 (#1014)
### 1.0 (2017-10-10)
* Completely overhauled workflow for updating apps
* Fully translatable app summaries and descriptions
* "What's New" section to show changes in current release
* Screenshots and feature graphics
* Support installing media, OTA, ZIP, etc. files
* Improved protection against tracking (HTTP ETag, TLS, etc.)
* Fully background updates with Privileged Extension
* Highlight donations to app developers
* Much faster index updates
### 1.0-alpha5 (2017-10-04)
* Fix bug that prevented translations from showing up on Android >= 7.0 (#987)
* Fix DB upgrade crash from 1.0-alpha3 --> 1.0-alpha4 (#1181)
### 1.0-alpha4 (2017-09-27)
* Added swipe gestures to the Updates tab
* Display warnings with actions in Updates tab for KnownVulns
* Translation updates
* Dark UI fixes
### 1.0-alpha3 (2017-09-12)
* Big UI performance improvements, especially with archive enabled
* Fixed crasher bugs
### 1.0-alpha2 (2017-09-04)
* Prevent HTTP ETag from being used as a tracking cookie
* Improved screenshots layout
* Properly clean up temp and cached files
* Dark mode fixes
### 1.0-alpha1 (2017-07-18)
* Fix bug removing apps from repos (#568)
* Much faster index updates
### 1.0-alpha0 (2017-07-08)
* Support installing media, OTA, ZIP, etc. files
* Fully support APKs signed by multiple signing keys
* Tibetan translation
* Remove related apps and categories after disabling repo
### 0.104 (2017-06-16)
* Support apps with APKs signed by more than one key
* Fix F-Droid update notifications that never go away
### 0.103.2 (2017-05-31)
* Fix problematic updates and notifications (#1013)
* Language and stability updates
### 0.103.1 (2017-05-12)
* Various stability fixes
* Bits of text no longer randomly switch to English
* Fix send F-Droid via Bluetooth on Android 7.x
### 0.103 (2017-05-02)
* Complete overhaul of the user experience
* Complete support for localization, including app descriptions
* Support for screenshots, graphics, and "What's New" texts
* Stable support for F-Droid Privileged Extension
### 0.102.3 (2017-04-01)
* Fix issue with installing from the wrong repo (#909)
* Allow F-Droid to update Privileged Extension (#911)
* Ignore errors that are likely due to filesystem corruption (#855)
* Improve installs/uninstalls with Privileged Extension on 7.x
### 0.102.2 (2017-03-14)
* Fix installing with Privileged Extension on 7.x
* Detect app updates via sytem OTA updates (#819)
### 0.102.1 (2017-02-24)
* Detect installed/uninstalled state more reliably (#854)
* Ensure dark theme gets applied everywhere (#750)
### 0.102 (2016-11-28)
* Optionally keep install history
* Optionally let repositories request installs and uninstalls of apps
* Support for APK extension files (OBB)
* Enable TLS v1.2 for HTTPS on all devices that support it (again)
* Better support for multiple repositories providing the same app
### 0.101 (2016-09-28)
* Support for Android 2.2 is dropped, 2.3.3 or later is now required
* Fixed APK Cache bugs, requiring the cache time be reset to one day
* Use Privileged Extension by default if installed
* Optionally grey out apps that require Anti-Features
* Translation updates
### 0.100.1 (2016-06-21)
* Fix background crash after installing or updating apps
* Fix crash if an app has a short description
* Fix background crash in the Wi-Fi state change swap service
* Fix crash if there is a problem listing the cached files to delete
### 0.100 (2016-06-07)
* Ability to download apps in the background
* Significant performance improvements when updating repositories
* Add setting to enable automatic downloading of updates
* Apks can now be kept on disk for various amounts of time
### Upcoming release
* Show what repository each apk comes from
@ -660,7 +36,7 @@
* Fix crash when adding malformed URIs as repos
* Fix Android.mk build when the output dir. is a relative path
* Fix Android.mk build when the output dir is a relative path
### 0.98 (2016-02-01)
@ -724,7 +100,7 @@
* Move the repo index update to a notification
* Handle APK downloads without a dialog
* Handle apk downloads without a dialog
* Don't let users try to uninstall system apps that haven't been updated
@ -767,14 +143,14 @@
and now easy to set up with root privileges
* Speed up and simplify repo update process by streaming the data out of the
JAR file directly
jar file directly
* Can now manually add swap repo via "Repositories" screen
* Using NFC during swap now initiates a proper swap, rather than redirecting to
the "Repositories" screen
* Drop Ant support to greatly simplify the build process and its maintenance
* Drop ant support to greatly simplify the build process and its maintenance
### 0.92 (2015-06-08)
@ -782,7 +158,7 @@
* Update Universal-Image-Loader to 1.9.4
* Make APK downloads progress be measured in kilobytes instead of bytes
* Make Apk downloads progress be measured in kilobytes instead of bytes
* Add missing Sardinian language to the preferences
@ -795,9 +171,9 @@
since it's not needed to use our own external app directory
* Fix a crash occuring if the user triggered a repo update that got rid of
more than 450 APKs at once
more than 450 apks at once
* Properly cache APK files on the SD card if configured this way
* Properly cache apk files on the SD card if configured this way
* Drop support for unsigned repos in favour of signed ones and TOFU support
@ -811,7 +187,7 @@
* Don't crash if links on descriptions cannot be handled by any application
* Support building as part of a ROM via an Android.mk using Gradle
* Support building as part of a ROM via an Android.mk using gradle
### 0.88 (2015-04-28)
@ -821,7 +197,7 @@
* User interface language can now be changed from inside the F-Droid
preferences without changing the system language (locale)
* Fix an issue where XML files could pile up in the data directory
* Fix an issue where xml files could pile up in the data directory
* Improve app and search link handling while also adding supporting for Amazon
and Google Play links
@ -831,12 +207,12 @@
* Show a message to the user when there are no apps to display.
* Swapping is now two-way. Connecting to a swap on one device will
initiate a swap on the other device
* Swapping is now two way. Connecting to a swap on one device will
initiate a swap on the other device.
* Small UI fixes to avoid overlapping text and improve app version ellipsizing
* Split up search terms when querying the app database"fire fox" now
* Split up search terms when querying the app database - "fire fox" now
matches FireFox
* Ignore trailing paces in search terms introduced by some input methods
@ -853,7 +229,7 @@
* Fix issue that caused the installed state label to sometimes not be updated
* Support for future devices with more than two CPU architectures
* Support for future devices with more than two cpu architectures
* Show when packages are installed but not via F-Droid (mismatching signature)
@ -882,7 +258,7 @@
* Update Universal-Image-Loader and the Support libraries
* Switch the directory structure to better suit building with Gradle
* Switch the directory structure to better suit building with gradle
* Translation updates
@ -904,14 +280,14 @@
* HTTP Proxy support in Preferences
* Directly send installed apps to other devices via Bluetooth and Android Beam
(NFC+Bluetooth), also compatible with Samsung/HTC S Beam
(NFC+Bluetooth), also compatible with Samsung/HTC S-Beam
* Initial support for root and system installers, allowing the client to
install APKs directly on its own
install apks directly on its own
* Increased performance when updating from repository with many apps
* Switch to AppCompat from the Support library
* Switch to Appcompat from the Support library
* Fix some crashes
@ -950,7 +326,7 @@
* Send F-Droid via Bluetooth to any device that supports receiving APKs via
Bluetooth (stock Android blocks APKs, most ROMs allow them)
* NFC support: Beam repo configs from the repo detail view (Android 4.0+),
* NFC support: beam repo configs from the repo detail view (Android 4.0+),
beam the F-Droid.apk from F-Droid's main screen (Android 4.1+)
* Support for repositories using self-signed HTTPS certificates through
@ -964,7 +340,7 @@
* Major internal changes to enable F-Droid to handle repos with thousands
of apps without slowing down too much. These internal changes will also make
new features easier to implement
new features easier to implement.
* Various fixes to layout issues introduced in 0.58
@ -978,7 +354,7 @@
* Tweaked some layouts, especially the app lists and their compact layout
* App lists now show more useful version information: Current version names,
* App lists now show more useful version information: current version names,
rather than number of versions available
* Reduce scroll lag in app lists by caching views in a ViewHolder
@ -994,21 +370,21 @@
can see what the checkbox preferences actually mean and what the edit and
list preferences are set at
* Support for Dogecoin donation method added (wow)
* Support for dogecoin donation method added (wow)
* Don't keep app icons older than 30 days in disc cache
* Don't keep app icons older than 30 days on disc cache
* Always include incompatible APKs in memory to avoid issues with apps
seemingly not having any APKs available
* Always include incompatible apks in memory to avoid issues with apps
seemingly not having any apks available
* Fixed a crash when trying to access a non-existing app
* F-Droid registers with Android to receive F-Droid URIs https://\*/fdroid/repo
and fdroidrepos://
* Support including signing key fingerprint in repo URIs
* support including signing key fingerprint in repo URIs
* When adding new repos that include the fingerprint, check to see whether
* when adding new repos that include the fingerprint, check to see whether
that repo exists in F-Droid already, and if the fingerprints match
* Other minor bug fixes
@ -1019,12 +395,12 @@
* Fixed problems with category selection and permission lists on Android 2.X devices.
* Lots of translation updates, including new Norwegian translation
* Lots of translation updates, including new Norwegian translation.
### 0.54 (2013-11-05)
* New options on the App Details screen to ignore all future updates for that
particular app, or ignore just the current update
particular app, or ignore just the current update.
* Apps with Anti-features are no longer hidden, and the corresponding
preferences to unhide them are removed. Instead they are clearly marked on the
@ -1033,7 +409,7 @@
* Apps with incompatible native code architecture requirements are now correctly
filtered.
* A bug that prevented update notifications from appearing has been fixed
* A bug that prevented update notifications from appearing has been fixed.
* Theming support, with Light and Dark themes.
@ -1042,29 +418,29 @@
installation.
* All app donation options have been grouped into a submenu, and Litecoin
donation support has been added
donation support has been added.
* App filter settings now take effect immediately
* App filter settings now take effect immediately.
* APK native code ABIs are now shown in expert mode
* Apk native code ABIs are now shown in expert mode.
* Search URIs for market://search and fdroid.search: are now handled
* Search uris for market://search and fdroid.search: are now handled.
* A problem with ActionBar Up navigation on some devices has been fixed
* A problem with ActionBar Up navigation on some devices has been fixed.
* Other minor bug fixes, and adjustments to spacings and layouts
* Other minor bug fixes, and adjustments to spacings and layouts.
* Lots of translation updates
* Lots of translation updates.
### 0.50 (2013-08-20)
* New basic app sharing functionality
* Handle f-droid.org web repo as well as market:// app URIs
* Handle f-droid.org web repo as well as market:// app uris
* Search by just typing on main screen and search results screen
* Flattr and bitcoin donation methods added
* Flattr and Bitcoin donation methods added
* Noticeable speedups when returning from installs and uninstalls

View File

@ -26,16 +26,14 @@ track of modifications and fuzzy translations. Applying translations manually
skips all of the fixes and checks, and overrides the fuzzy state of strings.
Note that you cannot change the English strings on Weblate. If you have any
suggestions on how to improve them, open an issue or merge request like you
would if you were making code changes. This way the changes can be reviewed
before the source strings on Weblate are changed.
suggestions on how to improve them, open a merge request like you would if you
were making code changes. This way the changes can be reviewed before the
source strings on Weblate are changed.
## Code Style
We follow the default Android Studio code formatter (e.g. `Ctrl-Alt-L`). This
should be more or less the same as [Android Java
style](https://source.android.com/source/code-style.html). Some key points:
We follow the [Android Java style](https://source.android.com/source/code-style.html).
Some key points:
* Four space indentation
* UTF-8 source files
@ -47,35 +45,63 @@ style](https://source.android.com/source/code-style.html). Some key points:
* Braces are always used after if, for and while
The current code base doesn't follow it entirely, but new code should follow
it. We enforce some of these, but not all, via `./gradlew checkstyle`.
it. We enforce some of these, but not all, via checkstyle.
## Debugging
To get all the logcat messages by F-Droid, you can run:
adb logcat | grep `adb shell ps | grep org.fdroid.fdroid | cut -c10-15`
## Building tips
* Use gradle with `--daemon` if you are going to build F-Droid multiple times.
* If you get a message like `Could not find com.android.support:support-...`,
make sure that you have the latest Android support maven repository.
## Running the test suite
Before pushing commits to a merge request, make sure this passes:
./gradlew checkstyle pmd lint
In order to run the F-Droid test suite, you will need to have either a real device
connected via `adb`, or an emulator running. Then, execute the following from the
command line:
./gradlew check
./gradlew connectedCheck
Many important tests require a device or emulator, but do not work in GitLab CI.
That mean they need to be run locally, and that is usually easiest in Android
Studio rather than the command line.
Note that the CI already runs the tests on an emulator, so you don't
necessarily have to do this yourself if you open a merge request as the tests
will get run.
For a quick way to run a specific JUnit/Robolectric test:
## Versioning
./gradlew testFullDebugUnitTest --tests *LocaleSelectionTest*
Each stable version follows the `X.Y` pattern. Hotfix releases - i.e. when a
stable has an important bug that needs immediate fixing - will follow the
`X.Y.Z` pattern.
For a quick way to run a specific emulator test:
Before each stable release, a number of alpha releases will be released. They
will follow the pattern `X.Y-alphaN`, where `N` is the current alpha number.
These will usually include changes and new features that have not been tested
enough for a stable release, so use at your own risk. Testers and reporters
are very welcome.
./gradlew connectedFullDebugAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=org.fdroid.fdroid.MainActivityExpressoTest
The version codes use a number of digits per each of these keys: `XXXYYYZNN`.
So for example, 1.3.1 would be `1003150` and 0.95-alpha13 would be `95013`
(leading zeros are omitted).
Note that we use a trailing `50` for actual stable releases, so alphas are
limited to `-alpha49`.
## Making releases
This is an example of a release process for **0.95**:
See https://gitlab.com/fdroid/wiki/-/wikis/Internal/Release-Process#fdroidclient
* We are currently at stable **0.94**
* **0.95-alpha1** is released
* **0.95-alpha2** is released
* **0.95-alpha3** is released
* `stable-v0.95` is branched and frozen
* **0.95** is released
* A bug is reported on the stable release and fixed
* **0.95.1** is released with only that fix
As soon as a stable is tagged, master will move on to `-alpha0` on the next
version. This is a temporary measure - until `-alpha1` is released - so that
moving from stable to master doesn't require a downgrade. `-alpha0` versions
will not be tagged nor released.

View File

@ -1,11 +0,0 @@
---
liberapay: F-Droid-Data
open_collective: F-Droid
github:
- f-droid
- eighthave
custom:
- https://f-droid.org/donate/
- https://www.hellotux.com/f-droid
- https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=E2FCXCT6837GL
- https://blockchain.info/address/15u8aAPK4jJ5N8wpWJ5gutAyyeHtKX5i18

View File

@ -0,0 +1,49 @@
repositories {
jcenter()
}
apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
dependencies {
compile project(':privileged-api-lib')
}
android {
compileSdkVersion 23
buildToolsVersion '23.0.3'
defaultConfig {
minSdkVersion 8
targetSdkVersion 23
versionCode 1050
versionName "0.1"
}
compileOptions {
compileOptions.encoding = "UTF-8"
// Use Java 1.7, requires minSdk 8
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
lintOptions {
// Do not abort build if lint finds errors
abortOnError false
}
}
checkstyle {
toolVersion = '6.17'
}
task checkstyle(type: Checkstyle) {
configFile file("${project.rootDir}/config/checkstyle/checkstyle.xml")
source 'src'
include '**/*.java'
exclude '**/gen/**'
classpath = files()
}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.fdroid.fdroid.privileged">
<!-- These permissions are only granted when this apk is installed as a privileged app! -->
<uses-permission
android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.DELETE_PACKAGES"
tools:ignore="ProtectedPermissions" />
<!--
Only apps signed with the same key can use this permission!
The permission is automatically granted independent of the install order
Never presented to the user due to the protectionLevel.
-->
<permission
android:name="org.fdroid.fdroid.privileged.USE_SERVICE"
android:protectionLevel="signature" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<service
android:name=".PrivilegedService"
android:enabled="true"
android:exported="true"
android:permission="org.fdroid.fdroid.privileged.USE_SERVICE"
android:process=":fdroid_privileged">
<intent-filter>
<action android:name="org.fdroid.fdroid.privileged.IPrivilegedService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package android.content.pm;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
/**
* Just a non-working implementation of this Stub to satisfy compiler!
*/
public interface IPackageDeleteObserver extends IInterface {
abstract class Stub extends Binder implements android.content.pm.IPackageDeleteObserver {
public Stub() {
throw new RuntimeException("Stub!");
}
public static IPackageDeleteObserver asInterface(IBinder obj) {
throw new RuntimeException("Stub!");
}
public IBinder asBinder() {
throw new RuntimeException("Stub!");
}
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
throw new RuntimeException("Stub!");
}
}
void packageDeleted(java.lang.String packageName, int returnCode) throws RemoteException;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package android.content.pm;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
/**
* Just a non-working implementation of this Stub to satisfy compiler!
*/
public interface IPackageInstallObserver extends IInterface {
abstract class Stub extends Binder implements android.content.pm.IPackageInstallObserver {
public Stub() {
throw new RuntimeException("Stub!");
}
public static android.content.pm.IPackageInstallObserver asInterface(IBinder obj) {
throw new RuntimeException("Stub!");
}
public IBinder asBinder() {
throw new RuntimeException("Stub!");
}
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
throw new RuntimeException("Stub!");
}
}
void packageInstalled(String packageName, int returnCode) throws RemoteException;
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.privileged;
import android.Manifest;
import android.app.Service;
import android.content.Intent;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.lang.reflect.Method;
/**
* This service provides an API via AIDL IPC for the main F-Droid app to install/delete packages.
*/
public class PrivilegedService extends Service {
private static final String TAG = "PrivilegedService";
private Method mInstallMethod;
private Method mDeleteMethod;
private boolean hasPrivilegedPermissionsImpl() {
boolean hasInstallPermission =
getPackageManager().checkPermission(Manifest.permission.INSTALL_PACKAGES, getPackageName())
== PackageManager.PERMISSION_GRANTED;
boolean hasDeletePermission =
getPackageManager().checkPermission(Manifest.permission.DELETE_PACKAGES, getPackageName())
== PackageManager.PERMISSION_GRANTED;
return hasInstallPermission && hasDeletePermission;
}
private void installPackageImpl(Uri packageURI, int flags, String installerPackageName,
final IPrivilegedCallback callback) {
// Internal callback from the system
IPackageInstallObserver.Stub installObserver = new IPackageInstallObserver.Stub() {
@Override
public void packageInstalled(String packageName, int returnCode) throws RemoteException {
// forward this internal callback to our callback
try {
callback.handleResult(packageName, returnCode);
} catch (RemoteException e1) {
Log.e(TAG, "RemoteException", e1);
}
}
};
// execute internal method
try {
mInstallMethod.invoke(getPackageManager(), packageURI, installObserver,
flags, installerPackageName);
} catch (Exception e) {
Log.e(TAG, "Android not compatible!", e);
try {
callback.handleResult(null, 0);
} catch (RemoteException e1) {
Log.e(TAG, "RemoteException", e1);
}
}
}
private void deletePackageImpl(String packageName, int flags, final IPrivilegedCallback callback) {
// Internal callback from the system
IPackageDeleteObserver.Stub deleteObserver = new IPackageDeleteObserver.Stub() {
@Override
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
// forward this internal callback to our callback
try {
callback.handleResult(packageName, returnCode);
} catch (RemoteException e1) {
Log.e(TAG, "RemoteException", e1);
}
}
};
// execute internal method
try {
mDeleteMethod.invoke(getPackageManager(), packageName, deleteObserver, flags);
} catch (Exception e) {
Log.e(TAG, "Android not compatible!", e);
try {
callback.handleResult(null, 0);
} catch (RemoteException e1) {
Log.e(TAG, "RemoteException", e1);
}
}
}
private final IPrivilegedService.Stub mBinder = new IPrivilegedService.Stub() {
@Override
public boolean hasPrivilegedPermissions() {
return hasPrivilegedPermissionsImpl();
}
@Override
public void installPackage(Uri packageURI, int flags, String installerPackageName,
IPrivilegedCallback callback) {
installPackageImpl(packageURI, flags, installerPackageName, callback);
}
@Override
public void deletePackage(String packageName, int flags, IPrivilegedCallback callback) {
deletePackageImpl(packageName, flags, callback);
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
// get internal methods via reflection
try {
Class<?>[] installTypes = {
Uri.class, IPackageInstallObserver.class, int.class,
String.class,
};
Class<?>[] deleteTypes = {
String.class, IPackageDeleteObserver.class,
int.class,
};
PackageManager pm = getPackageManager();
mInstallMethod = pm.getClass().getMethod("installPackage", installTypes);
mDeleteMethod = pm.getClass().getMethod("deletePackage", deleteTypes);
} catch (NoSuchMethodException e) {
Log.e(TAG, "Android not compatible!", e);
stopSelf();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Estensión de F-Droid privilexáu</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Privilegierte F-Droid-Erweiterung</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Επέκταση του F-Droid με δικαιώματα συστήματος</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="app_name">Privilegia F-Droid-aldonaĵo</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Extensión de F-Droid con permisos de sistema</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Pribilegiodun F-Droid Luzapena</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">افزونهٔ ممتاز اف‌دروید</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">F-Droid - Permissions Étendues</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">F-Droid Privileged Extension</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">F-Droid-tillegg med eleverte rettigheter</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="app_name">Geprivilegieerde F-Droid-extensie</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">F-Droid Privileged Extension</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Extensão privilegiada F-Droid</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="app_name">Extensie privilegiata F-Droid</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Привилегированное расширение F-Droid</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">F-Droid privilegované rozšírenie</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Prapashtesa e privilegjuar F-Droid</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Повлашћено проширење за Ф-дроид</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">F-Droid Ayrıcalıklı Uzantı</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Привілейоване розширення F-Droid</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='utf-8'?>
<resources><string name="app_name">Phần mở rộng F-Droid được cấp quyền</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">F-Droid Privileged Extension</string>
</resources>

View File

@ -1,7 +1,6 @@
# F-Droid Client
[![build status](https://gitlab.com/fdroid/fdroidclient/badges/master/pipeline.svg)](https://gitlab.com/fdroid/fdroidclient/-/jobs)
[![Translation status](https://hosted.weblate.org/widgets/f-droid/-/svg-badge.svg)](https://hosted.weblate.org/engage/f-droid/)
[![build status](https://gitlab.com/fdroid/fdroidclient/badges/HEAD/build.svg)](https://gitlab.com/fdroid/fdroidclient/builds) [![Translation status](https://hosted.weblate.org/widgets/f-droid/-/svg-badge.svg)](https://hosted.weblate.org/engage/f-droid/)
Client for [F-Droid](https://f-droid.org), the Free Software repository system
for Android.
@ -23,7 +22,7 @@ issues, translate the app into your language or help with development.
## IRC
We are on `#fdroid` and `#fdroid-dev` on Freenode. We hold weekly dev meetings
on `#fdroid-dev` on Thursdays at 11:30h UTC, which usually last half an hour.
on `#fdroid-dev` on Tuesdays at 20h UTC, which usually last half an hour.
## FAQ
@ -37,8 +36,10 @@ to what Google Play does.
privileged system app?
This used to be the case, but no longer is. Now the [Privileged
Extension](https://gitlab.com/fdroid/privileged-extension) is the one that should be placed in
the system. It can be bundled with a ROM or installed via a zip.
Extension](Privileged-Extension/) is the one that should be placed in
the system. It can be bundled with a ROM or installed via a zip, or
alternatively F-Droid can install it as a system app using root.
## License
This program is Free Software: You can use, study share and improve it at your
@ -57,11 +58,3 @@ Other icons are from the
[Material Design Icon set](https://github.com/google/material-design-icons)
released under an
[Attribution 4.0 International license](https://creativecommons.org/licenses/by/4.0/).
## Translation
Everything can be translated. See
[Translation and Localization](https://f-droid.org/docs/Translation_and_Localization)
for more info.
[![translation status](https://hosted.weblate.org/widgets/f-droid/-/f-droid/multi-auto.svg)](https://hosted.weblate.org/engage/f-droid/?utm_source=widget)

View File

@ -1,134 +1,183 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
apply plugin: 'checkstyle'
apply plugin: 'pmd'
/* gets the version name from the latest Git tag */
def getVersionName = { ->
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags', '--always'
standardOutput = stdout
}
return stdout.toString().trim()
repositories {
jcenter()
}
def isCi = "true" == System.getenv("CI")
def preDexEnabled = "true" == System.getProperty("pre-dex", "true")
dependencies {
compile project(':privileged-api-lib')
def fullApplicationId = "org.fdroid.fdroid"
def basicApplicationId = "org.fdroid.basic"
// yes, this actually needs both quotes https://stackoverflow.com/a/41391841
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:support-annotations:23.3.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.google.zxing:core:3.2.1'
compile 'eu.chainfire:libsuperuser:1.0.0.201602271131'
compile 'cc.mvdan.accesspoint:library:0.2.0'
compile 'info.guardianproject.netcipher:netcipher:1.2.1'
compile 'commons-io:commons-io:2.4'
compile 'commons-net:commons-net:3.4'
compile 'org.openhab.jmdns:jmdns:3.4.2'
compile('ch.acra:acra:4.8.5') {
exclude module: 'support-v4'
exclude module: 'support-annotations'
}
compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:0.23.0'
testCompile 'junit:junit:4.12'
androidTestCompile 'com.android.support:support-annotations:23.3.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
}
if (!hasProperty('sourceDeps')) {
repositories {
// This is here until we sort out all dependencies from mavenCentral/jcenter. Once all of
// the dependencies below have been sorted out, this can be removed.
flatDir {
dirs 'libs/binaryDeps'
}
}
dependencies {
compile 'com.madgag.spongycastle:pkix:1.53.0.0'
compile 'com.madgag.spongycastle:prov:1.53.0.0'
compile 'com.madgag.spongycastle:core:1.53.0.0'
// Upstream doesn't have a binary on mavenCentral/jcenter yet:
// https://github.com/kolavar/android-support-v4-preferencefragment/issues/13
compile(name: 'support-v4-preferencefragment-release', ext: 'aar')
// Fork for F-Droid, including support for https. Not merged into upstream
// yet (seems to be a little unsupported as of late), so not using mavenCentral/jcenter.
compile(name: 'nanohttpd-2.1.0')
// Upstream doesn't have a binary on mavenCentral, and it is an SVN repo on
// Google Code. We include this code directly in this repo, and have made
// modifications that should be pushed to anyone who wants to maintain it.
compile(name: 'zipsigner')
}
// Only do the libraries imported from maven repositories. Our own libraries
// (like privileged-api-lib) and the prebuilt jars already checked into the
// source code don't need to be here.
dependencyVerification {
verify = [
'com.android.support:support-v4:1e8b7cc1cb3d6f6a2fd913791a6313df6bbaa470be450384474e906bc234bd49',
'com.android.support:appcompat-v7:dce81c41f76d83fa315617f4bc8ef2f84c5aa54b686f37b559422b939f622490',
'com.android.support:support-annotations:e9e076f3ea4fb144387c6054a6f69a2f6150ad4b1907897aaf55d6e8f4b8b91e',
'com.nostra13.universalimageloader:universal-image-loader:dbd5197ffec3a8317533190870a7c00ff3750dd6a31241448c6a5522d51b65b4',
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
'eu.chainfire:libsuperuser:018344ff19ee94d252c14b4a503ee8b519184db473a5af83513f5837c413b128',
'cc.mvdan.accesspoint:library:0837b38adb48b66bb1385adb6ade8ecce7002ad815c55abf13517c82193458ea',
'commons-io:commons-io:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581',
'commons-net:commons-net:38cf2eca826b8bcdb236fc1f2e79e0c6dd8e7e0f5c44a3b8e839a1065b2fbe2e',
'info.guardianproject.netcipher:netcipher:611ec5bde9d799fd57e1efec5c375f9f460de2cdda98918541decc9a7d02f2ad',
'org.openhab.jmdns:jmdns:7a4b34b5606bbd2aff7fdfe629edcb0416fccd367fb59a099f210b9aba4f0bce',
'com.madgag.spongycastle:pkix:6aba9b2210907a3d46dd3dcac782bb3424185290468d102d5207ebdc9796a905',
'com.madgag.spongycastle:prov:029f26cd6b67c06ffa05702d426d472c141789001bcb15b7262ed86c868e5643',
'com.madgag.spongycastle:core:9b6b7ac856b91bcda2ede694eccd26cefb0bf0b09b89f13cda05b5da5ff68c6b',
'ch.acra:acra:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb',
'io.reactivex:rxjava:2c162afd78eba217cdfee78b60e85d3bfb667db61e12bc95e3cf2ddc5beeadf6',
'io.reactivex:rxandroid:35c1a90f8c1f499db3c1f3d608e1f191ac8afddb10c02dd91ef04c03a0a4bcda',
]
}
} else {
logger.info "Setting up *source* dependencies for F-Droid (because you passed in the -PsourceDeps argument to gradle while building)."
dependencies {
compile(project(':extern:support-v4-preferencefragment')) {
exclude module: 'support-v4'
}
compile project(':extern:nanohttpd:core')
compile project(':extern:zipsigner')
}
task binaryDeps(type: Copy, dependsOn: ':app:prepareReleaseDependencies') {
enabled = project.hasProperty('sourceDeps')
description = "Copies .jar and .aar files from subproject dependencies in extern/ to app/libs. Requires the sourceDeps property to be set (\"gradle -PsourceDeps binaryDeps\")"
from('../extern/') {
include 'support-v4-preferencefragment/build/outputs/aar/support-v4-preferencefragment-release.aar'
include 'nanohttpd/core/build/libs/nanohttpd-2.1.0.jar'
include 'zipsigner/build/libs/zipsigner.jar'
}
into 'libs/binaryDeps'
includeEmptyDirs false
eachFile { FileCopyDetails details ->
// Don't copy to a sub folder such as libs/binaryDeps/Project/build/outputs/aar/project.aar, but
// rather libs/binaryDeps/project.aar.
details.path = details.name
}
}
}
android {
compileSdkVersion 30
defaultConfig {
versionCode 1013001
versionName getVersionName()
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
minSdkVersion 24
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 28
/*
The Android Testing Support Library collects analytics to continuously improve the testing
experience. More specifically, it uploads a hash of the package name of the application
under test for each invocation. If you do not wish to upload this data, you can opt-out by
passing the following argument to the test runner: disableAnalytics "true".
*/
testInstrumentationRunnerArguments disableAnalytics: 'true'
vectorDrawables.useSupportLibrary = true
}
compileSdkVersion 23
buildToolsVersion '23.0.3'
useLibrary 'org.apache.http.legacy'
buildTypes {
// use proguard on debug too since we have unknowingly broken
// release builds before.
all {
minifyEnabled true
useProguard true
shrinkResources true
buildConfigField "String", "PRIVILEGED_EXTENSION_PACKAGE_NAME", privilegedExtensionApplicationId
buildConfigField "String", "ACRA_REPORT_EMAIL", '"reports@f-droid.org"' // String needs both quotes
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'src/androidTest/proguard-rules.pro'
}
debug {
applicationIdSuffix ".debug"
resValue "string", "applicationId", fullApplicationId + applicationIdSuffix
versionNameSuffix "-debug"
println 'buildTypes.debug defaultConfig.versionCode ' + defaultConfig.versionCode
}
}
flavorDimensions "base"
productFlavors {
full {
dimension "base"
applicationId fullApplicationId
resValue "string", "applicationId", fullApplicationId
}
basic {
dimension "base"
applicationId basicApplicationId
resValue "string", "applicationId", basicApplicationId
testCoverageEnabled = true
}
}
compileOptions {
compileOptions.encoding = "UTF-8"
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
// Use Java 1.7, requires minSdk 8
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
aaptOptions {
cruncherEnabled = false
}
dexOptions {
// Improve build server performance by allowing disabling of pre-dexing
// see http://tools.android.com/tech-docs/new-build-system/tips#TOC-Improving-Build-Server-performance
// Skip pre-dexing when running on CI or when disabled via -Dpre-dex=false.
preDexLibraries = preDexEnabled && !isCi
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
testOptions {
unitTests {
includeAndroidResources = true
// prevent tests from dying on android.util.Log calls
returnDefaultValues = true
all {
// All the usual Gradle options.
testLogging {
events "skipped", "failed", "standardOut", "standardError"
showStandardStreams = true
}
systemProperty 'robolectric.dependency.repo.url', 'https://repo1.maven.org/maven2'
// hack to avoid memory leak crashes
forkEvery = 1
}
}
}
sourceSets {
test {
java.srcDirs += "$projectDir/src/testShared/java"
}
androidTest {
java.srcDirs += "$projectDir/src/testShared/java"
}
// prevent tests from dying on android.util.Log calls
unitTests.returnDefaultValues = true
}
lintOptions {
checkReleaseBuilds false
abortOnError true
abortOnError false
htmlReport true
xmlReport false
textReport false
lintConfig file("lint.xml")
// Our translations are crowd-sourced
disable 'MissingTranslation'
// We have locale folders like "values-he" and "values-id" as symlinks
// since some devices ship deprecated locale codes
disable 'LocaleFolder'
// Like supportsRtl or parentActivityName. They are on purpose.
disable 'UnusedAttribute'
}
packagingOptions {
@ -141,61 +190,8 @@ android {
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.work:work-runtime:2.4.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
implementation 'com.google.zxing:core:3.3.3'
implementation 'info.guardianproject.netcipher:netcipher:2.2.0-alpha'
implementation 'info.guardianproject.panic:panic:1.0'
implementation 'commons-io:commons-io:2.6'
implementation 'commons-net:commons-net:3.6'
implementation 'ch.acra:acra:4.9.1'
implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.9'
implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1'
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.65'
fullImplementation 'cc.mvdan.accesspoint:library:0.2.0'
fullImplementation 'org.jmdns:jmdns:3.5.5'
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'junit:junit:4.13.1'
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation 'org.mockito:mockito-core:3.3.3'
testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.65'
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:monitor:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.work:work-testing:2.4.0'
}
checkstyle {
toolVersion = '7.2'
toolVersion = '6.17'
}
task checkstyle(type: Checkstyle) {
@ -207,24 +203,53 @@ task checkstyle(type: Checkstyle) {
}
pmd {
toolVersion = '6.20.0'
toolVersion = '5.4.1'
consoleOutput = true
}
task pmdMain(type: Pmd) {
dependsOn 'assembleDebug'
ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-main.xml")
ruleSets = [] // otherwise defaults clash with the list in rules.xml
task pmd(type: Pmd, dependsOn: assembleDebug) {
ruleSets = [
//'java-basic',
//'java-unusedcode',
'java-android',
'java-clone',
'java-finalizers',
'java-imports',
'java-migrating',
//'java-unnecessary', // too nitpicky with parenthesis
]
source 'src/main/java'
include '**/*.java'
}
task pmdTest(type: Pmd) {
dependsOn 'assembleDebug'
ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-test.xml")
ruleSets = [] // otherwise defaults clash with the list in rules.xml
source 'src/test/java', 'src/androidTest/java'
include '**/*.java'
}
// This person took the example code below from another blogpost online, however
// I lost the reference to it:
// http://stackoverflow.com/questions/23297562/gradle-javadoc-and-android-documentation
android.applicationVariants.all { variant ->
task pmd(dependsOn: [pmdMain, pmdTest]) {}
task("generate${variant.name}Javadoc", type: Javadoc) {
title = "$name $version API"
description "Generates Javadoc for F-Droid."
source = variant.javaCompile.source
def sdkDir
Properties properties = new Properties()
File localProps = project.rootProject.file('local.properties')
if (localProps.exists()) {
properties.load(localProps.newDataInputStream())
sdkDir = properties.getProperty('sdk.dir')
} else {
sdkDir = System.getenv('ANDROID_HOME')
}
if (!sdkDir) {
throw new ProjectConfigurationException("Cannot find android sdk. Make sure sdk.dir is defined in local.properties or the environment variable ANDROID_HOME is set.", null)
}
ext.androidJar = "${sdkDir}/platforms/${android.compileSdkVersion}/android.jar"
classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar)
options.links("http://docs.oracle.com/javase/7/docs/api/");
options.links("http://d.android.com/reference/");
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
}

Binary file not shown.

Binary file not shown.

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Our translations are crowd-sourced -->
<issue id="MissingTranslation" severity="ignore"/>
<!-- to make CI fail on errors until this is fixed
https://github.com/rtyley/spongycastle/issues/7 -->
<issue id="InvalidPackage" severity="warning"/>
<issue id="ImpliedQuantity" severity="error"/>
<issue id="DefaultLocale" severity="error"/>
<issue id="SimpleDateFormat" severity="error"/>
<issue id="NewApi" severity="error"/>
<issue id="InlinedApi" severity="error"/>
<!-- These are important to us, so promote from warning to error -->
<issue id="UnusedResources" severity="error">
<ignore path="src/main/res/drawable/category_**.png" />
<ignore path="src/main/res/values/dimens.xml"/>
<ignore path="src/main/res/values/styles.xml"/>
<ignore path="src/full/res/values/styles.xml"/>
<!-- keep a single strings.xml for all build flavors -->
<ignore path="src/main/res/values**/strings.xml"/>
</issue>
<issue id="AppCompatMethod" severity="error"/>
<issue id="NestedScrolling" severity="error"/>
<issue id="Typos" severity="error"/>
<issue id="StringFormatCount" severity="error"/>
<issue id="UnsafeProtectedBroadcastReceiver" severity="error"/>
<issue id="GetInstance" severity="error"/>
<issue id="PackageManagerGetSignatures" severity="error"/>
<issue id="HardwareIds" severity="error"/>
<issue id="TrustAllX509TrustManager" severity="error">
<!-- these come from included libraries -->
<ignore path="org/apache/commons/net/ftp/FTPSTrustManager.class"/>
<ignore path="org/bouncycastle/est/jcajce/JcaJceUtils$1.class"/>
<ignore path="org/bouncycastle/est/jcajce/JcaJceUtils$2.class"/>
<ignore path="org/apache/commons/net/util/TrustManagerUtils$TrustManager.class"/>
</issue>
<issue id="PluralsCandidate" severity="error"/>
<issue id="HardcodedText" severity="error"/>
<issue id="RtlCompat" severity="error"/>
<issue id="RtlEnabled" severity="error"/>
<!-- both the correct and deprecated locales need to be present for
them to be recognized on all devices -->
<issue id="LocaleFolder" severity="error">
<ignore path="src/main/res/values-he"/>
<ignore path="src/main/res/values-id"/>
</issue>
<issue id="SetWorldReadable" severity="error">
<ignore path="src/main/java/org/fdroid/fdroid/installer/ApkFileProvider.java"/>
</issue>
<issue id="ProtectedPermissions" severity="error">
<ignore path="src/debug/AndroidManifest.xml"/>
<ignore path="src/full/AndroidManifest.xml"/>
</issue>
<!-- these should be fixed, but it'll be a chunk of work -->
<issue id="SetTextI18n" severity="error">
<ignore path="src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java"/>
<ignore path="src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java"/>
</issue>
</lint>

View File

@ -4,16 +4,12 @@
-keep class org.fdroid.fdroid.** {*;}
-dontskipnonpubliclibraryclassmembers
-dontwarn android.test.**
-dontwarn com.android.support.test.**
-dontwarn javax.naming.**
-dontwarn org.slf4j.**
-dontnote org.apache.http.**
-dontnote android.net.http.**
-dontnote android.support.**
-dontnote **ILicensingService
# Needed for espresso https://stackoverflow.com/a/21706087
-dontwarn org.xmlpull.v1.**
# StrongHttpsClient and its support classes are totally unused, so the
# ch.boye.httpclientandroidlib.** classes are also unneeded
-dontwarn info.guardianproject.netcipher.client.**
@ -23,7 +19,7 @@
# removed, proguard will strip classes which are required, which may result in
# crashes.
-keep class kellinwood.security.zipsigner.** {*;}
-keep class org.bouncycastle.** {*;}
-keep class org.spongycastle.** {*;}
# This keeps class members used for SystemInstaller IPC.
# Reference: https://gitlab.com/fdroid/fdroidclient/issues/79
@ -31,17 +27,20 @@
public *;
}
-keepattributes *Annotation*,EnclosingMethod,Signature
-keepnames class com.fasterxml.jackson.** { *; }
-dontwarn com.fasterxml.jackson.databind.ext.**
-keep class org.codehaus.** { *; }
-keepclassmembers public final enum org.codehaus.jackson.annotate.JsonAutoDetect$Visibility {
public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
-keep public class your.class.** {
*;
# Samsung Android 4.2 bug
# https://code.google.com/p/android/issues/detail?id=78377
-keepnames class !android.support.v7.internal.view.menu.**, ** {*;}
-keep public class android.support.v7.widget.** {*;}
-keep public class android.support.v7.internal.widget.** {*;}
-keep public class * extends android.support.v4.view.ActionProvider {
public <init>(android.content.Context);
}
# This is necessary so that RemoteWorkManager can be initialized (also marked with @Keep)
-keep class androidx.work.multiprocess.RemoteWorkManagerClient {
public <init>(...);
}
# The rxjava library depends on sun.misc.Unsafe, which is unavailable on Android
# The rxjava team is aware of this, and mention in the docs that they only use
# the unsafe functionality if the platform supports it.
# - https://github.com/ReactiveX/RxJava/issues/1415#issuecomment-48390883
# - https://github.com/ReactiveX/RxJava/blob/1.x/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java#L23
-dontwarn rx.internal.util.**

View File

@ -1,24 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.fdroid.fdroid.tests"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk tools:overrideLibrary="android_libs.ub_uiautomator" />
package="org.fdroid.fdroid.tests"
android:versionCode="1"
android:versionName="1.0">
<!-- We add an application tag here just so that we can indicate that
this package needs to link against the android.test library,
which is needed when building test cases. -->
<application>
<uses-library
android:name="android.test.runner"
android:required="false" />
<uses-library android:name="android.test.runner"/>
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--
This declares that this application uses the instrumentation test runner targeting
the package of org.fdroid.fdroid. To run the tests use the command:
"adb shell am instrument -w org.fdroid.fdroid.tests/android.test.InstrumentationTestRunner"
-->
<instrumentation android:name="com.zutubi.android.junitreport.JUnitReportTestRunner"
android:targetPackage="org.fdroid.fdroid"
android:label="Tests for org.fdroid.fdroid" />
</manifest>

View File

@ -1,125 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<fdroid>
<repo name="F-Droid" icon="fdroid-icon.png" maxage="14"
pubkey="3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"
timestamp="1467169032" url="http://f-droid.org/repo" version="16">
<description>
This is just a test of the extended permissions attributes.
</description>
</repo>
<application id="urzip.at.or.at.urzip">
<id>at.bitfire.davdroid</id>
<added>2013-10-13</added>
<lastupdated>2016-06-26</lastupdated>
<name>DAVdroid</name>
<summary>Contacts and Calendar sync</summary>
<icon>at.bitfire.davdroid.107.png</icon>
<desc>apk generated from urzip to test extended permissions</desc>
<license>GPLv3</license>
<categories>Internet</categories>
<category>Internet</category>
<web>https://davdroid.bitfire.at/</web>
<source>https://davdroid.bitfire.at/source/</source>
<tracker>https://davdroid.bitfire.at/forums/</tracker>
<changelog>https://gitlab.com/bitfireAT/davdroid/tags</changelog>
<donate>https://davdroid.bitfire.at/donate/</donate>
<bitcoin>1KSCy7RHztKuhW9fLLaUYqdwdC2iwbejZU</bitcoin>
<flattr>2100160</flattr>
<marketversion>1.1.1.2</marketversion>
<marketvercode>107</marketvercode>
<package>
<version>1.3.2-FAKE</version>
<versioncode>117</versioncode>
<apkname>org.fdroid.extendedpermissionstest.apk</apkname>
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3298864</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-09-22</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,READ_SYNC_STATS
</permissions>
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission-sdk-23 name="android.permission.CAMERA" />
<uses-permission-sdk-23 name="android.permission.CALL_PHONE" maxSdkVersion="23" />
</package>
<package>
<version>1.3.1-ose</version>
<versioncode>116</versioncode>
<apkname>at.bitfire.davdroid_116.apk</apkname>
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3298864</size>
<sdkver>14</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-09-21</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,org.dmfs.permission.READ_TASKS,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,org.dmfs.permission.WRITE_TASKS
</permissions>
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
</package>
<package>
<version>1.1.1.2</version>
<versioncode>107</versioncode>
<apkname>at.bitfire.davdroid_107.apk</apkname>
<srcname>at.bitfire.davdroid_107_src.tar.gz</srcname>
<hash type="sha256">9a616f2e97bf8cf012baf896f95667dea4e3ce3252b31c5715073638a9fcc3d4
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3134363</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-06-26</added>
<permissions>
org.dmfs.permission.READ_TASKS,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
</permissions>
</package>
<package>
<version>1.1.1.1</version>
<versioncode>105</versioncode>
<apkname>at.bitfire.davdroid_105.apk</apkname>
<srcname>at.bitfire.davdroid_105_src.tar.gz</srcname>
<hash type="sha256">4a0408c61536a1cc1028cea4273adbde2e57dfa2b12d93c3b52f4c3d095e2849
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3131567</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-06-24</added>
<permissions>
org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
</permissions>
</package>
<package>
<version>1.1.1</version>
<versioncode>104</versioncode>
<apkname>at.bitfire.davdroid_104.apk</apkname>
<srcname>at.bitfire.davdroid_104_src.tar.gz</srcname>
<hash type="sha256">09ba34996177efe8b1498a93fe6521ab84efab3bccb0f42449116e80b59e5b56
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3131367</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-06-22</added>
<permissions>
org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
</permissions>
</package>
</application>
</fdroid>

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,247 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.test;
import android.annotation.TargetApi;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.database.DatabaseUtils;
import android.os.Build;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
import java.io.File;
/**
* This test case class provides a framework for testing a single
* {@link ContentProvider} and for testing your app code with an
* isolated content provider. Instead of using the system map of
* providers that is based on the manifests of other applications, the test
* case creates its own internal map. It then uses this map to resolve providers
* given an authority. This allows you to inject test providers and to null out
* providers that you do not want to use.
* <p>
* This test case also sets up the following mock objects:
* </p>
* <ul>
* <li>
* An {@link android.test.IsolatedContext} that stubs out Context methods that might
* affect the rest of the running system, while allowing tests to do real file and
* database work.
* </li>
* <li>
* A {@link android.test.mock.MockContentResolver} that provides the functionality of a
* regular content resolver, but uses {@link IsolatedContext}. It stubs out
* {@link ContentResolver#notifyChange(Uri, ContentObserver, boolean)} to
* prevent the test from affecting the running system.
* </li>
* <li>
* An instance of the provider under test, running in an {@link IsolatedContext}.
* </li>
* </ul>
* <p>
* This framework is set up automatically by the base class' {@link #setUp()} method. If you
* override this method, you must call the super method as the first statement in
* your override.
* </p>
* <p>
* In order for their tests to be run, concrete subclasses must provide their own
* constructor with no arguments. This constructor must call
* {@link #ProviderTestCase2MockContext(Class, String)} as its first operation.
* </p>
* For more information on content provider testing, please see
* <a href="{@docRoot}tools/testing/contentprovider_testing.html">Content Provider Testing</a>.
*/
public abstract class ProviderTestCase2MockContext<T extends ContentProvider> extends AndroidTestCase {
Class<T> mProviderClass;
String mProviderAuthority;
private IsolatedContext mProviderContext;
private MockContentResolver mResolver;
private class MockContext2 extends MockContext {
@Override
public Resources getResources() {
return getContext().getResources();
}
@Override
public File getDir(String name, int mode) {
// name the directory so the directory will be separated from
// one created through the regular Context
return getContext().getDir("mockcontext2_" + name, mode);
}
@Override
public Context getApplicationContext() {
return this;
}
}
/**
* Constructor.
*
* @param providerClass The class name of the provider under test
* @param providerAuthority The provider's authority string
*/
public ProviderTestCase2MockContext(Class<T> providerClass, String providerAuthority) {
mProviderClass = providerClass;
mProviderAuthority = providerAuthority;
}
private T mProvider;
/**
* Returns the content provider created by this class in the {@link #setUp()} method.
* @return T An instance of the provider class given as a parameter to the test case class.
*/
public T getProvider() {
return mProvider;
}
protected abstract Context createMockContext(Context delegate);
/**
* Sets up the environment for the test fixture.
* <p>
* Creates a new
* {@link android.test.mock.MockContentResolver}, a new IsolatedContext
* that isolates the provider's file operations, and a new instance of
* the provider under test within the isolated environment.
* </p>
*
* @throws Exception
*/
@Override
protected void setUp() throws Exception {
super.setUp();
mResolver = new MockContentResolver();
final String filenamePrefix = "test.";
final RenamingDelegatingContext targetContextWrapper = new
RenamingDelegatingContext(
createMockContext(new MockContext2()), // The context that most methods are delegated to
getContext(), // The context that file methods are delegated to
filenamePrefix);
mProviderContext = new IsolatedContext(mResolver, new ContextWrapper(targetContextWrapper) {
// The FDroidProvider class needs access to an application context in order to initialize
// the singleton DBHelper instance.
@Override
public Context getApplicationContext() {
return targetContextWrapper;
}
});
mProvider = mProviderClass.newInstance();
mProvider.attachInfo(mProviderContext, null);
assertNotNull(mProvider);
mResolver.addProvider(mProviderAuthority, getProvider());
}
/**
* Tears down the environment for the test fixture.
* <p>
* Calls {@link android.content.ContentProvider#shutdown()} on the
* {@link android.content.ContentProvider} represented by mProvider.
*/
@Override
protected void tearDown() throws Exception {
shutdownProvider();
super.tearDown();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void shutdownProvider() {
if (Build.VERSION.SDK_INT >= 11) {
mProvider.shutdown();
}
}
/**
* Gets the {@link MockContentResolver} created by this class during initialization. You
* must use the methods of this resolver to access the provider under test.
*
* @return A {@link MockContentResolver} instance.
*/
public MockContentResolver getMockContentResolver() {
return mResolver;
}
/**
* Gets the {@link IsolatedContext} created by this class during initialization.
* @return The {@link IsolatedContext} instance
*/
public IsolatedContext getMockContext() {
return mProviderContext;
}
/**
* <p>
* Creates a new content provider of the same type as that passed to the test case class,
* with an authority name set to the authority parameter, and using an SQLite database as
* the underlying data source. The SQL statement parameter is used to create the database.
* This method also creates a new {@link MockContentResolver} and adds the provider to it.
* </p>
* <p>
* Both the new provider and the new resolver are put into an {@link IsolatedContext}
* that uses the targetContext parameter for file operations and a {@link MockContext}
* for everything else. The IsolatedContext prepends the filenamePrefix parameter to
* file, database, and directory names.
* </p>
* <p>
* This is a convenience method for creating a "mock" provider that can contain test data.
* </p>
*
* @param targetContext The context to use as the basis of the IsolatedContext
* @param filenamePrefix A string that is prepended to file, database, and directory names
* @param providerClass The type of the provider being tested
* @param authority The authority string to associated with the test provider
* @param databaseName The name assigned to the database
* @param databaseVersion The version assigned to the database
* @param sql A string containing the SQL statements that are needed to create the desired
* database and its tables. The format is the same as that generated by the
* <a href="http://www.sqlite.org/sqlite.html">sqlite3</a> tool's <code>.dump</code> command.
* @return ContentResolver A new {@link MockContentResolver} linked to the provider
*
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T extends ContentProvider> ContentResolver newResolverWithContentProviderFromSql(
Context targetContext, String filenamePrefix, Class<T> providerClass, String authority,
String databaseName, int databaseVersion, String sql)
throws IllegalAccessException, InstantiationException {
MockContentResolver resolver = new MockContentResolver();
RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(
new MockContext(), // The context that most methods are delegated to
targetContext, // The context that file methods are delegated to
filenamePrefix);
Context context = new IsolatedContext(resolver, targetContextWrapper);
DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql);
T provider = providerClass.newInstance();
provider.attachInfo(context, null);
resolver.addProvider(authority, provider);
return resolver;
}
}

View File

@ -0,0 +1,19 @@
package mock;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
public class MockApplicationInfo extends ApplicationInfo {
private final PackageInfo info;
public MockApplicationInfo(PackageInfo info) {
this.info = info;
}
@Override
public CharSequence loadLabel(PackageManager pm) {
return "Mock app: " + info.packageName;
}
}

View File

@ -0,0 +1,27 @@
package mock;
import android.content.Context;
import org.fdroid.fdroid.R;
public class MockCategoryResources extends MockFDroidResources {
public MockCategoryResources(Context getStringDelegatingContext) {
super(getStringDelegatingContext);
}
@Override
public String getString(int id) {
switch (id) {
case R.string.category_All:
return "All";
case R.string.category_Recently_Updated:
return "Recently Updated";
case R.string.category_Whats_New:
return "Whats New";
default:
return "";
}
}
}

View File

@ -0,0 +1,14 @@
package mock;
/**
* As more components are required to test different parts of F-Droid, we can
* create them and add them here (and accessors to the parent class).
*/
public class MockContextEmptyComponents extends MockContextSwappableComponents {
public MockContextEmptyComponents() {
setPackageManager(new MockEmptyPackageManager());
setResources(new MockEmptyResources());
}
}

View File

@ -0,0 +1,43 @@
package mock;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
public class MockContextSwappableComponents extends MockContext {
private PackageManager packageManager;
private Resources resources;
private MockContentResolver contentResolver;
public MockContextSwappableComponents setPackageManager(PackageManager pm) {
packageManager = pm;
return this;
}
public MockContextSwappableComponents setResources(Resources resources) {
this.resources = resources;
return this;
}
public MockContextSwappableComponents setContentResolver(MockContentResolver contentResolver) {
this.contentResolver = contentResolver;
return this;
}
@Override
public PackageManager getPackageManager() {
return packageManager;
}
@Override
public Resources getResources() {
return resources;
}
@Override
public MockContentResolver getContentResolver() {
return contentResolver;
}
}

View File

@ -0,0 +1,16 @@
package mock;
import android.content.pm.PackageInfo;
import android.test.mock.MockPackageManager;
import java.util.ArrayList;
import java.util.List;
public class MockEmptyPackageManager extends MockPackageManager {
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
return new ArrayList<>();
}
}

View File

@ -0,0 +1,12 @@
package mock;
import android.test.mock.MockResources;
public class MockEmptyResources extends MockResources {
@Override
public String getString(int id) {
return "";
}
}

View File

@ -0,0 +1,37 @@
package mock;
import android.content.Context;
import android.test.mock.MockResources;
import org.fdroid.fdroid.R;
public class MockFDroidResources extends MockResources {
private Context getStringDelegatingContext;
public MockFDroidResources(Context getStringDelegatingContext) {
this.getStringDelegatingContext = getStringDelegatingContext;
}
@Override
public String getString(int id) {
return getStringDelegatingContext.getString(id);
}
@Override
public int getInteger(int id) {
switch (id) {
case R.integer.fdroid_repo_inuse:
return 1;
case R.integer.fdroid_archive_inuse:
return 0;
case R.integer.fdroid_repo_priority:
return 10;
case R.integer.fdroid_archive_priority:
return 20;
default:
return 0;
}
}
}

View File

@ -0,0 +1,59 @@
package mock;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.test.mock.MockPackageManager;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MockInstallablePackageManager extends MockPackageManager {
private List<PackageInfo> info = new ArrayList<>();
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
return info;
}
@Override
public PackageInfo getPackageInfo(String id, int flags) {
for (PackageInfo i : info) {
if (i.packageName.equals(id)) {
return i;
}
}
return null;
}
public void install(String id, int version, String versionName) {
PackageInfo existing = getPackageInfo(id, 0);
if (existing != null) {
existing.versionCode = version;
existing.versionName = versionName;
} else {
PackageInfo p = new PackageInfo();
p.packageName = id;
p.versionCode = version;
p.versionName = versionName;
info.add(p);
}
}
@Override
public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException {
return new MockApplicationInfo(getPackageInfo(packageName, 0));
}
public void remove(String id) {
for (Iterator<PackageInfo> it = info.iterator(); it.hasNext();) {
PackageInfo info = it.next();
if (info.packageName.equals(id)) {
it.remove();
return;
}
}
}
}

View File

@ -0,0 +1,216 @@
package org.fdroid.fdroid;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.mock.MockApk;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class ApkProviderHelperTest extends BaseApkProviderTest {
public void testKnownApks() {
for (int i = 0; i < 7; i++) {
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
}
for (int i = 0; i < 9; i++) {
TestUtils.insertApk(this, "org.example", i);
}
for (int i = 0; i < 3; i++) {
TestUtils.insertApk(this, "com.example", i);
}
TestUtils.insertApk(this, "com.apk.thingo", 1);
Apk[] known = {
new MockApk("org.fdroid.fdroid", 1),
new MockApk("org.fdroid.fdroid", 3),
new MockApk("org.fdroid.fdroid", 5),
new MockApk("com.example", 1),
new MockApk("com.example", 2),
};
Apk[] unknown = {
new MockApk("org.fdroid.fdroid", 7),
new MockApk("org.fdroid.fdroid", 9),
new MockApk("org.fdroid.fdroid", 11),
new MockApk("org.fdroid.fdroid", 13),
new MockApk("com.example", 3),
new MockApk("com.example", 4),
new MockApk("com.example", 5),
new MockApk("info.example", 1),
new MockApk("info.example", 2),
};
List<Apk> apksToCheck = new ArrayList<>(known.length + unknown.length);
Collections.addAll(apksToCheck, known);
Collections.addAll(apksToCheck, unknown);
String[] projection = {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.VERSION_CODE,
};
List<Apk> knownApks = ApkProvider.Helper.knownApks(getMockContext(), apksToCheck, projection);
assertResultCount(known.length, knownApks);
for (Apk knownApk : knownApks) {
assertContains(knownApks, knownApk);
}
}
public void testFindByApp() {
for (int i = 0; i < 7; i++) {
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
}
for (int i = 0; i < 9; i++) {
TestUtils.insertApk(this, "org.example", i);
}
for (int i = 0; i < 3; i++) {
TestUtils.insertApk(this, "com.example", i);
}
TestUtils.insertApk(this, "com.apk.thingo", 1);
assertTotalApkCount(7 + 9 + 3 + 1);
List<Apk> fdroidApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.fdroid.fdroid");
assertResultCount(7, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
List<Apk> exampleApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.example");
assertResultCount(9, exampleApks);
assertBelongsToApp(exampleApks, "org.example");
List<Apk> exampleApks2 = ApkProvider.Helper.findByPackageName(getMockContext(), "com.example");
assertResultCount(3, exampleApks2);
assertBelongsToApp(exampleApks2, "com.example");
List<Apk> thingoApks = ApkProvider.Helper.findByPackageName(getMockContext(), "com.apk.thingo");
assertResultCount(1, thingoApks);
assertBelongsToApp(thingoApks, "com.apk.thingo");
}
public void testUpdate() {
Uri apkUri = TestUtils.insertApk(this, "com.example", 10);
String[] allFields = ApkProvider.DataColumns.ALL;
Cursor cursor = getMockContentResolver().query(apkUri, allFields, null, null, null);
assertResultCount(1, cursor);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
assertEquals("com.example", apk.packageName);
assertEquals(10, apk.vercode);
assertNull(apk.features);
assertNull(apk.added);
assertNull(apk.hashType);
apk.features = Utils.CommaSeparatedList.make("one,two,three");
long dateTimestamp = System.currentTimeMillis();
apk.added = new Date(dateTimestamp);
apk.hashType = "i'm a hash type";
ApkProvider.Helper.update(getMockContext(), apk);
// Should not have inserted anything else, just updated the already existing apk.
Cursor allCursor = getMockContentResolver().query(ApkProvider.getContentUri(), allFields, null, null, null);
assertResultCount(1, allCursor);
allCursor.close();
Cursor updatedCursor = getMockContentResolver().query(apkUri, allFields, null, null, null);
assertResultCount(1, updatedCursor);
updatedCursor.moveToFirst();
Apk updatedApk = new Apk(updatedCursor);
updatedCursor.close();
assertEquals("com.example", updatedApk.packageName);
assertEquals(10, updatedApk.vercode);
assertNotNull(updatedApk.features);
assertNotNull(updatedApk.added);
assertNotNull(updatedApk.hashType);
assertEquals("one,two,three", updatedApk.features.toString());
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
assertEquals("i'm a hash type", updatedApk.hashType);
}
public void testFind() {
// Insert some random apks either side of the "com.example", so that
// the Helper.find() method doesn't stumble upon the app we are interested
// in by shear dumb luck...
for (int i = 0; i < 10; i++) {
TestUtils.insertApk(this, "org.fdroid.apk." + i, i);
}
ContentValues values = new ContentValues();
values.put(ApkProvider.DataColumns.VERSION, "v1.1");
values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy");
values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type");
TestUtils.insertApk(this, "com.example", 11, values);
// ...and a few more for good measure...
for (int i = 15; i < 20; i++) {
TestUtils.insertApk(this, "com.other.thing." + i, i);
}
Apk apk = ApkProvider.Helper.find(getMockContext(), "com.example", 11);
assertNotNull(apk);
// The find() method populates ALL fields if you don't specify any,
// so we expect to find each of the ones we inserted above...
assertEquals("com.example", apk.packageName);
assertEquals(11, apk.vercode);
assertEquals("v1.1", apk.version);
assertEquals("xxxxyyyy", apk.hash);
assertEquals("a hash type", apk.hashType);
String[] projection = {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.HASH,
};
Apk apkLessFields = ApkProvider.Helper.find(getMockContext(), "com.example", 11, projection);
assertNotNull(apkLessFields);
assertEquals("com.example", apkLessFields.packageName);
assertEquals("xxxxyyyy", apkLessFields.hash);
// Didn't ask for these fields, so should be their default values...
assertNull(apkLessFields.hashType);
assertNull(apkLessFields.version);
assertEquals(0, apkLessFields.vercode);
Apk notFound = ApkProvider.Helper.find(getMockContext(), "com.doesnt.exist", 1000);
assertNull(notFound);
}
}

View File

@ -0,0 +1,337 @@
package org.fdroid.fdroid;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockApp;
import org.fdroid.fdroid.mock.MockRepo;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class ApkProviderTest extends BaseApkProviderTest {
/**
* I want to test the protected {@link org.fdroid.fdroid.data.ApkProvider#getContentUri(java.util.List)}
* method, but don't want to make it public. This exposes it.
*/
private static class PublicApkProvider extends ApkProvider {
public static final int MAX_APKS_TO_QUERY = ApkProvider.MAX_APKS_TO_QUERY;
public static Uri getContentUri(List<Apk> apks) {
return ApkProvider.getContentUri(apks);
}
}
public void testUris() {
assertInvalidUri(ApkProvider.getAuthority());
assertInvalidUri(RepoProvider.getContentUri());
List<Apk> apks = new ArrayList<>(3);
for (int i = 0; i < 10; i++) {
apks.add(new MockApk("com.example." + i, i));
}
assertValidUri(ApkProvider.getContentUri());
assertValidUri(ApkProvider.getAppUri("org.fdroid.fdroid"));
assertValidUri(ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100)));
assertValidUri(ApkProvider.getContentUri());
assertValidUri(PublicApkProvider.getContentUri(apks));
assertValidUri(ApkProvider.getContentUri("org.fdroid.fdroid", 100));
assertValidUri(ApkProvider.getRepoUri(1000));
List<Apk> manyApks = new ArrayList<>(PublicApkProvider.MAX_APKS_TO_QUERY - 5);
for (int i = 0; i < PublicApkProvider.MAX_APKS_TO_QUERY - 1; i++) {
manyApks.add(new MockApk("com.example." + i, i));
}
assertValidUri(PublicApkProvider.getContentUri(manyApks));
manyApks.add(new MockApk("org.fdroid.fdroid.1", 1));
manyApks.add(new MockApk("org.fdroid.fdroid.2", 2));
try {
// Technically, it is a valid URI, because it doesn't
// throw an UnsupportedOperationException. However it
// is still not okay (we run out of bindable parameters
// in the sqlite query.
assertValidUri(PublicApkProvider.getContentUri(manyApks));
fail();
} catch (IllegalArgumentException e) {
// This is the expected error behaviour.
} catch (Exception e) {
fail();
}
}
public void testAppApks() {
for (int i = 1; i <= 10; i++) {
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
TestUtils.insertApk(this, "com.example", i);
}
assertTotalApkCount(20);
Cursor fdroidApks = getMockContentResolver().query(
ApkProvider.getAppUri("org.fdroid.fdroid"),
getMinimalProjection(),
null, null, null);
assertResultCount(10, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
fdroidApks.close();
Cursor exampleApks = getMockContentResolver().query(
ApkProvider.getAppUri("com.example"),
getMinimalProjection(),
null, null, null);
assertResultCount(10, exampleApks);
assertBelongsToApp(exampleApks, "com.example");
exampleApks.close();
ApkProvider.Helper.deleteApksByApp(getMockContext(), new MockApp("com.example"));
Cursor all = queryAllApks();
assertResultCount(10, all);
assertBelongsToApp(all, "org.fdroid.fdroid");
all.close();
}
public void testInvalidUpdateUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
List<Apk> apks = new ArrayList<>();
apks.add(apk);
assertCantUpdate(ApkProvider.getContentUri());
assertCantUpdate(ApkProvider.getAppUri("org.fdroid.fdroid"));
assertCantUpdate(ApkProvider.getRepoUri(1));
assertCantUpdate(PublicApkProvider.getContentUri(apks));
assertCantUpdate(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
// The only valid ones are:
// ApkProvider.getContentUri(apk)
// ApkProvider.getContentUri(id, version)
// which are tested elsewhere.
}
public void testDeleteArbitraryApks() {
Apk one = insertApkForRepo("com.example.one", 1, 10);
Apk two = insertApkForRepo("com.example.two", 1, 10);
Apk three = insertApkForRepo("com.example.three", 1, 10);
Apk four = insertApkForRepo("com.example.four", 1, 10);
Apk five = insertApkForRepo("com.example.five", 1, 10);
assertTotalApkCount(5);
assertEquals("com.example.one", one.packageName);
assertEquals("com.example.two", two.packageName);
assertEquals("com.example.five", five.packageName);
String[] expectedIds = {
"com.example.one",
"com.example.two",
"com.example.three",
"com.example.four",
"com.example.five",
};
List<Apk> all = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualIds = new ArrayList<>();
for (Apk apk : all) {
actualIds.add(apk.packageName);
}
TestUtils.assertContainsOnly(actualIds, expectedIds);
List<Apk> toDelete = new ArrayList<>(3);
toDelete.add(two);
toDelete.add(three);
toDelete.add(four);
ApkProvider.Helper.deleteApks(getSwappableContext(), toDelete);
assertTotalApkCount(2);
List<Apk> allRemaining = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualRemainingIds = new ArrayList<>();
for (Apk apk : allRemaining) {
actualRemainingIds.add(apk.packageName);
}
String[] expectedRemainingIds = {
"com.example.one",
"com.example.five",
};
TestUtils.assertContainsOnly(actualRemainingIds, expectedRemainingIds);
}
public void testInvalidDeleteUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
assertCantDelete(ApkProvider.getContentUri());
assertCantDelete(ApkProvider.getContentUri("org.fdroid.fdroid", 10));
assertCantDelete(ApkProvider.getContentUri(apk));
assertCantDelete(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
}
private static final long REPO_KEEP = 1;
private static final long REPO_DELETE = 2;
public void testRepoApks() {
// Insert apks into two repos, one of which we will later purge the
// the apks from.
for (int i = 1; i <= 5; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP);
insertApkForRepo("com.example." + i, 1, REPO_DELETE);
}
for (int i = 6; i <= 10; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE);
insertApkForRepo("com.example." + i, 1, REPO_KEEP);
}
assertTotalApkCount(20);
Cursor cursor = getMockContentResolver().query(
ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
assertResultCount(10, cursor);
assertBelongsToRepo(cursor, REPO_DELETE);
cursor.close();
int count = ApkProvider.Helper.deleteApksByRepo(getMockContext(), new MockRepo(REPO_DELETE));
assertEquals(10, count);
assertTotalApkCount(10);
cursor = getMockContentResolver().query(
ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
assertResultCount(0, cursor);
cursor.close();
// The only remaining apks should be those from REPO_KEEP.
assertBelongsToRepo(queryAllApks(), REPO_KEEP);
}
public void testQuery() {
Cursor cursor = queryAllApks();
assertNotNull(cursor);
cursor.close();
}
public void testInsert() {
// Start with an empty database...
Cursor cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
Apk apk = new MockApk("org.fdroid.fdroid", 13);
// Insert a new record...
Uri newUri = TestUtils.insertApk(this, apk.packageName, apk.vercode);
assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
// We intentionally throw an IllegalArgumentException if you haven't
// yet called cursor.move*()...
try {
new Apk(cursor);
fail();
} catch (IllegalArgumentException e) {
// Success!
} catch (Exception e) {
fail();
}
// And now we should be able to recover these values from the apk
// value object (because the queryAllApks() helper asks for VERSION_CODE and
// PACKAGE_NAME.
cursor.moveToFirst();
Apk toCheck = new Apk(cursor);
cursor.close();
assertEquals("org.fdroid.fdroid", toCheck.packageName);
assertEquals(13, toCheck.vercode);
}
public void testCount() {
String[] projectionFields = getMinimalProjection();
String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT};
for (int i = 0; i < 13; i++) {
TestUtils.insertApk(this, "com.example", i);
}
Uri all = ApkProvider.getContentUri();
Cursor allWithFields = getMockContentResolver().query(all, projectionFields, null, null, null);
Cursor allWithCount = getMockContentResolver().query(all, projectionCount, null, null, null);
assertResultCount(13, allWithFields);
allWithFields.close();
assertResultCount(1, allWithCount);
allWithCount.moveToFirst();
int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT);
assertEquals(13, allWithCount.getInt(countColumn));
allWithCount.close();
}
public void testInsertWithExtraFields() {
assertResultCount(0, queryAllApks());
String[] repoFields = new String[] {
RepoProvider.DataColumns.DESCRIPTION,
RepoProvider.DataColumns.ADDRESS,
RepoProvider.DataColumns.FINGERPRINT,
RepoProvider.DataColumns.NAME,
RepoProvider.DataColumns.PUBLIC_KEY,
};
for (String field : repoFields) {
ContentValues invalidRepo = new ContentValues();
invalidRepo.put(field, "Test data");
try {
TestUtils.insertApk(this, "org.fdroid.fdroid", 10, invalidRepo);
fail();
} catch (IllegalArgumentException e) {
} catch (Exception e) {
fail();
}
assertResultCount(0, queryAllApks());
}
ContentValues values = new ContentValues();
values.put(ApkProvider.DataColumns.REPO_ID, 10);
values.put(ApkProvider.DataColumns.REPO_ADDRESS, "http://example.com");
values.put(ApkProvider.DataColumns.REPO_VERSION, 3);
values.put(ApkProvider.DataColumns.FEATURES, "Some features");
Uri uri = TestUtils.insertApk(this, "com.example.com", 1, values);
assertResultCount(1, queryAllApks());
String[] projections = ApkProvider.DataColumns.ALL;
Cursor cursor = getMockContentResolver().query(uri, projections, null, null, null);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
// These should have quietly been dropped when we tried to save them,
// because the provider only knows how to query them (not update them).
assertEquals(null, apk.repoAddress);
assertEquals(0, apk.repoVersion);
// But this should have saved correctly...
assertEquals("Some features", apk.features.toString());
assertEquals("com.example.com", apk.packageName);
assertEquals(1, apk.vercode);
assertEquals(10, apk.repo);
}
}

View File

@ -0,0 +1,387 @@
package org.fdroid.fdroid;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
import java.util.ArrayList;
import java.util.List;
import mock.MockCategoryResources;
import mock.MockContextSwappableComponents;
import mock.MockInstallablePackageManager;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class AppProviderTest extends FDroidProviderTest<AppProvider> {
public AppProviderTest() {
super(AppProvider.class, AppProvider.getAuthority());
}
@Override
public void setUp() throws Exception {
super.setUp();
getSwappableContext().setResources(new MockCategoryResources(getContext()));
}
@Override
protected Resources getMockResources() {
return new MockCategoryResources(getContext());
}
@Override
protected String[] getMinimalProjection() {
return new String[] {
AppProvider.DataColumns.PACKAGE_NAME,
AppProvider.DataColumns.NAME,
};
}
/**
* Although this doesn't directly relate to the AppProvider, it is here because
* the AppProvider used to stumble across this bug when asking for installed apps,
* and the device had over 1000 apps installed.
*/
public void testMaxSqliteParams() {
MockInstallablePackageManager pm = new MockInstallablePackageManager();
getSwappableContext().setPackageManager(pm);
insertApp("com.example.app1", "App 1");
insertApp("com.example.app100", "App 100");
insertApp("com.example.app1000", "App 1000");
for (int i = 0; i < 50; i++) {
pm.install("com.example.app" + i, 1, "v" + 1);
}
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(1, AppProvider.getInstalledUri());
for (int i = 50; i < 500; i++) {
pm.install("com.example.app" + i, 1, "v" + 1);
}
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(2, AppProvider.getInstalledUri());
for (int i = 500; i < 1100; i++) {
pm.install("com.example.app" + i, 1, "v" + 1);
}
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(3, AppProvider.getInstalledUri());
}
public void testCantFindApp() {
assertNull(AppProvider.Helper.findByPackageName(getMockContentResolver(), "com.example.doesnt-exist"));
}
public void testUris() {
assertInvalidUri(AppProvider.getAuthority());
assertInvalidUri(ApkProvider.getContentUri());
assertValidUri(AppProvider.getContentUri(), "content://org.fdroid.fdroid.data.AppProvider");
assertValidUri(AppProvider.getSearchUri("'searching!'"), "content://org.fdroid.fdroid.data.AppProvider/search/'searching!'");
assertValidUri(AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F");
assertValidUri(AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider");
assertValidUri(AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider");
assertValidUri(AppProvider.getNoApksUri());
assertValidUri(AppProvider.getInstalledUri());
assertValidUri(AppProvider.getCanUpdateUri());
App app = new App();
app.packageName = "org.fdroid.fdroid";
List<App> apps = new ArrayList<>(1);
apps.add(app);
assertValidUri(AppProvider.getContentUri(app));
assertValidUri(AppProvider.getContentUri(apps));
assertValidUri(AppProvider.getContentUri("org.fdroid.fdroid"));
}
public void testQuery() {
Cursor cursor = queryAllApps();
assertNotNull(cursor);
cursor.close();
}
private void insertApps(int count) {
for (int i = 0; i < count; i++) {
insertApp("com.example.test." + i, "Test app " + i);
}
}
private void insertAndInstallApp(
MockInstallablePackageManager packageManager,
String id, int installedVercode, int suggestedVercode,
boolean ignoreAll, int ignoreVercode) {
ContentValues values = new ContentValues(3);
values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVercode);
values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAll);
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode);
insertApp(id, "App: " + id, values);
TestUtils.installAndBroadcast(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode);
}
public void testCanUpdate() {
MockContextSwappableComponents c = getSwappableContext();
MockInstallablePackageManager pm = new MockInstallablePackageManager();
c.setPackageManager(pm);
insertApp("not installed", "not installed");
insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0);
insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0);
insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0);
insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10);
insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5);
insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0);
insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0);
insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10);
insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8);
ContentResolver r = getMockContentResolver();
// Can't "update", although can "install"...
App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed");
assertFalse(notInstalled.canAndWantToUpdate());
App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available");
App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore");
App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all");
App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest");
App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old");
assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate());
assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate());
assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate());
assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate());
assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate());
App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore");
App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all");
App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest");
App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest");
assertTrue(installedOldNoIgnore.canAndWantToUpdate());
assertFalse(installedOldIgnoreAll.canAndWantToUpdate());
assertFalse(installedOldIgnoreLatest.canAndWantToUpdate());
assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate());
Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null);
canUpdateCursor.moveToFirst();
List<String> canUpdateIds = new ArrayList<>(canUpdateCursor.getCount());
while (!canUpdateCursor.isAfterLast()) {
canUpdateIds.add(new App(canUpdateCursor).packageName);
canUpdateCursor.moveToNext();
}
canUpdateCursor.close();
String[] expectedUpdateableIds = {
"installed, old version, no ignore",
"installed, old version, ignore newer, but not latest",
};
TestUtils.assertContainsOnly(expectedUpdateableIds, canUpdateIds);
}
public void testIgnored() {
MockInstallablePackageManager pm = new MockInstallablePackageManager();
getSwappableContext().setPackageManager(pm);
insertApp("not installed", "not installed");
insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0);
insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0);
insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0);
insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10);
insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5);
insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0);
insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0);
insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10);
insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8);
assertResultCount(10, AppProvider.getContentUri());
String[] projection = {AppProvider.DataColumns.PACKAGE_NAME};
List<App> ignoredApps = AppProvider.Helper.findIgnored(getMockContext(), projection);
String[] expectedIgnored = {
"installed, already latest, ignore all",
"installed, already latest, ignore latest",
// NOT "installed, already latest, ignore old" - because it
// is should only ignore if "ignored version" is >= suggested
"installed, old version, ignore all",
"installed, old version, ignore latest",
// NOT "installed, old version, ignore newer, but not latest"
// for the same reason as above.
};
assertContainsOnlyIds(ignoredApps, expectedIgnored);
}
private void assertContainsOnlyIds(List<App> actualApps, String[] expectedIds) {
List<String> actualIds = new ArrayList<>(actualApps.size());
for (App app : actualApps) {
actualIds.add(app.packageName);
}
TestUtils.assertContainsOnly(actualIds, expectedIds);
}
public void testInstalled() {
MockInstallablePackageManager pm = new MockInstallablePackageManager();
getSwappableContext().setPackageManager(pm);
insertApps(100);
assertResultCount(100, AppProvider.getContentUri());
assertResultCount(0, AppProvider.getInstalledUri());
for (int i = 10; i < 20; i++) {
TestUtils.installAndBroadcast(getSwappableContext(), pm, "com.example.test." + i, i, "v1");
}
assertResultCount(10, AppProvider.getInstalledUri());
}
public void testInsert() {
// Start with an empty database...
Cursor cursor = queryAllApps();
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
// Insert a new record...
insertApp("org.fdroid.fdroid", "F-Droid");
cursor = queryAllApps();
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
// We intentionally throw an IllegalArgumentException if you haven't
// yet called cursor.move*()...
try {
new App(cursor);
fail();
} catch (IllegalArgumentException e) {
// Success!
} catch (Exception e) {
fail();
}
// And now we should be able to recover these values from the app
// value object (because the queryAllApps() helper asks for NAME and
// PACKAGE_NAME.
cursor.moveToFirst();
App app = new App(cursor);
cursor.close();
assertEquals("org.fdroid.fdroid", app.packageName);
assertEquals("F-Droid", app.name);
}
private Cursor queryAllApps() {
return getMockContentResolver().query(AppProvider.getContentUri(), getMinimalProjection(), null, null, null);
}
// ========================================================================
// "Categories"
// (at this point) not an additional table, but we treat them sort of
// like they are. That means that if we change the implementation to
// use a separate table in the future, these should still pass.
// ========================================================================
public void testCategoriesSingle() {
insertAppWithCategory("com.dog", "Dog", "Animal");
insertAppWithCategory("com.rock", "Rock", "Mineral");
insertAppWithCategory("com.banana", "Banana", "Vegetable");
List<String> categories = AppProvider.Helper.categories(getMockContext());
String[] expected = new String[] {
getMockContext().getResources().getString(R.string.category_Whats_New),
getMockContext().getResources().getString(R.string.category_Recently_Updated),
getMockContext().getResources().getString(R.string.category_All),
"Animal",
"Mineral",
"Vegetable",
};
TestUtils.assertContainsOnly(categories, expected);
}
public void testCategoriesMultiple() {
insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal");
insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable");
insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable");
List<String> categories = AppProvider.Helper.categories(getMockContext());
String[] expected = new String[] {
getMockContext().getResources().getString(R.string.category_Whats_New),
getMockContext().getResources().getString(R.string.category_Recently_Updated),
getMockContext().getResources().getString(R.string.category_All),
"Animal",
"Mineral",
"Vegetable",
};
TestUtils.assertContainsOnly(categories, expected);
insertAppWithCategory("com.example.game", "Game",
"Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," +
"The quick brown fox jumps over the lazy dog,With apostrophe's");
List<String> categoriesLonger = AppProvider.Helper.categories(getMockContext());
String[] expectedLonger = new String[] {
getMockContext().getResources().getString(R.string.category_Whats_New),
getMockContext().getResources().getString(R.string.category_Recently_Updated),
getMockContext().getResources().getString(R.string.category_All),
"Animal",
"Mineral",
"Vegetable",
"Running",
"Shooting",
"Jumping",
"Bleh",
"Sneh",
"Pleh",
"Blah",
"Test category",
"The quick brown fox jumps over the lazy dog",
"With apostrophe's",
};
TestUtils.assertContainsOnly(categoriesLonger, expectedLonger);
}
// =======================================================================
// Misc helper functions
// (to be used by any tests in this suite)
// =======================================================================
private void insertApp(String id, String name) {
insertApp(id, name, new ContentValues());
}
private void insertAppWithCategory(String id, String name, String categories) {
ContentValues values = new ContentValues(1);
values.put(AppProvider.DataColumns.CATEGORIES, categories);
insertApp(id, name, values);
}
private void insertApp(String id, String name,
ContentValues additionalValues) {
TestUtils.insertApp(getMockContentResolver(), id, name, additionalValues);
}
}

View File

@ -1,43 +0,0 @@
package org.fdroid.fdroid;
import android.content.Context;
import android.util.Log;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static org.junit.Assert.fail;
public class AssetUtils {
private static final String TAG = "Utils";
/**
* This requires {@link Context} from {@link android.app.Instrumentation#getContext()}
*/
@Nullable
public static File copyAssetToDir(Context context, String assetName, File directory) {
File tempFile = null;
InputStream input = null;
OutputStream output = null;
try {
tempFile = File.createTempFile(assetName, ".testasset", directory);
Log.i(TAG, "Copying asset file " + assetName + " to directory " + directory);
input = context.getAssets().open(assetName);
output = new FileOutputStream(tempFile);
Utils.copy(input, output);
} catch (IOException e) {
Log.e(TAG, "Check the context is from Instrumentation.getContext()");
fail(e.getMessage());
} finally {
Utils.closeQuietly(output);
Utils.closeQuietly(input);
}
return tempFile;
}
}

View File

@ -0,0 +1,79 @@
package org.fdroid.fdroid;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import java.util.List;
/**
* Provides helper methods that can be used by both Helper and plain old
* Provider tests. Allows the test classes to contain only test methods,
* hopefully making them easier to understand.
*
* This should not contain any test methods, or else they get executed
* once for every concrete subclass.
*/
abstract class BaseApkProviderTest extends FDroidProviderTest<ApkProvider> {
BaseApkProviderTest() {
super(ApkProvider.class, ApkProvider.getAuthority());
}
@Override
protected String[] getMinimalProjection() {
return new String[] {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.VERSION_CODE,
ApkProvider.DataColumns.NAME,
ApkProvider.DataColumns.REPO_ID,
};
}
protected final Cursor queryAllApks() {
return getMockContentResolver().query(ApkProvider.getContentUri(), getMinimalProjection(), null, null, null);
}
protected void assertContains(List<Apk> apks, Apk apk) {
boolean found = false;
for (Apk a : apks) {
if (a.vercode == apk.vercode && a.packageName.equals(apk.packageName)) {
found = true;
break;
}
}
if (!found) {
fail("Apk [" + apk + "] not found in " + TestUtils.listToString(apks));
}
}
protected void assertBelongsToApp(Cursor apks, String appId) {
assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId);
}
protected void assertBelongsToApp(List<Apk> apks, String appId) {
for (Apk apk : apks) {
assertEquals(appId, apk.packageName);
}
}
protected void assertTotalApkCount(int expected) {
assertResultCount(expected, queryAllApks());
}
protected void assertBelongsToRepo(Cursor apkCursor, long repoId) {
for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) {
assertEquals(repoId, apk.repo);
}
}
protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
ContentValues additionalValues = new ContentValues();
additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
Uri uri = TestUtils.insertApk(this, id, versionCode, additionalValues);
return ApkProvider.Helper.get(getSwappableContext(), uri);
}
}

View File

@ -0,0 +1,186 @@
package org.fdroid.fdroid;
import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.ContactsContract;
import android.test.ProviderTestCase2MockContext;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProvider;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.RepoProvider;
import java.util.List;
import mock.MockContextEmptyComponents;
import mock.MockContextSwappableComponents;
import mock.MockFDroidResources;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public abstract class FDroidProviderTest<T extends FDroidProvider> extends ProviderTestCase2MockContext<T> {
private FDroidProvider[] allProviders = {
new AppProvider(),
new RepoProvider(),
new ApkProvider(),
new InstalledAppProvider(),
};
private MockContextSwappableComponents swappableContext;
public FDroidProviderTest(Class<T> providerClass, String providerAuthority) {
super(providerClass, providerAuthority);
}
protected Resources getMockResources() {
return new MockFDroidResources(getContext());
}
@Override
public void setUp() throws Exception {
super.setUp();
FDroidProvider.clearDbHelperSingleton();
// Instantiate all providers other than the one which was already created by the base class.
// This is because F-Droid providers tend to perform joins onto tables managed by other
// providers, and so we need to be able to insert into those other providers for these
// joins to be tested correctly.
for (FDroidProvider provider : allProviders) {
if (!provider.getName().equals(getProvider().getName())) {
provider.attachInfo(getMockContext(), null);
getMockContentResolver().addProvider(provider.getName(), provider);
}
}
getSwappableContext().setResources(getMockResources());
// The *Provider.Helper.* functions tend to take a Context as their
// first parameter. This context is used to connect to the relevant
// content provider. Thus, we need a context that is able to connect
// to the mock content resolver, in order to reach the provider
// under test.
getSwappableContext().setContentResolver(getMockContentResolver());
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
public void testObviouslyInvalidUris() {
assertInvalidUri("http://www.google.com");
assertInvalidUri(ContactsContract.AUTHORITY_URI);
assertInvalidUri("junk");
}
@Override
protected Context createMockContext(Context delegate) {
swappableContext = new MockContextEmptyComponents();
return swappableContext;
}
public MockContextSwappableComponents getSwappableContext() {
return swappableContext;
}
protected void assertCantDelete(Uri uri) {
try {
getMockContentResolver().delete(uri, null, null);
fail();
} catch (UnsupportedOperationException e) {
} catch (Exception e) {
fail();
}
}
protected void assertCantUpdate(Uri uri) {
try {
getMockContentResolver().update(uri, new ContentValues(), null, null);
fail();
} catch (UnsupportedOperationException e) {
} catch (Exception e) {
fail();
}
}
protected void assertInvalidUri(String uri) {
assertInvalidUri(Uri.parse(uri));
}
protected void assertValidUri(String uri) {
assertValidUri(Uri.parse(uri));
}
protected void assertInvalidUri(Uri uri) {
try {
// Use getProvdider instead of getContentResolver, because the mock
// content resolver wont result in the provider we are testing, and
// hence we don't get to see how our provider responds to invalid
// uris.
getProvider().query(uri, getMinimalProjection(), null, null, null);
fail();
} catch (UnsupportedOperationException e) { }
}
protected void assertValidUri(Uri uri) {
Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null);
assertNotNull(cursor);
cursor.close();
}
protected void assertValidUri(Uri actualUri, String expectedUri) {
assertValidUri(actualUri);
assertEquals(expectedUri, actualUri.toString());
}
/**
* Many queries need at least some sort of projection in order to produce
* valid SQL. As such, we also need to know about that, so we can provide
* helper functions that revolve around the contnet provider under test.
*/
protected abstract String[] getMinimalProjection();
protected void assertResultCount(int expectedCount, Uri uri) {
Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null);
assertResultCount(expectedCount, cursor);
cursor.close();
}
protected void assertResultCount(int expectedCount, List items) {
assertNotNull(items);
assertEquals(expectedCount, items.size());
}
protected void assertResultCount(int expectedCount, Cursor result) {
assertNotNull(result);
assertEquals(expectedCount, result.getCount());
}
protected void assertIsInstalledVersionInDb(String appId, int versionCode, String versionName) {
Uri uri = InstalledAppProvider.getAppUri(appId);
String[] projection = {
InstalledAppProvider.DataColumns.PACKAGE_NAME,
InstalledAppProvider.DataColumns.VERSION_CODE,
InstalledAppProvider.DataColumns.VERSION_NAME,
InstalledAppProvider.DataColumns.APPLICATION_LABEL,
};
Cursor cursor = getMockContentResolver().query(uri, projection, null, null, null);
assertNotNull(cursor);
assertEquals("App \"" + appId + "\" not installed", 1, cursor.getCount());
cursor.moveToFirst();
assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE)));
assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME)));
cursor.close();
}
}

View File

@ -0,0 +1,86 @@
package org.fdroid.fdroid;
import android.app.Instrumentation;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import org.fdroid.fdroid.compat.FileCompatForTest;
import org.fdroid.fdroid.data.SanitizedFile;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.UUID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* This test needs to run on the emulator, even though it technically could
* run as a plain JUnit test, because it is testing the specifics of
* Android's symlink handling.
*/
@RunWith(AndroidJUnit4.class)
public class FileCompatTest {
private static final String TAG = "FileCompatTest";
private File dir;
private SanitizedFile sourceFile;
private SanitizedFile destFile;
@Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
dir = TestUtils.getWriteableDir(instrumentation);
sourceFile = SanitizedFile.knownSanitized(TestUtils.copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
assertFalse(destFile.exists());
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
}
@After
public void tearDown() {
if (!sourceFile.delete()) {
System.out.println("Can't delete " + sourceFile.getAbsolutePath() + ".");
}
if (!destFile.delete()) {
System.out.println("Can't delete " + destFile.getAbsolutePath() + ".");
}
}
@Test
public void testSymlinkRuntime() {
FileCompatForTest.symlinkRuntimeTest(sourceFile, destFile);
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
}
@Test
public void testSymlinkLibcore() {
if (Build.VERSION.SDK_INT >= 19) {
FileCompatForTest.symlinkLibcoreTest(sourceFile, destFile);
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
} else {
Log.w(TAG, "Cannot test symlink-libcore on this device. Requires android-19, but this has android-" + Build.VERSION.SDK_INT);
}
}
@Test
public void testSymlinkOs() {
if (Build.VERSION.SDK_INT >= 21) {
FileCompatForTest.symlinkOsTest(sourceFile, destFile);
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
} else {
Log.w(TAG, "Cannot test symlink-os on this device. Requires android-21, but only has android-" + Build.VERSION.SDK_INT);
}
}
}

View File

@ -0,0 +1,179 @@
package org.fdroid.fdroid;
import org.fdroid.fdroid.data.InstalledAppProvider;
import mock.MockInstallablePackageManager;
/**
* Tests the ability of the {@link org.fdroid.fdroid.data.InstalledAppCacheUpdater} to stay in sync with
* the {@link android.content.pm.PackageManager}.
* For practical reasons, it extends FDroidProviderTest<InstalledAppProvider>, although there is also a
* separate test for the InstalledAppProvider which tests the CRUD operations in more detail.
*/
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class InstalledAppCacheTest extends FDroidProviderTest<InstalledAppProvider> {
private MockInstallablePackageManager packageManager;
public InstalledAppCacheTest() {
super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
}
@Override
public void setUp() throws Exception {
super.setUp();
packageManager = new MockInstallablePackageManager();
getSwappableContext().setPackageManager(packageManager);
}
@Override
protected String[] getMinimalProjection() {
return new String[] {
InstalledAppProvider.DataColumns.PACKAGE_NAME,
};
}
public void install(String appId, int versionCode, String versionName) {
packageManager.install(appId, versionCode, versionName);
}
public void remove(String appId) {
packageManager.remove(appId);
}
/* TODO fix me
public void testFromEmptyCache() {
assertResultCount(0, InstalledAppProvider.getContentUri());
for (int i = 1; i <= 15; i ++) {
install("com.example.app" + i, 200, "2.0");
}
InstalledAppCacheUpdater.updateInForeground(getMockContext());
String[] expectedInstalledIds = {
"com.example.app1",
"com.example.app2",
"com.example.app3",
"com.example.app4",
"com.example.app5",
"com.example.app6",
"com.example.app7",
"com.example.app8",
"com.example.app9",
"com.example.app10",
"com.example.app11",
"com.example.app12",
"com.example.app13",
"com.example.app14",
"com.example.app15",
};
TestUtils.assertContainsOnly(getInstalledAppIdsFromProvider(), expectedInstalledIds);
}
private String[] getInstalledAppIdsFromProvider() {
Uri uri = InstalledAppProvider.getContentUri();
String[] projection = { InstalledAppProvider.DataColumns.PACKAGE_NAME };
Cursor result = getMockContext().getContentResolver().query(uri, projection, null, null, null);
if (result == null) {
return new String[0];
}
String[] installedAppIds = new String[result.getCount()];
result.moveToFirst();
int i = 0;
while (!result.isAfterLast()) {
installedAppIds[i] = result.getString(result.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME));
result.moveToNext();
i ++;
}
result.close();
return installedAppIds;
}
public void testAppsAdded() {
assertResultCount(0, InstalledAppProvider.getContentUri());
install("com.example.app1", 1, "v1");
install("com.example.app2", 1, "v1");
install("com.example.app3", 1, "v1");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(3, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
install("com.example.app10", 1, "v1");
install("com.example.app11", 1, "v1");
install("com.example.app12", 1, "v1");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(6, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app10", 1, "v1");
assertIsInstalledVersionInDb("com.example.app11", 1, "v1");
assertIsInstalledVersionInDb("com.example.app12", 1, "v1");
}
public void testAppsRemoved() {
install("com.example.app1", 1, "v1");
install("com.example.app2", 1, "v1");
install("com.example.app3", 1, "v1");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(3, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
remove("com.example.app2");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
}
public void testAppsUpdated() {
install("com.example.app1", 1, "v1");
install("com.example.app2", 1, "v1");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
install("com.example.app2", 20, "v2.0");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
assertIsInstalledVersionInDb("com.example.app2", 20, "v2.0");
}
public void testAppsAddedRemovedAndUpdated() {
install("com.example.app1", 1, "v1");
install("com.example.app2", 1, "v1");
install("com.example.app3", 1, "v1");
install("com.example.app4", 1, "v1");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(4, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
assertIsInstalledVersionInDb("com.example.app4", 1, "v1");
install("com.example.app1", 13, "v1.3");
remove("com.example.app2");
remove("com.example.app3");
install("com.example.app10", 1, "v1");
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(3, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app1", 13, "v1.3");
assertIsInstalledVersionInDb("com.example.app4", 1, "v1");
assertIsInstalledVersionInDb("com.example.app10", 1, "v1");
}
*/
}

View File

@ -0,0 +1,183 @@
package org.fdroid.fdroid;
import android.content.ContentValues;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.RepoProvider;
import mock.MockInstallablePackageManager;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppProvider> {
private MockInstallablePackageManager packageManager;
public InstalledAppProviderTest() {
super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
}
@Override
public void setUp() throws Exception {
super.setUp();
packageManager = new MockInstallablePackageManager();
getSwappableContext().setPackageManager(packageManager);
}
protected MockInstallablePackageManager getPackageManager() {
return packageManager;
}
public void testUris() {
assertInvalidUri(InstalledAppProvider.getAuthority());
assertInvalidUri(RepoProvider.getContentUri());
assertInvalidUri(AppProvider.getContentUri());
assertInvalidUri(ApkProvider.getContentUri());
assertInvalidUri("blah");
assertValidUri(InstalledAppProvider.getContentUri());
assertValidUri(InstalledAppProvider.getAppUri("com.example.com"));
assertValidUri(InstalledAppProvider.getAppUri("blah"));
}
public void testInsert() {
assertResultCount(0, InstalledAppProvider.getContentUri());
insertInstalledApp("com.example.com1", 1, "v1");
insertInstalledApp("com.example.com2", 2, "v2");
insertInstalledApp("com.example.com3", 3, "v3");
assertResultCount(3, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.com1", 1, "v1");
assertIsInstalledVersionInDb("com.example.com2", 2, "v2");
assertIsInstalledVersionInDb("com.example.com3", 3, "v3");
}
public void testUpdate() {
insertInstalledApp("com.example.app1", 10, "1.0");
insertInstalledApp("com.example.app2", 10, "1.0");
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app2", 10, "1.0");
try {
getMockContentResolver().update(
InstalledAppProvider.getAppUri("com.example.app2"),
createContentValues(11, "1.1"),
null, null
);
fail();
} catch (UnsupportedOperationException e) {
// We expect this to happen, because we should be using insert() instead.
}
getMockContentResolver().insert(
InstalledAppProvider.getContentUri(),
createContentValues("com.example.app2", 11, "1.1")
);
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app2", 11, "1.1");
}
public void testDelete() {
insertInstalledApp("com.example.app1", 10, "1.0");
insertInstalledApp("com.example.app2", 10, "1.0");
assertResultCount(2, InstalledAppProvider.getContentUri());
getMockContentResolver().delete(InstalledAppProvider.getAppUri("com.example.app1"), null, null);
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.app2", 10, "1.0");
}
public void testInsertWithBroadcast() {
installAndBroadcast("com.example.broadcasted1", 10, "v1.0");
installAndBroadcast("com.example.broadcasted2", 105, "v1.05");
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.broadcasted1", 10, "v1.0");
assertIsInstalledVersionInDb("com.example.broadcasted2", 105, "v1.05");
}
public void testUpdateWithBroadcast() {
installAndBroadcast("com.example.toUpgrade", 1, "v0.1");
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toUpgrade", 1, "v0.1");
upgradeAndBroadcast("com.example.toUpgrade", 2, "v0.2");
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toUpgrade", 2, "v0.2");
}
public void testDeleteWithBroadcast() {
installAndBroadcast("com.example.toKeep", 1, "v0.1");
installAndBroadcast("com.example.toDelete", 1, "v0.1");
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
assertIsInstalledVersionInDb("com.example.toDelete", 1, "v0.1");
removeAndBroadcast("com.example.toDelete");
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
}
@Override
protected String[] getMinimalProjection() {
return new String[] {
InstalledAppProvider.DataColumns.PACKAGE_NAME,
InstalledAppProvider.DataColumns.VERSION_CODE,
InstalledAppProvider.DataColumns.VERSION_NAME,
};
}
private ContentValues createContentValues(int versionCode, String versionNumber) {
return createContentValues(null, versionCode, versionNumber);
}
private ContentValues createContentValues(String appId, int versionCode, String versionNumber) {
ContentValues values = new ContentValues(3);
if (appId != null) {
values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, appId);
}
values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Mock app: " + appId);
values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
values.put(InstalledAppProvider.DataColumns.SIGNATURE, "");
return values;
}
private void insertInstalledApp(String appId, int versionCode, String versionNumber) {
ContentValues values = createContentValues(appId, versionCode, versionNumber);
getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values);
}
private void removeAndBroadcast(String appId) {
TestUtils.removeAndBroadcast(getSwappableContext(), getPackageManager(), appId);
}
private void upgradeAndBroadcast(String appId, int versionCode, String versionName) {
TestUtils.upgradeAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
}
private void installAndBroadcast(String appId, int versionCode, String versionName) {
TestUtils.installAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
}
}

View File

@ -1,222 +0,0 @@
package org.fdroid.fdroid;
import android.app.Instrumentation;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllegalFormatException;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Runs through all of the translated strings and tests them with the same format
* values that the source strings expect. This is to ensure that the formats in
* the translations are correct in number and in type (e.g. {@code s} or {@code s}.
* It reads the source formats and then builds {@code formats} to represent the
* position and type of the formats. Then it runs through all of the translations
* with formats of the correct number and type.
*/
@RunWith(AndroidJUnit4.class)
public class LocalizationTest {
public static final String TAG = "LocalizationTest";
private final Pattern androidFormat = Pattern.compile("(%[a-z0-9]\\$?[a-z]?)");
private final Locale[] locales = Locale.getAvailableLocales();
private final HashSet<String> localeNames = new HashSet<>(locales.length);
private AssetManager assets;
private Configuration config;
private Resources resources;
@Before
public void setUp() {
for (Locale locale : Languages.LOCALES_TO_TEST) {
localeNames.add(locale.toString());
}
for (Locale locale : locales) {
localeNames.add(locale.toString());
}
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
Context context = instrumentation.getTargetContext();
assets = context.getAssets();
config = context.getResources().getConfiguration();
config.locale = Locale.ENGLISH;
// Resources() requires DisplayMetrics, but they are only needed for drawables
resources = new Resources(assets, new DisplayMetrics(), config);
}
@Test
public void testLoadAllPlural() throws IllegalAccessException {
Field[] fields = R.plurals.class.getDeclaredFields();
HashMap<String, String> haveFormats = new HashMap<>();
for (Field field : fields) {
//Log.i(TAG, field.getName());
int resId = field.getInt(int.class);
CharSequence string = resources.getQuantityText(resId, 4);
//Log.i(TAG, field.getName() + ": '" + string + "'");
Matcher matcher = androidFormat.matcher(string);
int matches = 0;
char[] formats = new char[5];
while (matcher.find()) {
String match = matcher.group(0);
char formatType = match.charAt(match.length() - 1);
switch (match.length()) {
case 2:
formats[matches] = formatType;
matches++;
break;
case 4:
formats[Integer.parseInt(match.substring(1, 2)) - 1] = formatType;
break;
case 5:
formats[Integer.parseInt(match.substring(1, 3)) - 1] = formatType;
break;
default:
throw new IllegalStateException(field.getName() + " has bad format: " + match);
}
}
haveFormats.put(field.getName(), new String(formats).trim());
}
for (Locale locale : locales) {
config.locale = locale;
// Resources() requires DisplayMetrics, but they are only needed for drawables
resources = new Resources(assets, new DisplayMetrics(), config);
for (Field field : fields) {
String formats = null;
try {
int resId = field.getInt(int.class);
for (int quantity = 0; quantity < 567; quantity++) {
resources.getQuantityString(resId, quantity);
}
formats = haveFormats.get(field.getName());
switch (formats) {
case "d":
resources.getQuantityString(resId, 1, 1);
break;
case "s":
resources.getQuantityString(resId, 1, "ONE");
break;
case "ds":
resources.getQuantityString(resId, 2, 1, "TWO");
break;
default:
if (!TextUtils.isEmpty(formats)) {
throw new IllegalStateException("Pattern not included in tests: " + formats);
}
}
} catch (IllegalFormatException | Resources.NotFoundException e) {
Log.i(TAG, locale + " " + field.getName());
throw new IllegalArgumentException("Bad '" + formats + "' format in " + locale + " "
+ field.getName() + ": " + e.getMessage());
}
}
}
}
@Test
public void testLoadAllStrings() throws IllegalAccessException {
Field[] fields = R.string.class.getDeclaredFields();
HashMap<String, String> haveFormats = new HashMap<>();
for (Field field : fields) {
String string = resources.getString(field.getInt(int.class));
Matcher matcher = androidFormat.matcher(string);
int matches = 0;
char[] formats = new char[5];
while (matcher.find()) {
String match = matcher.group(0);
char formatType = match.charAt(match.length() - 1);
switch (match.length()) {
case 2:
formats[matches] = formatType;
matches++;
break;
case 4:
formats[Integer.parseInt(match.substring(1, 2)) - 1] = formatType;
break;
case 5:
formats[Integer.parseInt(match.substring(1, 3)) - 1] = formatType;
break;
default:
throw new IllegalStateException(field.getName() + " has bad format: " + match);
}
}
haveFormats.put(field.getName(), new String(formats).trim());
}
for (Locale locale : locales) {
config.locale = locale;
// Resources() requires DisplayMetrics, but they are only needed for drawables
resources = new Resources(assets, new DisplayMetrics(), config);
for (Field field : fields) {
int resId = field.getInt(int.class);
resources.getString(resId);
String formats = haveFormats.get(field.getName());
try {
switch (formats) {
case "d":
resources.getString(resId, 1);
break;
case "dd":
resources.getString(resId, 1, 2);
break;
case "ds":
resources.getString(resId, 1, "TWO");
break;
case "dds":
resources.getString(resId, 1, 2, "THREE");
break;
case "sds":
resources.getString(resId, "ONE", 2, "THREE");
break;
case "s":
resources.getString(resId, "ONE");
break;
case "ss":
resources.getString(resId, "ONE", "TWO");
break;
case "sss":
resources.getString(resId, "ONE", "TWO", "THREE");
break;
case "ssss":
resources.getString(resId, "ONE", "TWO", "THREE", "FOUR");
break;
case "ssd":
resources.getString(resId, "ONE", "TWO", 3);
break;
case "sssd":
resources.getString(resId, "ONE", "TWO", "THREE", 4);
break;
default:
if (!TextUtils.isEmpty(formats)) {
throw new IllegalStateException("Pattern not included in tests: " + formats);
}
}
} catch (Exception e) {
Log.i(TAG, locale + " " + field.getName());
throw new IllegalArgumentException("Bad format in '" + locale + "' '" + field.getName() + "': "
+ e.getMessage());
}
}
}
}
}

View File

@ -1,303 +0,0 @@
package org.fdroid.fdroid;
import android.Manifest;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Build;
import androidx.core.content.ContextCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.espresso.IdlingPolicies;
import androidx.test.espresso.ViewInteraction;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;
import android.util.Log;
import android.view.View;
import org.fdroid.fdroid.views.StatusBanner;
import org.fdroid.fdroid.views.main.MainActivity;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.swipeDown;
import static androidx.test.espresso.action.ViewActions.swipeLeft;
import static androidx.test.espresso.action.ViewActions.swipeRight;
import static androidx.test.espresso.action.ViewActions.swipeUp;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityEspressoTest {
public static final String TAG = "MainActivityEspressoTest";
/**
* Emulators older than {@code android-25} seem to fail at running Espresso tests.
* <p>
* ARM emulators are too slow to run these tests in a useful way. The sad
* thing is that it would probably work if Android didn't put up the ANR
* "Process system isn't responding" on boot each time. There seems to be no
* way to increase the ANR timeout.
*/
private static boolean canRunEspresso() {
if (Build.VERSION.SDK_INT < 25
|| (Build.SUPPORTED_ABIS[0].startsWith("arm") && isEmulator())) {
Log.e(TAG, "SKIPPING TEST: ARM emulators are too slow to run these tests in a useful way");
return false;
}
return true;
}
@BeforeClass
public static void classSetUp() {
IdlingPolicies.setIdlingResourceTimeout(10, TimeUnit.MINUTES);
IdlingPolicies.setMasterPolicyTimeout(10, TimeUnit.MINUTES);
if (!canRunEspresso()) {
return;
}
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
try {
UiDevice.getInstance(instrumentation)
.executeShellCommand("pm grant "
+ instrumentation.getTargetContext().getPackageName()
+ " android.permission.SET_ANIMATION_SCALE");
} catch (IOException e) {
e.printStackTrace();
}
SystemAnimations.disableAll(ApplicationProvider.getApplicationContext());
// dismiss the ANR or any other system dialogs that might be there
UiObject button = new UiObject(new UiSelector().text("Wait").enabled(true));
try {
button.click();
} catch (UiObjectNotFoundException e) {
Log.d(TAG, e.getLocalizedMessage());
}
new UiWatchers().registerAnrAndCrashWatchers();
Context context = instrumentation.getTargetContext();
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
ActivityManager activityManager = ContextCompat.getSystemService(context, ActivityManager.class);
activityManager.getMemoryInfo(mi);
long percentAvail = mi.availMem / mi.totalMem;
Log.i(TAG, "RAM: " + mi.availMem + " / " + mi.totalMem + " = " + percentAvail);
}
@AfterClass
public static void classTearDown() {
SystemAnimations.enableAll(ApplicationProvider.getApplicationContext());
}
public static boolean isEmulator() {
return Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86")
|| Build.MANUFACTURER.contains("Genymotion")
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|| "google_sdk".equals(Build.PRODUCT);
}
@Before
public void setUp() {
assumeTrue(canRunEspresso());
}
/**
* Placate {@link android.os.StrictMode}
*
* @see <a href="https://github.com/aosp-mirror/platform_frameworks_base/commit/6f3a38f3afd79ed6dddcef5c83cb442d6749e2ff"> Run finalizers before counting for StrictMode</a>
*/
@After
public void tearDown() {
System.gc();
System.runFinalization();
System.gc();
}
@Rule
public ActivityTestRule<MainActivity> activityTestRule =
new ActivityTestRule<>(MainActivity.class);
@Rule
public GrantPermissionRule accessCoarseLocationPermissionRule = GrantPermissionRule.grant(
Manifest.permission.ACCESS_COARSE_LOCATION);
@Rule
public GrantPermissionRule writeExternalStoragePermissionRule = GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE);
@Test
public void bottomNavFlavorCheck() {
onView(withText(R.string.main_menu__updates)).check(matches(isDisplayed()));
onView(withText(R.string.menu_settings)).check(matches(isDisplayed()));
onView(withText("THIS SHOULD NOT SHOW UP ANYWHERE!!!")).check(doesNotExist());
assertTrue(BuildConfig.FLAVOR.startsWith("full") || BuildConfig.FLAVOR.startsWith("basic"));
if (BuildConfig.FLAVOR.startsWith("basic")) {
onView(withText(R.string.main_menu__latest_apps)).check(matches(isDisplayed()));
onView(withText(R.string.main_menu__categories)).check(doesNotExist());
onView(withText(R.string.main_menu__swap_nearby)).check(doesNotExist());
}
if (BuildConfig.FLAVOR.startsWith("full")) {
onView(withText(R.string.main_menu__latest_apps)).check(matches(isDisplayed()));
onView(withText(R.string.main_menu__categories)).check(matches(isDisplayed()));
onView(withText(R.string.main_menu__swap_nearby)).check(matches(isDisplayed()));
}
}
@LargeTest
@Test
public void showSettings() {
ViewInteraction settingsBottonNavButton = onView(
allOf(withText(R.string.menu_settings), isDisplayed()));
settingsBottonNavButton.perform(click());
onView(withText(R.string.preference_manage_installed_apps)).check(matches(isDisplayed()));
if (BuildConfig.FLAVOR.startsWith("basic") && BuildConfig.APPLICATION_ID.endsWith(".debug")) {
// TODO fix me by sorting out the flavor applicationId for debug builds in app/build.gradle
Log.i(TAG, "Skipping the remainder of showSettings test because it just crashes on basic .debug builds");
return;
}
ViewInteraction manageInstalledAppsButton = onView(
allOf(withText(R.string.preference_manage_installed_apps), isDisplayed()));
manageInstalledAppsButton.perform(click());
onView(withText(R.string.installed_apps__activity_title)).check(matches(isDisplayed()));
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
onView(withText(R.string.menu_manage)).perform(click());
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
manageInstalledAppsButton.perform(click());
onView(withText(R.string.installed_apps__activity_title)).check(matches(isDisplayed()));
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
onView(withText(R.string.menu_manage)).perform(click());
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
onView(withText(R.string.about_title)).perform(click());
onView(withId(R.id.version)).check(matches(isDisplayed()));
onView(withId(R.id.ok_button)).perform(click());
onView(withId(android.R.id.list_container)).perform(swipeUp()).perform(swipeUp()).perform(swipeUp());
}
@LargeTest
@Test
public void showUpdates() {
ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed()));
updatesBottonNavButton.perform(click());
onView(withText(R.string.main_menu__updates)).check(matches(isDisplayed()));
}
@LargeTest
@Test
public void startSwap() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
}
ViewInteraction nearbyBottonNavButton = onView(
allOf(withText(R.string.main_menu__swap_nearby), isDisplayed()));
nearbyBottonNavButton.perform(click());
ViewInteraction findPeopleButton = onView(
allOf(withId(R.id.find_people_button), withText(R.string.nearby_splash__find_people_button),
isDisplayed()));
findPeopleButton.perform(click());
onView(withText(R.string.swap_send_fdroid)).check(matches(isDisplayed()));
}
@LargeTest
@Test
public void showCategories() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
}
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
onView(allOf(withText(R.string.main_menu__categories), isDisplayed())).perform(click());
onView(allOf(withId(R.id.swipe_to_refresh), isDisplayed()))
.perform(swipeDown())
.perform(swipeUp())
.perform(swipeUp())
.perform(swipeUp())
.perform(swipeUp())
.perform(swipeUp())
.perform(swipeUp())
.perform(swipeDown())
.perform(swipeDown())
.perform(swipeRight())
.perform(swipeLeft())
.perform(swipeLeft())
.perform(swipeLeft())
.perform(swipeLeft())
.perform(click());
}
@LargeTest
@Test
public void showLatest() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
}
onView(Matchers.<View>instanceOf(StatusBanner.class)).check(matches(not(isDisplayed())));
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
onView(allOf(withText(R.string.main_menu__latest_apps), isDisplayed())).perform(click());
onView(allOf(withId(R.id.swipe_to_refresh), isDisplayed()))
.perform(swipeDown())
.perform(swipeUp())
.perform(swipeUp())
.perform(swipeUp())
.perform(swipeDown())
.perform(swipeUp())
.perform(swipeDown())
.perform(swipeDown())
.perform(swipeDown())
.perform(swipeDown())
.perform(click());
}
@LargeTest
@Test
public void showSearch() {
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
onView(withId(R.id.fab_search)).check(doesNotExist());
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
}
onView(allOf(withText(R.string.main_menu__latest_apps), isDisplayed())).perform(click());
onView(allOf(withId(R.id.fab_search), isDisplayed())).perform(click());
onView(withId(R.id.sort)).check(matches(isDisplayed()));
onView(allOf(withId(R.id.search), isDisplayed()))
.perform(click())
.perform(typeText("test"));
onView(allOf(withId(R.id.sort), isDisplayed())).perform(click());
}
}

View File

@ -0,0 +1,446 @@
package org.fdroid.fdroid;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.test.InstrumentationTestCase;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.RepoUpdater.UpdateException;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProvider;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.TempApkProvider;
import org.fdroid.fdroid.data.TempAppProvider;
import java.io.File;
import java.util.List;
import java.util.UUID;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class MultiRepoUpdaterTest extends InstrumentationTestCase {
private static final String TAG = "RepoUpdaterTest";
private static final String REPO_MAIN = "Test F-Droid repo";
private static final String REPO_ARCHIVE = "Test F-Droid repo (Archive)";
private static final String REPO_CONFLICTING = "Test F-Droid repo with different apps";
private Context context;
private RepoUpdater conflictingRepoUpdater;
private RepoUpdater mainRepoUpdater;
private RepoUpdater archiveRepoUpdater;
private File testFilesDir;
private static final String PUB_KEY =
"3082050b308202f3a003020102020420d8f212300d06092a864886f70d01010b050030363110300e0603" +
"55040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e7365727779" +
"6c6f2e636f6d301e170d3135303931323233313632315a170d3433303132383233313632315a30363110" +
"300e060355040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e73" +
"657277796c6f2e636f6d30820222300d06092a864886f70d01010105000382020f003082020a02820201" +
"00b21fe72b84ce721967851364bd20511088d117bc3034e4bb4d3c1a06af2a308fdffdaf63b12e0926b9" +
"0545134b9ff570646cbcad89d9e86dcc8eb9977dd394240c75bccf5e8ddc3c5ef91b4f16eca5f36c36f1" +
"92463ff2c9257d3053b7c9ecdd1661bd01ec3fe70ee34a7e6b92ddba04f258a32d0cfb1b0ce85d047180" +
"97fc4bdfb54541b430dfcfc1c84458f9eb5627e0ec5341d561c3f15f228379a1282d241329198f31a7ac" +
"cd51ab2bbb881a1da55001123483512f77275f8990c872601198065b4e0137ddd1482e4fdefc73b857d4" +
"be324ca96c268ceb725398f8cc38a0dc6aa2c277f8686724e8c7ff3f320a05791fccacc6caa956cf23a9" +
"de2dc7070b262c0e35d90d17e90773bb11e875e79a8dfd958e359d5d5ad903a7cbc2955102502bd0134c" +
"a1ff7a0bbbbb57302e4a251e40724dcaa8ad024f4b3a71b8fceaac664c0dcc1995a1c4cf42676edad8bc" +
"b03ba255ab796677f18fff2298e1aaa5b134254b44d08a4d934c9859af7bbaf078c37b7f628db0e2cffb" +
"0493a669d5f4770d35d71284550ce06d6f6811cd2a31585085716257a4ba08ad968b0a2bf88f34ca2f2c" +
"73af1c042ab147597faccfb6516ef4468cfa0c5ab3c8120eaa7bac1080e4d2310f717db20815d0e1ee26" +
"bd4e47eed8d790892017ae9595365992efa1b7fd1bc1963f018264b2b3749b8f7b1907bb0843f1e7fc2d" +
"3f3b02284cd4bae0ab0203010001a321301f301d0603551d0e0416041456110e4fed863ab1df9448bfd9" +
"e10a8bc32ffe08300d06092a864886f70d01010b050003820201008082572ae930ebc55ecf1110f4bb72" +
"ad2a952c8ac6e65bd933706beb4a310e23deabb8ef6a7e93eea8217ab1f3f57b1f477f95f1d62eccb563" +
"67a4d70dfa6fcd2aace2bb00b90af39412a9441a9fae2396ff8b93de1df3d9837c599b1f80b7d75285cb" +
"df4539d7dd9612f54b45ca59bc3041c9b92fac12753fac154d12f31df360079ab69a2d20db9f6a7277a8" +
"259035e93de95e8cbc80351bc83dd24256183ea5e3e1db2a51ea314cdbc120c064b77e2eb3a731530511" +
"1e1dabed6996eb339b7cb948d05c1a84d63094b4a4c6d11389b2a7b5f2d7ecc9a149dda6c33705ef2249" +
"58afdfa1d98cf646dcf8857cd8342b1e07d62cb4313f35ad209046a4a42ff73f38cc740b1e695eeda49d" +
"5ea0384ad32f9e3ae54f6a48a558dbc7cccabd4e2b2286dc9c804c840bd02b9937841a0e48db00be9e3c" +
"d7120cf0f8648ce4ed63923f0352a2a7b3b97fc55ba67a7a218b8c0b3cda4a45861280a622e0a59cc9fb" +
"ca1117568126c581afa4408b0f5c50293c212c406b8ab8f50aad5ed0f038cfca580ef3aba7df25464d9e" +
"495ffb629922cfb511d45e6294c045041132452f1ed0f20ac3ab4792f610de1734e4c8b71d743c4b0101" +
"98f848e0dbfce5a0f2da0198c47e6935a47fda12c518ef45adfb66ddf5aebaab13948a66c004b8592d22" +
"e8af60597c4ae2977977cf61dc715a572e241ae717cafdb4f71781943945ac52e0f50b";
public class TestContext extends RenamingDelegatingContext {
private MockContentResolver resolver;
public TestContext() {
super(getInstrumentation().getTargetContext(), "test.");
resolver = new MockContentResolver();
resolver.addProvider(AppProvider.getAuthority(), prepareProvider(new AppProvider()));
resolver.addProvider(ApkProvider.getAuthority(), prepareProvider(new ApkProvider()));
resolver.addProvider(RepoProvider.getAuthority(), prepareProvider(new RepoProvider()));
resolver.addProvider(TempAppProvider.getAuthority(), prepareProvider(new TempAppProvider()));
resolver.addProvider(TempApkProvider.getAuthority(), prepareProvider(new TempApkProvider()));
}
private ContentProvider prepareProvider(ContentProvider provider) {
provider.attachInfo(this, null);
provider.onCreate();
return provider;
}
@Override
public File getFilesDir() {
return getInstrumentation().getTargetContext().getFilesDir();
}
/**
* String resources used during testing (e.g. when bootstraping the database) are from
* the real org.fdroid.fdroid app, not the test org.fdroid.fdroid.test app.
*/
@Override
public Resources getResources() {
return getInstrumentation().getTargetContext().getResources();
}
@Override
public ContentResolver getContentResolver() {
return resolver;
}
@Override
public AssetManager getAssets() {
return getInstrumentation().getContext().getAssets();
}
@Override
public File getDatabasePath(String name) {
return new File(getInstrumentation().getContext().getFilesDir(), "fdroid_test.db");
}
@Override
public Context getApplicationContext() {
// Used by the DBHelper singleton instance.
return this;
}
}
@Override
public void setUp() throws Exception {
super.setUp();
FDroidProvider.clearDbHelperSingleton();
context = new TestContext();
testFilesDir = TestUtils.getWriteableDir(getInstrumentation());
// On a fresh database install, there will be F-Droid + GP repos, including their Archive
// repos that we are not interested in.
RepoProvider.Helper.remove(context, 1);
RepoProvider.Helper.remove(context, 2);
RepoProvider.Helper.remove(context, 3);
RepoProvider.Helper.remove(context, 4);
conflictingRepoUpdater = createUpdater(REPO_CONFLICTING, context);
mainRepoUpdater = createUpdater(REPO_MAIN, context);
archiveRepoUpdater = createUpdater(REPO_ARCHIVE, context);
}
/**
* Check that all of the expected apps and apk versions are available in the database. This
* check will take into account the repository the apks came from, to ensure that each
* repository indeed contains the apks that it said it would provide.
*/
private void assertExpected() {
Log.d(TAG, "Asserting all versions of each .apk are in index.");
List<Repo> repos = RepoProvider.Helper.all(context);
assertEquals("Repos", 3, repos.size());
assertMainRepo(repos);
assertMainArchiveRepo(repos);
assertConflictingRepo(repos);
}
/**
*
*/
private void assertSomewhatAcceptable() {
Log.d(TAG, "Asserting at least one versions of each .apk is in index.");
List<Repo> repos = RepoProvider.Helper.all(context);
assertEquals("Repos", 3, repos.size());
assertApp2048();
assertAppAdaway();
assertAppAdbWireless();
assertAppIcsImport();
}
private void assertApp(String packageName, int[] versionCodes) {
List<Apk> apks = ApkProvider.Helper.findByPackageName(context, packageName, ApkProvider.DataColumns.ALL);
assertApksExist(apks, packageName, versionCodes);
}
private void assertApp2048() {
assertApp("com.uberspot.a2048", new int[]{19, 18});
}
private void assertAppAdaway() {
assertApp("org.adaway", new int[]{54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 42, 40, 38, 37, 36, 35});
}
private void assertAppAdbWireless() {
assertApp("siir.es.adbWireless", new int[]{12});
}
private void assertAppIcsImport() {
assertApp("org.dgtale.icsimport", new int[]{3, 2});
}
/**
* + 2048 (com.uberspot.a2048)
* - Version 1.96 (19)
* - Version 1.95 (18)
* + AdAway (org.adaway)
* - Version 3.0.2 (54)
* - Version 3.0.1 (53)
* - Version 3.0 (52)
* + adbWireless (siir.es.adbWireless)
* - Version 1.5.4 (12)
*/
private void assertMainRepo(List<Repo> allRepos) {
Repo repo = findRepo(REPO_MAIN, allRepos);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
assertEquals("Apks for main repo", apks.size(), 6);
assertApksExist(apks, "com.uberspot.a2048", new int[]{18, 19});
assertApksExist(apks, "org.adaway", new int[]{52, 53, 54});
assertApksExist(apks, "siir.es.adbWireless", new int[]{12});
}
/**
* + AdAway (org.adaway)
* - Version 2.9.2 (51)
* - Version 2.9.1 (50)
* - Version 2.9 (49)
* - Version 2.8.1 (48)
* - Version 2.8 (47)
* - Version 2.7 (46)
* - Version 2.6 (45)
* - Version 2.3 (42)
* - Version 2.1 (40)
* - Version 1.37 (38)
* - Version 1.36 (37)
* - Version 1.35 (36)
* - Version 1.34 (35)
*/
private void assertMainArchiveRepo(List<Repo> allRepos) {
Repo repo = findRepo(REPO_ARCHIVE, allRepos);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
assertEquals("Apks for main archive repo", 13, apks.size());
assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51});
}
/**
* + AdAway (org.adaway)
* - Version 3.0.1 (53) *
* - Version 3.0 (52) *
* - Version 2.9.2 (51) *
* - Version 2.2.1 (50) *
* + Add to calendar (org.dgtale.icsimport)
* - Version 1.2 (3)
* - Version 1.1 (2)
*/
private void assertConflictingRepo(List<Repo> allRepos) {
Repo repo = findRepo(REPO_CONFLICTING, allRepos);
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
assertEquals("Apks for main repo", 6, apks.size());
assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53});
assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3});
}
@NonNull
private Repo findRepo(@NonNull String name, List<Repo> allRepos) {
Repo repo = null;
for (Repo r : allRepos) {
if (TextUtils.equals(name, r.getName())) {
repo = r;
break;
}
}
assertNotNull("Repo " + allRepos, repo);
return repo;
}
/**
* Checks that each version of appId as specified in versionCodes is present in apksToCheck.
*/
private void assertApksExist(List<Apk> apksToCheck, String appId, int[] versionCodes) {
for (int versionCode : versionCodes) {
boolean found = false;
for (Apk apk : apksToCheck) {
if (apk.vercode == versionCode && apk.packageName.equals(appId)) {
found = true;
break;
}
}
assertTrue("Couldn't find app " + appId + ", v" + versionCode, found);
}
}
private void assertEmpty() {
assertEquals("No apps present", 0, AppProvider.Helper.all(context.getContentResolver()).size());
String[] packages = {
"com.uberspot.a2048",
"org.adaway",
"siir.es.adbWireless",
};
for (String id : packages) {
assertEquals("No apks for " + id, 0, ApkProvider.Helper.findByPackageName(context, id).size());
}
}
/* At time fo writing, the following tests did not pass. This is because the multi-repo support
in F-Droid was not sufficient. When working on proper multi repo support than this should be
ucommented and all these tests should pass:
public void testCorrectConflictingThenMainThenArchive() throws UpdateException {
assertEmpty();
if (updateConflicting() && updateMain() && updateArchive()) {
assertExpected();
}
}
public void testCorrectConflictingThenArchiveThenMain() throws UpdateException {
assertEmpty();
if (updateConflicting() && updateArchive() && updateMain()) {
assertExpected();
}
}
public void testCorrectArchiveThenMainThenConflicting() throws UpdateException {
assertEmpty();
if (updateArchive() && updateMain() && updateConflicting()) {
assertExpected();
}
}
public void testCorrectArchiveThenConflictingThenMain() throws UpdateException {
assertEmpty();
if (updateArchive() && updateConflicting() && updateMain()) {
assertExpected();
}
}
public void testCorrectMainThenArchiveThenConflicting() throws UpdateException {
assertEmpty();
if (updateMain() && updateArchive() && updateConflicting()) {
assertExpected();
}
}
public void testCorrectMainThenConflictingThenArchive() throws UpdateException {
assertEmpty();
if (updateMain() && updateConflicting() && updateArchive()) {
assertExpected();
}
}
*/
public void testAcceptableConflictingThenMainThenArchive() throws UpdateException {
assertEmpty();
if (updateConflicting() && updateMain() && updateArchive()) {
assertSomewhatAcceptable();
}
}
public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException {
assertEmpty();
if (updateConflicting() && updateArchive() && updateMain()) {
assertSomewhatAcceptable();
}
}
public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException {
assertEmpty();
if (updateArchive() && updateMain() && updateConflicting()) {
assertSomewhatAcceptable();
}
}
public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException {
assertEmpty();
if (updateArchive() && updateConflicting() && updateMain()) {
assertSomewhatAcceptable();
}
}
public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException {
assertEmpty();
if (updateMain() && updateArchive() && updateConflicting()) {
assertSomewhatAcceptable();
}
}
public void testAcceptableMainThenConflictingThenArchive() throws UpdateException {
assertEmpty();
if (updateMain() && updateConflicting() && updateArchive()) {
assertSomewhatAcceptable();
}
}
private RepoUpdater createUpdater(String name, Context context) {
Repo repo = new Repo();
repo.pubkey = PUB_KEY;
repo.address = UUID.randomUUID().toString();
repo.name = name;
ContentValues values = new ContentValues(2);
values.put(RepoProvider.DataColumns.PUBLIC_KEY, repo.pubkey);
values.put(RepoProvider.DataColumns.ADDRESS, repo.address);
values.put(RepoProvider.DataColumns.NAME, repo.name);
RepoProvider.Helper.insert(context, values);
// Need to reload the repo based on address so that it includes the primary key from
// the database.
return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address));
}
private boolean updateConflicting() throws UpdateException {
return updateRepo(conflictingRepoUpdater, "multiRepo.conflicting.jar");
}
private boolean updateMain() throws UpdateException {
return updateRepo(mainRepoUpdater, "multiRepo.normal.jar");
}
private boolean updateArchive() throws UpdateException {
return updateRepo(archiveRepoUpdater, "multiRepo.archive.jar");
}
private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException {
if (!testFilesDir.canWrite()) {
return false;
}
File indexJar = TestUtils.copyAssetToDir(context, indexJarPath, testFilesDir);
updater.processDownloadedFile(indexJar);
return true;
}
}

View File

@ -1,372 +0,0 @@
package org.fdroid.fdroid;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Replacer for the netstat utility, by reading the /proc filesystem it can find out the
* open connections of the system
* From http://www.ussg.iu.edu/hypermail/linux/kernel/0409.1/2166.html :
* It will first list all listening TCP sockets, and next list all established
* TCP connections. A typical entry of /proc/net/tcp would look like this (split
* up into 3 parts because of the length of the line):
* <p>
* 46: 010310AC:9C4C 030310AC:1770 01
* | | | | | |--> connection state
* | | | | |------> remote TCP port number
* | | | |-------------> remote IPv4 address
* | | |--------------------> local TCP port number
* | |---------------------------> local IPv4 address
* |----------------------------------> number of entry
* <p>
* 00000150:00000000 01:00000019 00000000
* | | | | |--> number of unrecovered RTO timeouts
* | | | |----------> number of jiffies until timer expires
* | | |----------------> timer_active (see below)
* | |----------------------> receive-queue
* |-------------------------------> transmit-queue
* <p>
* 1000 0 54165785 4 cd1e6040 25 4 27 3 -1
* | | | | | | | | | |--> slow start size threshold,
* | | | | | | | | | or -1 if the treshold
* | | | | | | | | | is >= 0xFFFF
* | | | | | | | | |----> sending congestion window
* | | | | | | | |-------> (ack.quick<<1)|ack.pingpong
* | | | | | | |---------> Predicted tick of soft clock
* | | | | | | (delayed ACK control data)
* | | | | | |------------> retransmit timeout
* | | | | |------------------> location of socket in memory
* | | | |-----------------------> socket reference count
* | | |-----------------------------> inode
* | |----------------------------------> unanswered 0-window probes
* |---------------------------------------------> uid
*
* @author Ciprian Dobre
*/
public class Netstat {
/**
* Possible values for states in /proc/net/tcp
*/
private static final String[] STATES = {
"ESTBLSH", "SYNSENT", "SYNRECV", "FWAIT1", "FWAIT2", "TMEWAIT",
"CLOSED", "CLSWAIT", "LASTACK", "LISTEN", "CLOSING", "UNKNOWN",
};
/**
* Pattern used when parsing through /proc/net/tcp
*/
private static final Pattern NET_PATTERN = Pattern.compile(
"\\d+:\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+):([\\dA-F]+)\\s+([\\dA-F]+)\\s+" +
"[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+:[\\dA-F]+\\s+[\\dA-F]+\\s+([\\d]+)\\s+[\\d]+\\s+([\\d]+)");
/**
* Utility method that converts an address from a hex representation as founded in /proc to String representation
*/
private static String getAddress(final String hexa) {
try {
// first let's convert the address to Integer
final long v = Long.parseLong(hexa, 16);
// in /proc the order is little endian and java uses big endian order we also need to invert the order
final long adr = (v >>> 24) | (v << 24) |
((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00);
// and now it's time to output the result
return ((adr >> 24) & 0xff) + "." + ((adr >> 16) & 0xff) + "." + ((adr >> 8) & 0xff) + "." + (adr & 0xff);
} catch (Exception ex) {
ex.printStackTrace();
return "0.0.0.0"; // NOPMD
}
}
private static int getInt16(final String hexa) {
try {
return Integer.parseInt(hexa, 16);
} catch (Exception ex) {
ex.printStackTrace();
return -1;
}
}
/*
private static String getPName(final int pid) {
final Pattern pattern = Pattern.compile("Name:\\s*(\\S+)");
try {
BufferedReader in = new BufferedReader(new FileReader("/proc/" + pid + "/status"));
String line;
while ((line = in.readLine()) != null) {
final Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
return matcher.group(1);
}
}
in.close();
} catch (Throwable t) {
// ignored
}
return "UNKNOWN";
}
*/
/**
* Method used to question for the connections currently openned
*
* @return The list of connections (as Connection objects)
*/
public static List<Connection> getConnections() {
final ArrayList<Connection> net = new ArrayList<>();
// read from /proc/net/tcp the list of currently openned socket connections
try {
BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp"));
String line;
while ((line = in.readLine()) != null) { // NOPMD
Matcher matcher = NET_PATTERN.matcher(line);
if (matcher.find()) {
final Connection c = new Connection();
c.setProtocol(Connection.TCP_CONNECTION);
net.add(c);
final String localPortHexa = matcher.group(2);
final String remoteAddressHexa = matcher.group(3);
final String remotePortHexa = matcher.group(4);
final String statusHexa = matcher.group(5);
//final String uid = matcher.group(6);
//final String inode = matcher.group(7);
c.setLocalPort(getInt16(localPortHexa));
c.setRemoteAddress(getAddress(remoteAddressHexa));
c.setRemotePort(getInt16(remotePortHexa));
try {
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
} catch (Exception ex) {
c.setStatus(STATES[11]); // unknown
}
c.setPID(-1); // unknown
c.setPName("UNKNOWN");
}
}
in.close();
} catch (Throwable t) { // NOPMD
// ignored
}
// read from /proc/net/udp the list of currently openned socket connections
try {
BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp"));
String line;
while ((line = in.readLine()) != null) { // NOPMD
Matcher matcher = NET_PATTERN.matcher(line);
if (matcher.find()) {
final Connection c = new Connection();
c.setProtocol(Connection.UDP_CONNECTION);
net.add(c);
final String localPortHexa = matcher.group(2);
final String remoteAddressHexa = matcher.group(3);
final String remotePortHexa = matcher.group(4);
final String statusHexa = matcher.group(5);
//final String uid = matcher.group(6);
//final String inode = matcher.group(7);
c.setLocalPort(getInt16(localPortHexa));
c.setRemoteAddress(getAddress(remoteAddressHexa));
c.setRemotePort(getInt16(remotePortHexa));
try {
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
} catch (Exception ex) {
c.setStatus(STATES[11]); // unknown
}
c.setPID(-1); // unknown
c.setPName("UNKNOWN");
}
}
in.close();
} catch (Throwable t) { // NOPMD
// ignored
}
// read from /proc/net/raw the list of currently openned socket connections
try {
BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw"));
String line;
while ((line = in.readLine()) != null) { // NOPMD
Matcher matcher = NET_PATTERN.matcher(line);
if (matcher.find()) {
final Connection c = new Connection();
c.setProtocol(Connection.RAW_CONNECTION);
net.add(c);
//final String localAddressHexa = matcher.group(1);
final String localPortHexa = matcher.group(2);
final String remoteAddressHexa = matcher.group(3);
final String remotePortHexa = matcher.group(4);
final String statusHexa = matcher.group(5);
//final String uid = matcher.group(6);
//final String inode = matcher.group(7);
c.setLocalPort(getInt16(localPortHexa));
c.setRemoteAddress(getAddress(remoteAddressHexa));
c.setRemotePort(getInt16(remotePortHexa));
try {
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
} catch (Exception ex) {
c.setStatus(STATES[11]); // unknown
}
c.setPID(-1); // unknown
c.setPName("UNKNOWN");
}
}
in.close();
} catch (Throwable t) { // NOPMD
// ignored
}
return net;
}
/**
* Informations about a given connection
*
* @author Ciprian Dobre
*/
public static class Connection {
/**
* Types of connection protocol
***/
public static final byte TCP_CONNECTION = 0;
public static final byte UDP_CONNECTION = 1;
public static final byte RAW_CONNECTION = 2;
/**
* <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1988671591829311032L;
/**
* The protocol of the connection (can be tcp, udp or raw)
*/
protected byte protocol;
/**
* The owner of the connection (username)
*/
protected String powner;
/**
* The pid of the owner process
*/
protected int pid;
/**
* The name of the program owning the connection
*/
protected String pname;
/**
* Local port
*/
protected int localPort;
/**
* Remote address of the connection
*/
protected String remoteAddress;
/**
* Remote port
*/
protected int remotePort;
/**
* Status of the connection
*/
protected String status;
public final byte getProtocol() {
return protocol;
}
public final void setProtocol(final byte protocol) {
this.protocol = protocol;
}
public final String getProtocolAsString() {
switch (protocol) {
case TCP_CONNECTION:
return "TCP";
case UDP_CONNECTION:
return "UDP";
case RAW_CONNECTION:
return "RAW";
}
return "UNKNOWN";
}
public final String getPOwner() {
return powner;
}
public final void setPOwner(final String owner) {
this.powner = owner;
}
public final int getPID() {
return pid;
}
public final void setPID(final int pid) {
this.pid = pid;
}
public final String getPName() {
return pname;
}
public final void setPName(final String pname) {
this.pname = pname;
}
public final int getLocalPort() {
return localPort;
}
public final void setLocalPort(final int localPort) {
this.localPort = localPort;
}
public final String getRemoteAddress() {
return remoteAddress;
}
public final void setRemoteAddress(final String remoteAddress) {
this.remoteAddress = remoteAddress;
}
public final int getRemotePort() {
return remotePort;
}
public final void setRemotePort(final int remotePort) {
this.remotePort = remotePort;
}
public final String getStatus() {
return status;
}
public final void setStatus(final String status) {
this.status = status;
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[Prot=").append(getProtocolAsString());
buf.append(",POwner=").append(powner);
buf.append(",PID=").append(pid);
buf.append(",PName=").append(pname);
buf.append(",LPort=").append(localPort);
buf.append(",RAddress=").append(remoteAddress);
buf.append(",RPort=").append(remotePort);
buf.append(",Status=").append(status);
buf.append("]");
return buf.toString();
}
}
}

View File

@ -0,0 +1,153 @@
package org.fdroid.fdroid;
import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.fdroid.fdroid.RepoUpdater.UpdateException;
import org.fdroid.fdroid.data.Repo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class RepoUpdaterTest {
private static final String TAG = "RepoUpdaterTest";
private Context context;
private RepoUpdater repoUpdater;
private File testFilesDir;
String simpleIndexPubkey = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b";
@Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
context = instrumentation.getContext();
testFilesDir = TestUtils.getWriteableDir(instrumentation);
Repo repo = new Repo();
repo.pubkey = this.simpleIndexPubkey;
repoUpdater = new RepoUpdater(context, repo);
}
@Test
public void testExtractIndexFromJar() {
if (!testFilesDir.canWrite()) {
return;
}
File simpleIndexJar = TestUtils.copyAssetToDir(context, "simpleIndex.jar", testFilesDir);
// these are supposed to succeed
try {
repoUpdater.processDownloadedFile(simpleIndexJar);
} catch (UpdateException e) {
e.printStackTrace();
fail();
}
}
@Test(expected = UpdateException.class)
public void testExtractIndexFromJarWithoutSignatureJar() throws UpdateException {
if (!testFilesDir.canWrite()) {
return;
}
// this is supposed to fail
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithoutSignature.jar", testFilesDir);
repoUpdater.processDownloadedFile(jarFile);
}
@Test
public void testExtractIndexFromJarWithCorruptedManifestJar() {
if (!testFilesDir.canWrite()) {
return;
}
// this is supposed to fail
try {
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedManifest.jar", testFilesDir);
repoUpdater.processDownloadedFile(jarFile);
fail();
} catch (UpdateException e) {
e.printStackTrace();
fail();
} catch (SecurityException e) {
// success!
}
}
@Test
public void testExtractIndexFromJarWithCorruptedSignature() {
if (!testFilesDir.canWrite()) {
return;
}
// this is supposed to fail
try {
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedSignature.jar", testFilesDir);
repoUpdater.processDownloadedFile(jarFile);
fail();
} catch (UpdateException e) {
e.printStackTrace();
fail();
} catch (SecurityException e) {
// success!
}
}
@Test
public void testExtractIndexFromJarWithCorruptedCertificate() {
if (!testFilesDir.canWrite()) {
return;
}
// this is supposed to fail
try {
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedCertificate.jar", testFilesDir);
repoUpdater.processDownloadedFile(jarFile);
fail();
} catch (UpdateException e) {
e.printStackTrace();
fail();
} catch (SecurityException e) {
// success!
}
}
@Test
public void testExtractIndexFromJarWithCorruptedEverything() {
if (!testFilesDir.canWrite()) {
return;
}
// this is supposed to fail
try {
File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedEverything.jar", testFilesDir);
repoUpdater.processDownloadedFile(jarFile);
fail();
} catch (UpdateException e) {
e.printStackTrace();
fail();
} catch (SecurityException e) {
// success!
}
}
@Test
public void testExtractIndexFromMasterKeyIndexJar() {
if (!testFilesDir.canWrite()) {
return;
}
// this is supposed to fail
try {
File jarFile = TestUtils.copyAssetToDir(context, "masterKeyIndex.jar", testFilesDir);
repoUpdater.processDownloadedFile(jarFile);
fail(); //NOPMD
} catch (UpdateException e) {
// success!
} catch (SecurityException e) {
// success!
}
}
}

View File

@ -1,119 +1,48 @@
/*
* Copyright (C) 2016 Blue Jay Wireless
* Copyright (C) 2015 Daniel Martí <mvdan@mvdan.cc>
* Copyright (C) 2014-2016 Hans-Christoph Steiner <hans@eds.org>
* Copyright (C) 2014-2016 Peter Serwylo <peter@serwylo.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.data;
package org.fdroid.fdroid;
import android.support.annotation.NonNull;
import android.support.test.runner.AndroidJUnit4;
import android.text.TextUtils;
import android.util.Log;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.mock.MockRepo;
import org.fdroid.fdroid.mock.RepoDetails;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import androidx.annotation.NonNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(RobolectricTestRunner.class)
@RunWith(AndroidJUnit4.class)
public class RepoXMLHandlerTest {
private static final String TAG = "RepoXMLHandlerTest";
private static final String FAKE_SIGNING_CERT = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345"; // NOCHECKSTYLE LineLength
/**
* Set to random time zone to make sure that the dates are properly parsed.
*/
@BeforeClass
public static void setRandomTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT-%d:%02d",
System.currentTimeMillis() % 12, System.currentTimeMillis() % 60)));
System.out.println("TIME ZONE for this test: " + TimeZone.getDefault());
}
@Test
public void testExtendedPerms() throws IOException {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"; // NOCHECKSTYLE LineLength
expectedRepo.description = "This is just a test of the extended permissions attributes.";
expectedRepo.timestamp = 1467169032;
RepoDetails actualDetails = getFromFile("extendedPerms.xml");
handlerTestSuite(expectedRepo, actualDetails, 2, 6, 14, 16);
}
@Test
public void testObbIndex() throws IOException {
writeResourceToObbDir("main.1101613.obb.main.twoversions.obb");
writeResourceToObbDir("main.1101615.obb.main.twoversions.obb");
writeResourceToObbDir("main.1434483388.obb.main.oldversion.obb");
writeResourceToObbDir("main.1619.obb.mainpatch.current.obb");
writeResourceToObbDir("patch.1619.obb.mainpatch.current.obb");
RepoDetails actualDetails = getFromFile("obbIndex.xml");
for (Apk indexApk : actualDetails.apks) {
Apk localApk = new Apk();
localApk.packageName = indexApk.packageName;
localApk.versionCode = indexApk.versionCode;
localApk.hashType = indexApk.hashType;
App.initInstalledObbFiles(localApk);
assertEquals(indexApk.obbMainFile, localApk.obbMainFile);
assertEquals(indexApk.obbMainFileSha256, localApk.obbMainFileSha256);
assertEquals(indexApk.obbPatchFile, localApk.obbPatchFile);
assertEquals(indexApk.obbPatchFileSha256, localApk.obbPatchFileSha256);
}
}
private static final String FAKE_PUBKEY = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345";
@Test
public void testSimpleIndex() {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b"; // NOCHECKSTYLE LineLength
expectedRepo.description = "The official repository of the F-Droid client. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitorious.org/f-droid."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1398733213;
expectedRepo.pubkey = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b";
expectedRepo.description = "The official repository of the F-Droid client. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitorious.org/f-droid.";
RepoDetails actualDetails = getFromFile("simpleIndex.xml");
handlerTestSuite(expectedRepo, actualDetails, 0, 0, -1, 12);
}
@ -122,9 +51,8 @@ public class RepoXMLHandlerTest {
public void testSmallRepo() {
Repo expectedRepo = new Repo();
expectedRepo.name = "Android-Nexus-7-20139453 on UNSET";
expectedRepo.signingCertificate = "308202da308201c2a00302010202080eb08c796fec91aa300d06092a864886f70d0101050500302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a656374301e170d3134313030333135303631325a170d3135313030333135303631325a302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a0282010100c7ab44b130be5c00eedcc3625462f6f6ac26e502641cd641f3e30cbb0ff1ba325158611e7fc2448a35b6a6df30dc6e23602cf6909448befcf11e2fe486b580f1e76fe5887d159050d00afd2c4079f6538896bb200627f4b3e874f011ce5df0fef5d150fcb0b377b531254e436eaf4083ea72fe3b8c3ef450789fa858f2be8f6c5335bb326aff3dda689fbc7b5ba98dea53651dbea7452c38d294985ac5dd8a9e491a695de92c706d682d6911411fcaef3b0a08a030fe8a84e47acaab0b7edcda9d190ce39e810b79b1d8732eca22b15f0d048c8d6f00503a7ee81ab6e08919ff465883432304d95238b95e95c5f74e0a421809e2a6a85825aed680e0d6939e8f0203010001300d06092a864886f70d010105050003820101006d17aad3271b8b2c299dbdb7b1182849b0d5ddb9f1016dcb3487ae0db02b6be503344c7d066e2050bcd01d411b5ee78c7ed450f0ff9da5ce228f774cbf41240361df53d9c6078159d16f4d34379ab7dedf6186489397c83b44b964251a2ebb42b7c4689a521271b1056d3b5a5fa8f28ba64fb8ce5e2226c33c45d27ba3f632dc266c12abf582b8438c2abcf3eae9de9f31152b4158ace0ef33435c20eb809f1b3988131db6e5a1442f2617c3491d9565fedb3e320e8df4236200d3bd265e47934aa578f84d0d1a5efeb49b39907e876452c46996d0feff9404b41aa5631b4482175d843d5512ded45e12a514690646492191e7add434afce63dbff8f0b03ec0c"; // NOCHECKSTYLE LineLength
expectedRepo.pubkey = "308202da308201c2a00302010202080eb08c796fec91aa300d06092a864886f70d0101050500302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a656374301e170d3134313030333135303631325a170d3135313030333135303631325a302d3111300f060355040a0c084b6572706c61707031183016060355040b0c0f477561726469616e50726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a0282010100c7ab44b130be5c00eedcc3625462f6f6ac26e502641cd641f3e30cbb0ff1ba325158611e7fc2448a35b6a6df30dc6e23602cf6909448befcf11e2fe486b580f1e76fe5887d159050d00afd2c4079f6538896bb200627f4b3e874f011ce5df0fef5d150fcb0b377b531254e436eaf4083ea72fe3b8c3ef450789fa858f2be8f6c5335bb326aff3dda689fbc7b5ba98dea53651dbea7452c38d294985ac5dd8a9e491a695de92c706d682d6911411fcaef3b0a08a030fe8a84e47acaab0b7edcda9d190ce39e810b79b1d8732eca22b15f0d048c8d6f00503a7ee81ab6e08919ff465883432304d95238b95e95c5f74e0a421809e2a6a85825aed680e0d6939e8f0203010001300d06092a864886f70d010105050003820101006d17aad3271b8b2c299dbdb7b1182849b0d5ddb9f1016dcb3487ae0db02b6be503344c7d066e2050bcd01d411b5ee78c7ed450f0ff9da5ce228f774cbf41240361df53d9c6078159d16f4d34379ab7dedf6186489397c83b44b964251a2ebb42b7c4689a521271b1056d3b5a5fa8f28ba64fb8ce5e2226c33c45d27ba3f632dc266c12abf582b8438c2abcf3eae9de9f31152b4158ace0ef33435c20eb809f1b3988131db6e5a1442f2617c3491d9565fedb3e320e8df4236200d3bd265e47934aa578f84d0d1a5efeb49b39907e876452c46996d0feff9404b41aa5631b4482175d843d5512ded45e12a514690646492191e7add434afce63dbff8f0b03ec0c";
expectedRepo.description = "A local FDroid repo generated from apps installed on Android-Nexus-7-20139453";
expectedRepo.timestamp = 1412696461;
RepoDetails actualDetails = getFromFile("smallRepo.xml");
handlerTestSuite(expectedRepo, actualDetails, 12, 12, 14, -1);
checkIncludedApps(actualDetails.apps, new String[]{
@ -141,125 +69,14 @@ public class RepoXMLHandlerTest {
"org.gege.caldavsyncadapter",
"info.guardianproject.checkey",
});
for (App app : actualDetails.apps) {
if ("org.mozilla.firefox".equals(app.packageName)) {
assertEquals(1411776000000L, app.added.getTime());
assertEquals(1411862400000L, app.lastUpdated.getTime());
}
}
}
@Test(expected = IllegalArgumentException.class)
public void testSimpleIndexWithCorruptedPackageName() throws Throwable {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b"; // NOCHECKSTYLE LineLength
expectedRepo.description = "The official repository of the F-Droid client. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitorious.org/f-droid."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1398733213;
InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("simpleIndexWithCorruptedPackageName.xml");
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
RepoDetails repoDetails = new RepoDetails();
MockRepo mockRepo = new MockRepo(100, Repo.PUSH_REQUEST_IGNORE);
RepoXMLHandler handler = new RepoXMLHandler(mockRepo, repoDetails);
reader.setContentHandler(handler);
InputSource is = new InputSource(new BufferedInputStream(inputStream));
try {
reader.parse(is);
} catch (org.xml.sax.SAXException e) {
throw e.getCause();
}
fail();
}
@Test
public void testPushRequestsRepoIgnore() {
Repo expectedRepo = new Repo();
expectedRepo.name = "non-public test repo";
expectedRepo.signingCertificate = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
expectedRepo.description = "This is a repository of apps to be used with F-Droid. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitlab.com/u/fdroid."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1472071347;
RepoDetails actualDetails = getFromFile("pushRequestsIndex.xml", Repo.PUSH_REQUEST_IGNORE);
handlerTestSuite(expectedRepo, actualDetails, 2, 14, -1, 17);
checkPushRequests(actualDetails);
List<RepoPushRequest> repoPushRequests = actualDetails.repoPushRequestList;
assertNotNull(repoPushRequests);
assertEquals(0, repoPushRequests.size());
}
@Test
public void testPushRequestsRepoAlways() {
Repo expectedRepo = new Repo();
expectedRepo.name = "non-public test repo";
expectedRepo.signingCertificate = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b06035504031304736f7661301e170d3136303832333133333131365a170d3434303130393133333131365a30213110300e060355040b1307462d44726f6964310d300b06035504031304736f766130820222300d06092a864886f70d01010105000382020f003082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e2179930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a88f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e4564d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d4775cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d57f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f04f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f120334812693a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da80401c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbebe3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b05000382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa64bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8de58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d9085927634072c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fdb8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5abe89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d060711f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350cb55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2d70f080ac6"; // NOCHECKSTYLE LineLength
expectedRepo.description = "This is a repository of apps to be used with F-Droid. Applications in this repository are either official binaries built by the original application developers, or are binaries built from source by the admin of f-droid.org using the tools on https://gitlab.com/u/fdroid."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1472071347;
RepoDetails actualDetails = getFromFile("pushRequestsIndex.xml", Repo.PUSH_REQUEST_ACCEPT_ALWAYS);
handlerTestSuite(expectedRepo, actualDetails, 2, 14, -1, 17);
checkPushRequests(actualDetails);
List<RepoPushRequest> repoPushRequests = actualDetails.repoPushRequestList;
assertNotNull(repoPushRequests);
assertEquals(6, repoPushRequests.size());
}
@Test
public void testPushRequestsRepoCorruption() {
RepoPushRequest repoPushRequest;
repoPushRequest = new RepoPushRequest(null, null, null); // request with no data
assertEquals(repoPushRequest.request, null);
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "org.fdroid.fdroid", "999999999999");
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "org.fdroid.fdroid",
String.valueOf(((long) Integer.MAX_VALUE) + 1));
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "org.fdroid.fdroid",
String.valueOf(((long) Integer.MIN_VALUE) - 1));
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("Robert'); DROP TABLE Students; --", "org.fdroid.fdroid", null);
assertEquals(repoPushRequest.request, null);
assertEquals(repoPushRequest.packageName, "org.fdroid.fdroid");
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "Robert'); DROP TABLE Students; --", "123.1.1");
assertEquals(repoPushRequest.request, "install");
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, null);
repoPushRequest = new RepoPushRequest("install", "--", "123");
assertEquals(repoPushRequest.request, "install");
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, Integer.valueOf(123));
repoPushRequest = new RepoPushRequest("uninstall", "Robert'); DROP TABLE Students; --", "123");
assertEquals(repoPushRequest.request, "uninstall");
assertEquals(repoPushRequest.packageName, null);
assertEquals(repoPushRequest.versionCode, Integer.valueOf(123));
repoPushRequest = new RepoPushRequest("badrquest", "asdfasdfasdf", "123");
assertEquals(repoPushRequest.request, null);
assertEquals(repoPushRequest.packageName, "asdfasdfasdf");
assertEquals(repoPushRequest.versionCode, Integer.valueOf(123));
}
@Test
public void testMediumRepo() {
Repo expectedRepo = new Repo();
expectedRepo.name = "Guardian Project Official Releases";
expectedRepo.signingCertificate = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f"; // NOCHECKSTYLE LineLength
expectedRepo.description = "The official app repository of The Guardian Project. Applications in this repository are official binaries build by the original application developers and signed by the same key as the APKs that are released in the Google Play store."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1411427879;
expectedRepo.pubkey = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f";
expectedRepo.description = "The official app repository of The Guardian Project. Applications in this repository are official binaries build by the original application developers and signed by the same key as the APKs that are released in the Google Play store.";
RepoDetails actualDetails = getFromFile("mediumRepo.xml");
handlerTestSuite(expectedRepo, actualDetails, 15, 36, 60, 12);
checkIncludedApps(actualDetails.apps, new String[]{
@ -285,70 +102,10 @@ public class RepoXMLHandlerTest {
public void testLargeRepo() {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"; // NOCHECKSTYLE LineLength
expectedRepo.description = "The official FDroid repository. Applications in this repository are mostly built directory from the source code. Some are official binaries built by the original application developers - these will be replaced by source-built versions over time."; // NOCHECKSTYLE LineLength
expectedRepo.timestamp = 1412746769;
expectedRepo.pubkey = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef";
expectedRepo.description = "The official FDroid repository. Applications in this repository are mostly built directory from the source code. Some are official binaries built by the original application developers - these will be replaced by source-built versions over time.";
RepoDetails actualDetails = getFromFile("largeRepo.xml");
handlerTestSuite(expectedRepo, actualDetails, 1211, 2381, 14, 12);
// Generated using something like the following:
// sed 's,<application,\n<application,g' largeRepo.xml | grep "antifeatures" | sed 's,.*id="\(.*\)".*<antifeatures>\(.*\)</antifeatures>.*,\1 \2,p' | sort | uniq // NOCHECKSTYLE LineLength
Map<String, List<String>> expectedAntiFeatures = new HashMap<>();
expectedAntiFeatures.put("org.fdroid.fdroid", new ArrayList<String>());
expectedAntiFeatures.put("org.adblockplus.android", Arrays.asList("Tracking", "Ads"));
expectedAntiFeatures.put("org.microg.nlp.backend.apple", Arrays.asList("Tracking", "NonFreeNet"));
expectedAntiFeatures.put("com.ds.avare", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("com.miracleas.bitcoin_spinner", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("de.Cherubin7th.blackscreenpresentationremote", Collections.singletonList("Ads"));
expectedAntiFeatures.put("budo.budoist", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("no.rkkc.bysykkel", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("com.jadn.cc", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.atai.TessUI", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("org.zephyrsoft.checknetwork", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("de.bashtian.dashclocksunrise", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("org.geometerplus.zlibrary.ui.android", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("org.mozilla.firefox", Arrays.asList("NonFreeAdd", "Tracking"));
expectedAntiFeatures.put("com.gmail.charleszq", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("it.andreascarpino.forvodroid", Arrays.asList("NonFreeNet", "NonFreeDep"));
expectedAntiFeatures.put("de.b0nk.fp1_epo_autoupdate", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.blogspot.tonyatkins.freespeech", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.frostwire.android", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.namsor.api.samples.gendre", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.github.mobile", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.cradle.iitc_mobile", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.matteopacini.katana", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("de.enaikoon.android.keypadmapper3", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.linphone", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("ch.rrelmy.android.locationcachemap", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("com.powerpoint45.lucidbrowser", Arrays.asList("Ads", "NonFreeDep"));
expectedAntiFeatures.put("org.mixare", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("apps.droidnotify", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("com.numix.calculator", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("com.numix.icons_circle", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("com.gh4a", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("at.tomtasche.reader", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("de.uni_potsdam.hpi.openmensa", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("net.osmand.plus", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("byrne.utilities.pasteedroid", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.bwx.bequick", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("be.geecko.QuickLyric", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.wanghaus.remembeer", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("cri.sanity", Collections.singletonList("Ads"));
expectedAntiFeatures.put("com.showmehills", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.akop.bach", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("org.dmfs.tasks", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("org.telegram.messenger", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.danvelazco.fbwrapper", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.zephyrsoft.trackworktime", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("org.transdroid", Collections.singletonList("Tracking"));
expectedAntiFeatures.put("com.lonepulse.travisjr", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("com.twsitedapps.homemanager", Collections.singletonList("NonFreeAdd"));
expectedAntiFeatures.put("org.zeitgeist.movement", Collections.singletonList("NonFreeDep"));
expectedAntiFeatures.put("net.wigle.wigleandroid", Collections.singletonList("NonFreeNet"));
expectedAntiFeatures.put("org.nick.wwwjdic", Collections.singletonList("Tracking"));
checkAntiFeatures(actualDetails.apps, expectedAntiFeatures);
/*
* generated using: sed 's,<application,\n<application,g' largeRepo.xml
* | sed -n 's,.*id="\(.[^"]*\)".*,"\1"\,,p'
@ -836,22 +593,6 @@ public class RepoXMLHandlerTest {
});
}
private void checkAntiFeatures(List<App> apps, Map<String, List<String>> expectedAntiFeatures) {
for (App app : apps) {
if (expectedAntiFeatures.containsKey(app.packageName)) {
List<String> antiFeatures = expectedAntiFeatures.get(app.packageName);
if (antiFeatures.isEmpty()) {
assertNull(app.antiFeatures);
} else {
List<String> actualAntiFeatures = new ArrayList<>();
Collections.addAll(actualAntiFeatures, app.antiFeatures);
assertTrue(actualAntiFeatures.containsAll(antiFeatures));
assertTrue(antiFeatures.containsAll(actualAntiFeatures));
}
}
}
}
private void checkIncludedApps(List<App> actualApps, String[] expctedAppIds) {
assertNotNull(actualApps);
assertNotNull(expctedAppIds);
@ -868,37 +609,12 @@ public class RepoXMLHandlerTest {
}
}
private void checkPushRequests(RepoDetails actualDetails) {
final Object[] expectedPushRequestsIndex = new Object[]{
"install", "org.fdroid.fdroid", 101002,
"install", "org.fdroid.fdroid.privileged", null,
"uninstall", "com.android.vending", null,
"uninstall", "com.facebook.orca", -12345,
"uninstall", null, null, // request with no data
"install", "asdfasdfasdf", null, // non-existent app
};
checkIncludedApps(actualDetails.apps, new String[]{
"org.fdroid.fdroid", "org.fdroid.fdroid.privileged",
});
List<RepoPushRequest> repoPushRequestList = actualDetails.repoPushRequestList;
int i = 0;
for (RepoPushRequest repoPushRequest : repoPushRequestList) {
assertEquals(repoPushRequest.request, expectedPushRequestsIndex[i]);
assertEquals(repoPushRequest.packageName, expectedPushRequestsIndex[i + 1]);
assertEquals(repoPushRequest.versionCode, expectedPushRequestsIndex[i + 2]);
i += 3;
}
}
private void handlerTestSuite(Repo expectedRepo, RepoDetails actualDetails,
int appCount, int apkCount, int maxAge, int version) {
private void handlerTestSuite(Repo expectedRepo, RepoDetails actualDetails, int appCount, int apkCount, int maxAge, int version) {
assertNotNull(actualDetails);
assertFalse(TextUtils.isEmpty(actualDetails.signingCert));
assertEquals(expectedRepo.signingCertificate.length(), actualDetails.signingCert.length());
assertEquals(expectedRepo.signingCertificate, actualDetails.signingCert);
assertFalse(FAKE_SIGNING_CERT.equals(actualDetails.signingCert));
assertEquals(expectedRepo.pubkey.length(), actualDetails.signingCert.length());
assertEquals(expectedRepo.pubkey, actualDetails.signingCert);
assertFalse(FAKE_PUBKEY.equals(actualDetails.signingCert));
assertFalse(TextUtils.isEmpty(actualDetails.name));
assertEquals(expectedRepo.name.length(), actualDetails.name.length());
@ -910,44 +626,67 @@ public class RepoXMLHandlerTest {
assertEquals(actualDetails.maxAge, maxAge);
assertEquals(actualDetails.version, version);
assertEquals(expectedRepo.timestamp, actualDetails.timestamp);
List<App> apps = actualDetails.apps;
assertNotNull(apps);
assertEquals(apps.size(), appCount);
for (App app : apps) {
assertTrue("Added should have been set", app.added.getTime() > 0);
assertTrue("Last Updated should have been set", app.lastUpdated.getTime() > 0);
}
List<Apk> apks = actualDetails.apks;
assertNotNull(apks);
assertEquals(apks.size(), apkCount);
}
private static class RepoDetails implements RepoXMLHandler.IndexReceiver {
public String name;
public String description;
public String signingCert;
public int maxAge;
public int version;
public List<Apk> apks = new ArrayList<>();
public List<App> apps = new ArrayList<>();
@Override
public void receiveRepo(String name, String description, String signingCert, int maxage, int version) {
this.name = name;
this.description = description;
this.signingCert = signingCert;
this.maxAge = maxage;
this.version = version;
}
@Override
public void receiveApp(App app, List<Apk> packages) {
apks.addAll(packages);
apps.add(app);
}
}
@NonNull
private RepoDetails getFromFile(String indexFilename) {
return getFromFile(indexFilename, Repo.PUSH_REQUEST_IGNORE);
SAXParser parser;
try {
parser = SAXParserFactory.newInstance().newSAXParser();
XMLReader reader = parser.getXMLReader();
RepoDetails repoDetails = new RepoDetails();
RepoXMLHandler handler = new RepoXMLHandler(new MockRepo(100), repoDetails);
reader.setContentHandler(handler);
String resName = "assets/" + indexFilename;
Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName));
InputStream input = getClass().getClassLoader().getResourceAsStream(resName);
InputSource is = new InputSource(new BufferedInputStream(input));
reader.parse(is);
return repoDetails;
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
fail();
// Satisfies the compiler, but fail() will always throw a runtime exception so we never
// reach this return statement.
return null;
}
}
@NonNull
private RepoDetails getFromFile(String indexFilename, int pushRequests) {
return getFromFile(getClass().getClassLoader(), indexFilename, pushRequests);
}
@NonNull
public static RepoDetails getFromFile(ClassLoader classLoader, String indexFilename, int pushRequests) {
Log.i(TAG, "test file: " + classLoader.getResource(indexFilename));
InputStream inputStream = classLoader.getResourceAsStream(indexFilename);
return RepoDetails.getFromFile(inputStream, pushRequests);
}
private void writeResourceToObbDir(String assetName) throws IOException {
InputStream input = getClass().getClassLoader().getResourceAsStream(assetName);
String packageName = assetName.substring(assetName.indexOf("obb"),
assetName.lastIndexOf('.'));
File f = new File(App.getObbDir(packageName), assetName);
FileUtils.copyToFile(input, f);
input.close();
}
}

View File

@ -1,62 +0,0 @@
package org.fdroid.fdroid;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.util.Log;
import java.lang.reflect.Method;
/**
* @see <a href="https://artemzin.com/blog/easiest-way-to-give-set_animation_scale-permission-for-your-ui-tests-on-android/>EASIEST WAY TO GIVE SET_ANIMATION_SCALE PERMISSION FOR YOUR UI TESTS ON ANDROID</a>
* @see <a href="https://gist.github.com/xrigau/11284124>Disable animations for Espresso tests</a>
*/
class SystemAnimations {
public static final String TAG = "SystemAnimations";
private static final float DISABLED = 0.0f;
private static final float DEFAULT = 1.0f;
static void disableAll(Context context) {
int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE);
if (permStatus == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Manifest.permission.SET_ANIMATION_SCALE PERMISSION_GRANTED");
setSystemAnimationsScale(DISABLED);
} else {
Log.i(TAG, "Disabling Manifest.permission.SET_ANIMATION_SCALE failed: " + permStatus);
}
}
static void enableAll(Context context) {
int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE);
if (permStatus == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Manifest.permission.SET_ANIMATION_SCALE PERMISSION_GRANTED");
setSystemAnimationsScale(DEFAULT);
} else {
Log.i(TAG, "Enabling Manifest.permission.SET_ANIMATION_SCALE failed: " + permStatus);
}
}
private static void setSystemAnimationsScale(float animationScale) {
try {
Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
Method asInterface = windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
Method setAnimationScales = windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");
IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
for (int i = 0; i < currentScales.length; i++) {
currentScales[i] = animationScale;
}
setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
} catch (Exception e) {
Log.e(TAG, "Could not change animation scale to " + animationScale + " :'(");
}
}
}

View File

@ -0,0 +1,250 @@
package org.fdroid.fdroid;
import android.app.Instrumentation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.util.Log;
import junit.framework.AssertionFailedError;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.receiver.PackageAddedReceiver;
import org.fdroid.fdroid.receiver.PackageRemovedReceiver;
import org.fdroid.fdroid.receiver.PackageUpgradedReceiver;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import mock.MockContextSwappableComponents;
import mock.MockInstallablePackageManager;
public class TestUtils {
private static final String TAG = "TestUtils";
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, List<T> expectedList) {
List<T> actualList = new ArrayList<>(actualArray.length);
Collections.addAll(actualList, actualArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualArray, expectedList);
}
public static <T> String listToString(List<T> list) {
String string = "[";
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
string += ", ";
}
string += "'" + list.get(i) + "'";
}
string += "]";
return string;
}
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, List<T> expectedContains) {
if (actualList.size() != expectedContains.size()) {
String message =
"List sizes don't match.\n" +
"Expected: " +
listToString(expectedContains) + "\n" +
"Actual: " +
listToString(actualList);
throw new AssertionFailedError(message);
}
for (T required : expectedContains) {
boolean containsRequired = false;
for (T itemInList : actualList) {
if (required.equals(itemInList)) {
containsRequired = true;
break;
}
}
if (!containsRequired) {
String message =
"List doesn't contain \"" + required + "\".\n" +
"Expected: " +
listToString(expectedContains) + "\n" +
"Actual: " +
listToString(actualList);
throw new AssertionFailedError(message);
}
}
}
public static void insertApp(ContentResolver resolver, String appId, String name) {
insertApp(resolver, appId, name, new ContentValues());
}
public static void insertApp(ContentResolver resolver, String id, String name, ContentValues additionalValues) {
ContentValues values = new ContentValues();
values.put(AppProvider.DataColumns.PACKAGE_NAME, id);
values.put(AppProvider.DataColumns.NAME, name);
// Required fields (NOT NULL in the database).
values.put(AppProvider.DataColumns.SUMMARY, "test summary");
values.put(AppProvider.DataColumns.DESCRIPTION, "test description");
values.put(AppProvider.DataColumns.LICENSE, "GPL?");
values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1);
values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0);
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0);
values.putAll(additionalValues);
Uri uri = AppProvider.getContentUri();
resolver.insert(uri, values);
}
public static Uri insertApk(FDroidProviderTest<ApkProvider> providerTest, String id, int versionCode) {
return insertApk(providerTest, id, versionCode, new ContentValues());
}
public static Uri insertApk(FDroidProviderTest<ApkProvider> providerTest, String id, int versionCode, ContentValues additionalValues) {
ContentValues values = new ContentValues();
values.put(ApkProvider.DataColumns.PACKAGE_NAME, id);
values.put(ApkProvider.DataColumns.VERSION_CODE, versionCode);
// Required fields (NOT NULL in the database).
values.put(ApkProvider.DataColumns.REPO_ID, 1);
values.put(ApkProvider.DataColumns.VERSION, "The good one");
values.put(ApkProvider.DataColumns.HASH, "11111111aaaaaaaa");
values.put(ApkProvider.DataColumns.NAME, "Test Apk");
values.put(ApkProvider.DataColumns.SIZE, 10000);
values.put(ApkProvider.DataColumns.IS_COMPATIBLE, 1);
values.putAll(additionalValues);
Uri uri = ApkProvider.getContentUri();
return providerTest.getMockContentResolver().insert(uri, values);
}
/**
* Will tell {@code pm} that we are installing {@code appId}, and then alert the
* {@link org.fdroid.fdroid.receiver.PackageAddedReceiver}. This will in turn update the
* "installed apps" table in the database.
*/
public static void installAndBroadcast(MockContextSwappableComponents context,
MockInstallablePackageManager pm, String appId,
int versionCode, String versionName) {
context.setPackageManager(pm);
pm.install(appId, versionCode, versionName);
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
installIntent.setData(Uri.parse("package:" + appId));
new PackageAddedReceiver().onReceive(context, installIntent);
}
/**
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
*/
public static void upgradeAndBroadcast(MockContextSwappableComponents context,
MockInstallablePackageManager pm, String appId,
int versionCode, String versionName) {
/*
removeAndBroadcast(context, pm, appId);
installAndBroadcast(context, pm, appId, versionCode, versionName);
*/
context.setPackageManager(pm);
pm.install(appId, versionCode, versionName);
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
installIntent.setData(Uri.parse("package:" + appId));
new PackageUpgradedReceiver().onReceive(context, installIntent);
}
/**
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
*/
public static void removeAndBroadcast(MockContextSwappableComponents context, MockInstallablePackageManager pm, String appId) {
context.setPackageManager(pm);
pm.remove(appId);
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
installIntent.setData(Uri.parse("package:" + appId));
new PackageRemovedReceiver().onReceive(context, installIntent);
}
@Nullable
public static File copyAssetToDir(Context context, String assetName, File directory) {
File tempFile;
InputStream input = null;
OutputStream output = null;
try {
tempFile = File.createTempFile(assetName + "-", ".testasset", directory);
Log.d(TAG, "Copying asset file " + assetName + " to directory " + directory);
input = context.getAssets().open(assetName);
output = new FileOutputStream(tempFile);
Utils.copy(input, output);
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
Utils.closeQuietly(output);
Utils.closeQuietly(input);
}
return tempFile;
}
/**
* Prefer internal over external storage, because external tends to be FAT filesystems,
* which don't support symlinks (which we test using this method).
*/
public static File getWriteableDir(Instrumentation instrumentation) {
Context context = instrumentation.getContext();
Context targetContext = instrumentation.getTargetContext();
File[] dirsToTry = new File[]{
context.getCacheDir(),
context.getFilesDir(),
targetContext.getCacheDir(),
targetContext.getFilesDir(),
context.getExternalCacheDir(),
context.getExternalFilesDir(null),
targetContext.getExternalCacheDir(),
targetContext.getExternalFilesDir(null),
Environment.getExternalStorageDirectory(),
};
return getWriteableDir(dirsToTry);
}
private static File getWriteableDir(File[] dirsToTry) {
for (File dir : dirsToTry) {
if (dir != null && dir.canWrite()) {
return dir;
}
}
return null;
}
}

View File

@ -1,156 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fdroid.fdroid;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;
import androidx.test.uiautomator.UiWatcher;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SuppressWarnings("MemberName")
public class UiWatchers {
private static final String LOG_TAG = UiWatchers.class.getSimpleName();
private final List<String> mErrors = new ArrayList<String>();
/**
* We can use the UiDevice registerWatcher to register a small script to be executed when the
* framework is waiting for a control to appear. Waiting may be the cause of an unexpected
* dialog on the screen and it is the time when the framework runs the registered watchers.
* This is a sample watcher looking for ANR and crashes. it closes it and moves on. You should
* create your own watchers and handle error logging properly for your type of tests.
*/
public void registerAnrAndCrashWatchers() {
UiDevice.getInstance().registerWatcher("ANR", new UiWatcher() {
@Override
public boolean checkForCondition() {
UiObject window = new UiObject(new UiSelector().className(
"com.android.server.am.AppNotRespondingDialog"));
String errorText = null;
if (window.exists()) {
try {
errorText = window.getText();
} catch (UiObjectNotFoundException e) {
Log.e(LOG_TAG, "dialog gone?", e);
}
onAnrDetected(errorText);
postHandler("Wait");
return true; // triggered
}
return false; // no trigger
}
});
// class names may have changed
UiDevice.getInstance().registerWatcher("ANR2", new UiWatcher() {
@Override
public boolean checkForCondition() {
UiObject window = new UiObject(new UiSelector().packageName("android")
.textContains("isn't responding."));
if (window.exists()) {
String errorText = null;
try {
errorText = window.getText();
} catch (UiObjectNotFoundException e) {
Log.e(LOG_TAG, "dialog gone?", e);
}
onAnrDetected(errorText);
postHandler("Wait");
return true; // triggered
}
return false; // no trigger
}
});
UiDevice.getInstance().registerWatcher("CRASH", new UiWatcher() {
@Override
public boolean checkForCondition() {
UiObject window = new UiObject(new UiSelector().className(
"com.android.server.am.AppErrorDialog"));
if (window.exists()) {
String errorText = null;
try {
errorText = window.getText();
} catch (UiObjectNotFoundException e) {
Log.e(LOG_TAG, "dialog gone?", e);
}
onCrashDetected(errorText);
postHandler("OK");
return true; // triggered
}
return false; // no trigger
}
});
UiDevice.getInstance().registerWatcher("CRASH2", new UiWatcher() {
@Override
public boolean checkForCondition() {
UiObject window = new UiObject(new UiSelector().packageName("android")
.textContains("has stopped"));
if (window.exists()) {
String errorText = null;
try {
errorText = window.getText();
} catch (UiObjectNotFoundException e) {
Log.e(LOG_TAG, "dialog gone?", e);
}
onCrashDetected(errorText);
postHandler("OK");
return true; // triggered
}
return false; // no trigger
}
});
Log.i(LOG_TAG, "Registered GUI Exception watchers");
}
public void onAnrDetected(String errorText) {
mErrors.add(errorText);
}
public void onCrashDetected(String errorText) {
mErrors.add(errorText);
}
public void reset() {
mErrors.clear();
}
public List<String> getErrors() {
return Collections.unmodifiableList(mErrors);
}
/**
* Current implementation ignores the exception and continues.
*/
public void postHandler(String buttonText) {
// TODO: Add custom error logging here
String formatedOutput = String.format("UI Exception Message: %-20s\n", UiDevice
.getInstance().getCurrentPackageName());
Log.e(LOG_TAG, formatedOutput);
UiObject buttonOK = new UiObject(new UiSelector().text(buttonText).enabled(true));
// sometimes it takes a while for the OK button to become enabled
buttonOK.waitForExists(5000);
try {
buttonOK.click();
} catch (UiObjectNotFoundException e) {
e.printStackTrace();
}
}
}

View File

@ -2,29 +2,17 @@
package org.fdroid.fdroid;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.fdroid.fdroid.views.AppDetailsRecyclerViewAdapter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.io.File;
import java.util.Date;
import java.util.Random;
import java.util.TimeZone;
import androidx.test.core.app.ApplicationProvider;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
@SuppressWarnings("LineLength")
@RunWith(AndroidJUnit4.class)
public class UtilsTest {
String fdroidFingerprint = "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB";
@ -55,41 +43,9 @@ public class UtilsTest {
String fingerprintLongByOneFingerprint = "59050C8155DCA377F23D5A15B77D37134000CDBD8B42FBFBE0E3F38096E68CECE";
String fingerprintLongByOnePubkey = "308203c5308202ada00302010202047b7cf549300d06092a864886f70d01010b0500308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f301e170d3132313032393130323530305a170d3430303331363130323530305a308192310b30090603550406130255533111300f060355040813084e657720596f726b3111300f060355040713084e657720596f726b311d301b060355040a131454686520477561726469616e2050726f6a656374311f301d060355040b1316477561726469616e20462d44726f6964204275696c64311d301b06035504031314677561726469616e70726f6a6563742e696e666f30820122300d06092a864886f70d01010105000382010f003082010a0282010100b7f1f635fa3fce1a8042aaa960c2dc557e4ad2c082e5787488cba587fd26207cf59507919fc4dcebda5c8c0959d14146d0445593aa6c29dc639570b71712451fd5c231b0c9f5f0bec380503a1c2a3bc00048bc5db682915afa54d1ecf67b45e1e05c0934b3037a33d3a565899131f27a72c03a5de93df17a2376cc3107f03ee9d124c474dfab30d4053e8f39f292e2dcb6cc131bce12a0c5fc307985195d256bf1d7a2703d67c14bf18ed6b772bb847370b20335810e337c064fef7e2795a524c664a853cd46accb8494f865164dabfb698fa8318236432758bc40d52db00d5ce07fe2210dc06cd95298b4f09e6c9b7b7af61c1d62ea43ea36a2331e7b2d4e250203010001a321301f301d0603551d0e0416041404d763e981cf3a295b94a790d8536a783097232b300d06092a864886f70d01010b05000382010100654e6484ff032c54fed1d96d3c8e731302be9dbd7bb4fe635f2dac05b69f3ecbb5acb7c9fe405e2a066567a8f5c2beb8b199b5a4d5bb1b435cf02df026d4fb4edd9d8849078f085b00950083052d57467d65c6eebd98f037cff9b148d621cf8819c4f7dc1459bf8fc5c7d76f901495a7caf35d1e5c106e1d50610c4920c3c1b50adcfbd4ad83ce7353cdea7d856bba0419c224f89a2f3ebc203d20eb6247711ad2b55fd4737936dc42ced7a047cbbd24012079204a2883b6d55d5d5b66d9fd82fb51fca9a5db5fad9af8564cb380ff30ae8263dbbf01b46e01313f53279673daa3f893380285646b244359203e7eecde94ae141b7dfa8e6499bb8e7e0b25ab85";
@Test
public void trailingNewLines() {
CharSequence threeParagraphs = AppDetailsRecyclerViewAdapter.trimTrailingNewlines("Paragraph One\n\nParagraph Two\n\nParagraph Three\n\n");
assertEquals("Paragraph One\n\nParagraph Two\n\nParagraph Three", threeParagraphs);
CharSequence leadingAndExtraTrailing = AppDetailsRecyclerViewAdapter.trimTrailingNewlines("\n\n\nA\n\n\n");
assertEquals("\n\n\nA", leadingAndExtraTrailing);
}
@Test
public void commaSeparatedStrings() {
assertNull(Utils.parseCommaSeparatedString(null));
assertNull(Utils.parseCommaSeparatedString(""));
String[] singleValue = Utils.parseCommaSeparatedString("single");
assertNotNull(singleValue);
assertEquals(1, singleValue.length);
assertEquals("single", singleValue[0]);
String[] tripleValue = Utils.parseCommaSeparatedString("One,TWO,three");
assertNotNull(tripleValue);
assertEquals(3, tripleValue.length);
assertEquals("One", tripleValue[0]);
assertEquals("TWO", tripleValue[1]);
assertEquals("three", tripleValue[2]);
assertNull(Utils.serializeCommaSeparatedString(null));
assertNull(Utils.serializeCommaSeparatedString(new String[]{}));
assertEquals("Single", Utils.serializeCommaSeparatedString(new String[]{"Single"}));
assertEquals("One,TWO,three", Utils.serializeCommaSeparatedString(new String[]{"One", "TWO", "three"}));
}
@Test
public void testFormatFingerprint() {
Context context = ApplicationProvider.getApplicationContext();
Context context = InstrumentationRegistry.getTargetContext();
String badResult = Utils.formatFingerprint(context, "");
// real fingerprints
String formatted;
@ -180,78 +136,5 @@ public class UtilsTest {
}
}
@Test
public void testGetBinaryHash() {
File f = TestUtils.copyResourceToTempFile("largeRepo.xml");
assertEquals("df1754aa4b56c86c06d7842dfd02064f0781c1f740f489d3fc158bb541c8d197",
Utils.getBinaryHash(f, "sha256"));
f = TestUtils.copyResourceToTempFile("masterKeyIndex.jar");
assertEquals("625d5aedcd0499fe04ebab81f3c7ae30c236cee653a914ffb587d890198f3aba",
Utils.getBinaryHash(f, "sha256"));
f = TestUtils.copyResourceToTempFile("index.fdroid.2016-10-30.jar");
assertEquals("c138b503c6475aa749585d0e3ad4dba3546b6d33ec485efd8ac8bd603d93fedb",
Utils.getBinaryHash(f, "sha256"));
f = TestUtils.copyResourceToTempFile("index.fdroid.2016-11-10.jar");
assertEquals("93bea45814fd8955cabb957e7a3f8790d6c568eaa16fa30425c2d26c60490bde",
Utils.getBinaryHash(f, "sha256"));
}
// TODO write tests that work with a Certificate
@Test
public void testIndexDatesWithTimeZones() {
for (int h = 0; h < 12; h++) {
for (int m = 0; m < 60; m = m + 15) {
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT+%d%02d", h, m)));
String timeString = "2017-11-27_20:13:24";
Date time = Utils.parseTime(timeString, null);
assertEquals("The String representation must match", timeString, Utils.formatTime(time, null));
assertEquals(timeString + " failed to parse", 1511813604000L, time.getTime());
assertEquals("time zones should match", -((h * 60) + m), time.getTimezoneOffset());
TimeZone.setDefault(TimeZone.getTimeZone(String.format("GMT+%d%02d", h, m)));
String dateString = "2017-11-27";
Date date = Utils.parseDate(dateString, null);
assertEquals("The String representation must match", dateString, Utils.formatDate(date, null));
assertEquals(dateString + " failed to parse", 1511740800000L, date.getTime());
assertEquals("time zones should match", -((h * 60) + m), date.getTimezoneOffset());
}
}
}
/**
* Test the replacement for the ancient fingerprint algorithm.
*
* @see org.fdroid.fdroid.data.Apk#sig
*/
@Test
public void testGetsig() {
/*
* I don't fully understand the loop used here. I've copied it verbatim
* from getsig.java bundled with FDroidServer. I *believe* it is taking
* the raw byte encoding of the certificate & converting it to a byte
* array of the hex representation of the original certificate byte
* array. This is then MD5 sum'd. It's a really bad way to be doing this
* if I'm right... If I'm not right, I really don't know! see lines
* 67->75 in getsig.java bundled with Fdroidserver
*/
for (int length : new int[]{256, 345, 1233, 4032, 12092}) {
byte[] rawCertBytes = new byte[length];
new Random().nextBytes(rawCertBytes);
final byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
String sig = Utils.hashBytes(fdroidSig, "md5");
assertEquals(sig, Utils.getsig(rawCertBytes));
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{new Signature(rawCertBytes)};
assertEquals(sig, Utils.getPackageSig(packageInfo));
}
}
}

View File

@ -0,0 +1,27 @@
package org.fdroid.fdroid.compat;
import android.annotation.TargetApi;
import android.os.Build;
import org.fdroid.fdroid.data.SanitizedFile;
/**
* Used to expose the protected methods from FileCompat in a public manner so
* that they can be called from a test harness.
*/
public class FileCompatForTest extends FileCompat {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void symlinkOsTest(SanitizedFile source, SanitizedFile dest) {
symlinkOs(source, dest);
}
public static void symlinkRuntimeTest(SanitizedFile source, SanitizedFile dest) {
symlinkRuntime(source, dest);
}
public static void symlinkLibcoreTest(SanitizedFile source, SanitizedFile dest) {
symlinkLibcore(source, dest);
}
}

View File

@ -1,115 +0,0 @@
package org.fdroid.fdroid.compat;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import android.util.Log;
import org.fdroid.fdroid.AssetUtils;
import org.fdroid.fdroid.data.SanitizedFile;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.UUID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
/**
* This test needs to run on the emulator, even though it technically could
* run as a plain JUnit test, because it is testing the specifics of
* Android's symlink handling.
*/
@RunWith(AndroidJUnit4.class)
public class FileCompatTest {
private static final String TAG = "FileCompatTest";
private SanitizedFile sourceFile;
private SanitizedFile destFile;
@Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
File dir = getWriteableDir(instrumentation);
sourceFile = SanitizedFile.knownSanitized(
AssetUtils.copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
assertFalse(destFile.exists());
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
}
@After
public void tearDown() {
if (!sourceFile.delete()) {
Log.w(TAG, "Can't delete " + sourceFile.getAbsolutePath() + ".");
}
if (!destFile.delete()) {
Log.w(TAG, "Can't delete " + destFile.getAbsolutePath() + ".");
}
}
@Test
public void testSymlinkRuntime() {
FileCompat.symlinkRuntime(sourceFile, destFile);
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
}
@Test
public void testSymlinkLibcore() {
assumeTrue(Build.VERSION.SDK_INT >= 19);
FileCompat.symlinkLibcore(sourceFile, destFile);
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
}
@Test
public void testSymlinkOs() {
assumeTrue(Build.VERSION.SDK_INT >= 21);
FileCompat.symlinkOs(sourceFile, destFile);
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
}
/**
* Prefer internal over external storage, because external tends to be FAT filesystems,
* which don't support symlinks (which we test using this method).
*/
public static File getWriteableDir(Instrumentation instrumentation) {
Context context = instrumentation.getContext();
Context targetContext = instrumentation.getTargetContext();
File[] dirsToTry = new File[]{
context.getCacheDir(),
context.getFilesDir(),
targetContext.getCacheDir(),
targetContext.getFilesDir(),
context.getExternalCacheDir(),
context.getExternalFilesDir(null),
targetContext.getExternalCacheDir(),
targetContext.getExternalFilesDir(null),
Environment.getExternalStorageDirectory(),
};
return getWriteableDir(dirsToTry);
}
private static File getWriteableDir(File[] dirsToTry) {
for (File dir : dirsToTry) {
if (dir != null && dir.canWrite()) {
return dir;
}
}
return null;
}
}

Some files were not shown because too many files have changed in this diff Show More