Compare commits
No commits in common. "master" and "v0.101-alpha1" have entirely different histories.
master
...
v0.101-alp
1
.gitattributes
vendored
@ -1 +0,0 @@
|
|||||||
*.gpg binary
|
|
6
.gitignore
vendored
@ -16,6 +16,7 @@ build.xml
|
|||||||
# Gradle files
|
# Gradle files
|
||||||
.gradle/
|
.gradle/
|
||||||
build/
|
build/
|
||||||
|
gradle.properties
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
# Local configuration file (sdk path, etc)
|
||||||
local.properties
|
local.properties
|
||||||
@ -42,8 +43,3 @@ extern/*/*/libs/
|
|||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
junit-report.xml
|
junit-report.xml
|
||||||
|
|
||||||
# Screen dumps from Android Studio/DDMS
|
|
||||||
captures/
|
|
||||||
|
|
||||||
/fdroid/
|
|
||||||
|
210
.gitlab-ci.yml
@ -1,142 +1,92 @@
|
|||||||
stages:
|
image: mvdan/fdroid-ci:client-20160617
|
||||||
- test
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
.base:
|
cache:
|
||||||
image: registry.gitlab.com/fdroid/ci-images-client:latest
|
paths:
|
||||||
before_script:
|
- .gradle/wrapper
|
||||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
- .gradle/caches
|
||||||
- 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
|
|
||||||
|
|
||||||
.test-template: &test-template
|
test:
|
||||||
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"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
script:
|
script:
|
||||||
- export EXITVALUE=0
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
- function set_error() { export EXITVALUE=1; printf "\x1b[31mERROR `history|tail -2|head -1|cut -b 6-500`\x1b[0m\n"; }
|
- ./gradlew assemble -PdisablePreDex
|
||||||
- ./gradlew assemble
|
|
||||||
# always report on lint errors to the build log
|
# always report on lint errors to the build log
|
||||||
- sed -i -e 's,textReport .*,textReport true,' app/build.gradle
|
- sed -i -e 's,textReport .*,textReport true,' app/build.gradle
|
||||||
- ./gradlew testFullDebugUnitTest || set_error
|
- ./gradlew lint -PdisablePreDex
|
||||||
- ./gradlew lint || set_error
|
- ./gradlew test -PdisablePreDex || {
|
||||||
- ./gradlew pmd || set_error
|
for log in app/build/reports/*ests/*/*ml; do
|
||||||
- ./gradlew checkstyle || set_error
|
echo "read $log here:";
|
||||||
- ./tools/check-format-strings.py || set_error
|
cat "$log" | curl --silent -F 'clbin=<-' https://clbin.com;
|
||||||
- ./tools/check-fastlane-whitespace.py || set_error
|
done;
|
||||||
- ./tools/remove-unused-and-blank-translations.py || set_error
|
exit 1;
|
||||||
- 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
|
# this file changes every time but should not be cached
|
||||||
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
|
|
||||||
|
connected10:
|
||||||
|
variables:
|
||||||
|
AVD_SDK: "10"
|
||||||
|
script:
|
||||||
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
|
- emulator64-arm -avd fcl-test-$AVD_SDK -no-skin -no-audio -no-window &
|
||||||
|
- ./tools/wait-for-emulator
|
||||||
|
- adb shell input keyevent 82 &
|
||||||
|
- export EXITVALUE=0
|
||||||
|
- ./gradlew connectedCheck -PdisablePreDex || {
|
||||||
|
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
|
||||||
|
# this file changes every time but should not be cached
|
||||||
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
|
- exit $EXITVALUE
|
||||||
|
allow_failure: true # remove once install segfaults are gone
|
||||||
|
|
||||||
|
connected17:
|
||||||
|
variables:
|
||||||
|
AVD_SDK: "17"
|
||||||
|
script:
|
||||||
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
|
- emulator64-arm -avd fcl-test-$AVD_SDK -no-skin -no-audio -no-window &
|
||||||
|
- ./tools/wait-for-emulator
|
||||||
|
- adb shell input keyevent 82 &
|
||||||
|
- export EXITVALUE=0
|
||||||
|
- ./gradlew connectedCheck -PdisablePreDex || {
|
||||||
|
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
|
||||||
|
# this file changes every time but should not be cached
|
||||||
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
- exit $EXITVALUE
|
- exit $EXITVALUE
|
||||||
|
|
||||||
errorprone:
|
pmd:
|
||||||
extends: .base
|
|
||||||
stage: test
|
|
||||||
script:
|
script:
|
||||||
- apt-get update
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
- apt-get install -t stretch-backports openjdk-11-jdk-headless
|
- ./gradlew pmd -PdisablePreDex
|
||||||
- update-java-alternatives --set java-1.11.0-openjdk-amd64
|
# this file changes every time but should not be cached
|
||||||
- export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
- cat config/errorprone.gradle >> app/build.gradle
|
|
||||||
- ./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug
|
|
||||||
|
|
||||||
# Run the tests in the emulator. Each step is broken out to run on
|
checkstyle:
|
||||||
# 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
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew assembleFullDebug
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
- export AVD_SDK=`echo $CI_JOB_NAME | awk '{print $2}'`
|
- ./gradlew checkstyle -PdisablePreDex
|
||||||
- export AVD_TAG=`echo $CI_JOB_NAME | awk '{print $3}'`
|
# this file changes every time but should not be cached
|
||||||
- export AVD_ARCH=`echo $CI_JOB_NAME | awk '{print $4}'`
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
- export AVD_PACKAGE="system-images;android-${AVD_SDK};${AVD_TAG};${AVD_ARCH}"
|
|
||||||
- echo $AVD_PACKAGE
|
|
||||||
|
|
||||||
- alias sdkmanager
|
tools:
|
||||||
- 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
|
|
||||||
script:
|
script:
|
||||||
- test -z "$DEBUG_KEYSTORE" && exit 0
|
- cd app
|
||||||
- sed -i
|
- ./tools/langs-list-check.py
|
||||||
's,<string name="app_name">.*</string>,<string name="app_name">F-Nightly</string>,'
|
- ./tools/check-string-format.py
|
||||||
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
|
|
||||||
|
3
.weblate
@ -1,3 +0,0 @@
|
|||||||
[weblate]
|
|
||||||
url = https://hosted.weblate.org/api/
|
|
||||||
translation = f-droid/f-droid
|
|
@ -10,7 +10,7 @@ fdroid_root := $(LOCAL_PATH)
|
|||||||
fdroid_dir := app
|
fdroid_dir := app
|
||||||
fdroid_out := $(PWD)/$(OUT_DIR)/target/common/obj/APPS/$(LOCAL_MODULE)_intermediates
|
fdroid_out := $(PWD)/$(OUT_DIR)/target/common/obj/APPS/$(LOCAL_MODULE)_intermediates
|
||||||
fdroid_build := $(fdroid_root)/$(fdroid_dir)/build
|
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):
|
$(fdroid_root)/$(fdroid_dir)/$(fdroid_apk):
|
||||||
rm -Rf $(fdroid_build)
|
rm -Rf $(fdroid_build)
|
||||||
|
690
CHANGELOG.md
@ -1,619 +1,7 @@
|
|||||||
### 1.13-alpha1 (2021-06-02)
|
### Upcoming release
|
||||||
|
|
||||||
* 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
|
* 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)
|
### 0.100 (2016-06-07)
|
||||||
|
|
||||||
* Ability to download apps in the background
|
* Ability to download apps in the background
|
||||||
@ -660,7 +48,7 @@
|
|||||||
|
|
||||||
* Fix crash when adding malformed URIs as repos
|
* 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)
|
### 0.98 (2016-02-01)
|
||||||
|
|
||||||
@ -724,7 +112,7 @@
|
|||||||
|
|
||||||
* Move the repo index update to a notification
|
* 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
|
* Don't let users try to uninstall system apps that haven't been updated
|
||||||
|
|
||||||
@ -767,14 +155,14 @@
|
|||||||
and now easy to set up with root privileges
|
and now easy to set up with root privileges
|
||||||
|
|
||||||
* Speed up and simplify repo update process by streaming the data out of the
|
* 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
|
* Can now manually add swap repo via "Repositories" screen
|
||||||
|
|
||||||
* Using NFC during swap now initiates a proper swap, rather than redirecting to
|
* Using NFC during swap now initiates a proper swap, rather than redirecting to
|
||||||
the "Repositories" screen
|
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)
|
### 0.92 (2015-06-08)
|
||||||
|
|
||||||
@ -782,7 +170,7 @@
|
|||||||
|
|
||||||
* Update Universal-Image-Loader to 1.9.4
|
* 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
|
* Add missing Sardinian language to the preferences
|
||||||
|
|
||||||
@ -795,9 +183,9 @@
|
|||||||
since it's not needed to use our own external app directory
|
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
|
* 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
|
* Drop support for unsigned repos in favour of signed ones and TOFU support
|
||||||
|
|
||||||
@ -811,7 +199,7 @@
|
|||||||
|
|
||||||
* Don't crash if links on descriptions cannot be handled by any application
|
* 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)
|
### 0.88 (2015-04-28)
|
||||||
|
|
||||||
@ -821,7 +209,7 @@
|
|||||||
* User interface language can now be changed from inside the F-Droid
|
* User interface language can now be changed from inside the F-Droid
|
||||||
preferences without changing the system language (locale)
|
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
|
* Improve app and search link handling while also adding supporting for Amazon
|
||||||
and Google Play links
|
and Google Play links
|
||||||
@ -831,12 +219,12 @@
|
|||||||
|
|
||||||
* Show a message to the user when there are no apps to display.
|
* 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
|
* Swapping is now two way. Connecting to a swap on one device will
|
||||||
initiate a swap on the other device
|
initiate a swap on the other device.
|
||||||
|
|
||||||
* Small UI fixes to avoid overlapping text and improve app version ellipsizing
|
* 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
|
matches FireFox
|
||||||
|
|
||||||
* Ignore trailing paces in search terms introduced by some input methods
|
* Ignore trailing paces in search terms introduced by some input methods
|
||||||
@ -853,7 +241,7 @@
|
|||||||
|
|
||||||
* Fix issue that caused the installed state label to sometimes not be updated
|
* 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)
|
* Show when packages are installed but not via F-Droid (mismatching signature)
|
||||||
|
|
||||||
@ -882,7 +270,7 @@
|
|||||||
|
|
||||||
* Update Universal-Image-Loader and the Support libraries
|
* 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
|
* Translation updates
|
||||||
|
|
||||||
@ -904,14 +292,14 @@
|
|||||||
* HTTP Proxy support in Preferences
|
* HTTP Proxy support in Preferences
|
||||||
|
|
||||||
* Directly send installed apps to other devices via Bluetooth and Android Beam
|
* 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
|
* 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
|
* 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
|
* Fix some crashes
|
||||||
|
|
||||||
@ -950,7 +338,7 @@
|
|||||||
* Send F-Droid via Bluetooth to any device that supports receiving APKs via
|
* Send F-Droid via Bluetooth to any device that supports receiving APKs via
|
||||||
Bluetooth (stock Android blocks APKs, most ROMs allow them)
|
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+)
|
beam the F-Droid.apk from F-Droid's main screen (Android 4.1+)
|
||||||
|
|
||||||
* Support for repositories using self-signed HTTPS certificates through
|
* Support for repositories using self-signed HTTPS certificates through
|
||||||
@ -964,7 +352,7 @@
|
|||||||
|
|
||||||
* Major internal changes to enable F-Droid to handle repos with thousands
|
* 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
|
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
|
* Various fixes to layout issues introduced in 0.58
|
||||||
|
|
||||||
@ -978,7 +366,7 @@
|
|||||||
|
|
||||||
* Tweaked some layouts, especially the app lists and their compact layout
|
* 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
|
rather than number of versions available
|
||||||
|
|
||||||
* Reduce scroll lag in app lists by caching views in a ViewHolder
|
* Reduce scroll lag in app lists by caching views in a ViewHolder
|
||||||
@ -994,21 +382,21 @@
|
|||||||
can see what the checkbox preferences actually mean and what the edit and
|
can see what the checkbox preferences actually mean and what the edit and
|
||||||
list preferences are set at
|
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
|
* Always include incompatible apks in memory to avoid issues with apps
|
||||||
seemingly not having any APKs available
|
seemingly not having any apks available
|
||||||
|
|
||||||
* Fixed a crash when trying to access a non-existing app
|
* Fixed a crash when trying to access a non-existing app
|
||||||
|
|
||||||
* F-Droid registers with Android to receive F-Droid URIs https://\*/fdroid/repo
|
* F-Droid registers with Android to receive F-Droid URIs https://\*/fdroid/repo
|
||||||
and fdroidrepos://
|
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
|
that repo exists in F-Droid already, and if the fingerprints match
|
||||||
|
|
||||||
* Other minor bug fixes
|
* Other minor bug fixes
|
||||||
@ -1019,12 +407,12 @@
|
|||||||
|
|
||||||
* Fixed problems with category selection and permission lists on Android 2.X devices.
|
* 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)
|
### 0.54 (2013-11-05)
|
||||||
|
|
||||||
* New options on the App Details screen to ignore all future updates for that
|
* 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
|
* Apps with Anti-features are no longer hidden, and the corresponding
|
||||||
preferences to unhide them are removed. Instead they are clearly marked on the
|
preferences to unhide them are removed. Instead they are clearly marked on the
|
||||||
@ -1033,7 +421,7 @@
|
|||||||
* Apps with incompatible native code architecture requirements are now correctly
|
* Apps with incompatible native code architecture requirements are now correctly
|
||||||
filtered.
|
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.
|
* Theming support, with Light and Dark themes.
|
||||||
|
|
||||||
@ -1042,29 +430,29 @@
|
|||||||
installation.
|
installation.
|
||||||
|
|
||||||
* All app donation options have been grouped into a submenu, and Litecoin
|
* 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)
|
### 0.50 (2013-08-20)
|
||||||
|
|
||||||
* New basic app sharing functionality
|
* 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
|
* 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
|
* Noticeable speedups when returning from installs and uninstalls
|
||||||
|
|
||||||
|
@ -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.
|
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
|
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
|
suggestions on how to improve them, open a merge request like you would if you
|
||||||
would if you were making code changes. This way the changes can be reviewed
|
were making code changes. This way the changes can be reviewed before the
|
||||||
before the source strings on Weblate are changed.
|
source strings on Weblate are changed.
|
||||||
|
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
We follow the default Android Studio code formatter (e.g. `Ctrl-Alt-L`). This
|
We follow the [Android Java style](https://source.android.com/source/code-style.html).
|
||||||
should be more or less the same as [Android Java
|
Some key points:
|
||||||
style](https://source.android.com/source/code-style.html). Some key points:
|
|
||||||
|
|
||||||
* Four space indentation
|
* Four space indentation
|
||||||
* UTF-8 source files
|
* UTF-8 source files
|
||||||
@ -47,35 +45,65 @@ style](https://source.android.com/source/code-style.html). Some key points:
|
|||||||
* Braces are always used after if, for and while
|
* Braces are always used after if, for and while
|
||||||
|
|
||||||
The current code base doesn't follow it entirely, but new code should follow
|
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.
|
||||||
|
* When building as part of AOSP with `Android.mk`, make sure you have a
|
||||||
|
recent version of Gradle installed as `gradlew` will not be used.
|
||||||
|
|
||||||
## Running the test suite
|
## 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
|
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
|
connected via `adb`, or an emulator running. Then, execute the following from the
|
||||||
command line:
|
command line:
|
||||||
|
|
||||||
./gradlew check
|
./gradlew check
|
||||||
|
|
||||||
Many important tests require a device or emulator, but do not work in GitLab CI.
|
Note that the CI already runs the tests on an emulator, so you don't
|
||||||
That mean they need to be run locally, and that is usually easiest in Android
|
necessarily have to do this yourself if you open a merge request as the tests
|
||||||
Studio rather than the command line.
|
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 \
|
The version codes use a number of digits per each of these keys: `XXXYYYZNN`.
|
||||||
-Pandroid.testInstrumentationRunnerArguments.class=org.fdroid.fdroid.MainActivityExpressoTest
|
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.
|
||||||
|
11
FUNDING.yml
@ -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
|
|
48
Privileged-Extension/build.gradle
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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.19'
|
||||||
|
}
|
||||||
|
|
||||||
|
task checkstyle(type: Checkstyle) {
|
||||||
|
configFile file("${project.rootDir}/config/checkstyle/checkstyle.xml")
|
||||||
|
source 'src/main/java'
|
||||||
|
include '**/*.java'
|
||||||
|
|
||||||
|
classpath = files()
|
||||||
|
}
|
41
Privileged-Extension/src/main/AndroidManifest.xml
Normal 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>
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
Privileged-Extension/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
Privileged-Extension/src/main/res/mipmap-ldpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Privileged-Extension/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Privileged-Extension/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
Privileged-Extension/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
Privileged-Extension/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
4
Privileged-Extension/src/main/res/values-ar/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources><string name="app_name">ملحقات أف-درويد المميز</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-ast/strings.xml
Normal 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>
|
4
Privileged-Extension/src/main/res/values-de/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Privilegierte F-Droid-Erweiterung</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-el/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Επέκταση του F-Droid με δικαιώματα συστήματος</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-eo/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources><string name="app_name">Privilegia F-Droid-aldonaĵo</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-es/strings.xml
Normal 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>
|
4
Privileged-Extension/src/main/res/values-eu/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Pribilegiodun F-Droid Luzapena</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-fa/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">افزونهٔ ممتاز افدروید</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-fr/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources><string name="app_name">F-Droid Privileged Extension</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-it/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources><string name="app_name">Estensione F-Droid con Privilegi</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-ja/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">F-Droid Privileged Extension</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-nb/strings.xml
Normal 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>
|
4
Privileged-Extension/src/main/res/values-nl/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources><string name="app_name">Geprivilegieerde F-Droid-extensie</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-pl/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">F-Droid Privileged Extension</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-pt/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Extensão privilegiada F-Droid</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-ro/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<resources><string name="app_name">Extensie privilegiata F-Droid</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-ru/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Привилегированное расширение F-Droid</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-sk/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">F-Droid privilegované rozšírenie</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-sq/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Prapashtesa e privilegjuar F-Droid</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-sr/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Повлашћено проширење за Ф-дроид</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-tr/strings.xml
Normal 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>
|
4
Privileged-Extension/src/main/res/values-uk/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources><string name="app_name">Привілейоване розширення F-Droid</string>
|
||||||
|
|
||||||
|
</resources>
|
4
Privileged-Extension/src/main/res/values-vi/strings.xml
Normal 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>
|
6
Privileged-Extension/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">F-Droid Privileged Extension</string>
|
||||||
|
|
||||||
|
</resources>
|
18
README.md
@ -1,6 +1,6 @@
|
|||||||
# F-Droid Client
|
# F-Droid Client
|
||||||
|
|
||||||
[](https://gitlab.com/fdroid/fdroidclient/-/jobs)
|
[](https://gitlab.com/fdroid/fdroidclient/builds)
|
||||||
[](https://hosted.weblate.org/engage/f-droid/)
|
[](https://hosted.weblate.org/engage/f-droid/)
|
||||||
|
|
||||||
Client for [F-Droid](https://f-droid.org), the Free Software repository system
|
Client for [F-Droid](https://f-droid.org), the Free Software repository system
|
||||||
@ -23,7 +23,7 @@ issues, translate the app into your language or help with development.
|
|||||||
## IRC
|
## IRC
|
||||||
|
|
||||||
We are on `#fdroid` and `#fdroid-dev` on Freenode. We hold weekly dev meetings
|
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
|
## FAQ
|
||||||
|
|
||||||
@ -37,8 +37,10 @@ to what Google Play does.
|
|||||||
privileged system app?
|
privileged system app?
|
||||||
|
|
||||||
This used to be the case, but no longer is. Now the [Privileged
|
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
|
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.
|
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
|
## License
|
||||||
|
|
||||||
This program is Free Software: You can use, study share and improve it at your
|
This program is Free Software: You can use, study share and improve it at your
|
||||||
@ -57,11 +59,3 @@ Other icons are from the
|
|||||||
[Material Design Icon set](https://github.com/google/material-design-icons)
|
[Material Design Icon set](https://github.com/google/material-design-icons)
|
||||||
released under an
|
released under an
|
||||||
[Attribution 4.0 International license](https://creativecommons.org/licenses/by/4.0/).
|
[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.
|
|
||||||
[](https://hosted.weblate.org/engage/f-droid/?utm_source=widget)
|
|
||||||
|
320
app/build.gradle
@ -1,99 +1,176 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'witness'
|
||||||
apply plugin: 'checkstyle'
|
apply plugin: 'checkstyle'
|
||||||
apply plugin: 'pmd'
|
apply plugin: 'pmd'
|
||||||
|
|
||||||
/* gets the version name from the latest Git tag */
|
/* gets the version name from the latest Git tag, stripping the leading v off */
|
||||||
def getVersionName = { ->
|
def getVersionName = { ->
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
commandLine 'git', 'describe', '--tags', '--always'
|
commandLine 'git', 'describe', '--tags', '--always'
|
||||||
standardOutput = stdout
|
standardOutput = stdout
|
||||||
}
|
}
|
||||||
return stdout.toString().trim()
|
return stdout.toString().trim().substring(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
def isCi = "true" == System.getenv("CI")
|
repositories {
|
||||||
def preDexEnabled = "true" == System.getProperty("pre-dex", "true")
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
def fullApplicationId = "org.fdroid.fdroid"
|
dependencies {
|
||||||
def basicApplicationId = "org.fdroid.basic"
|
compile project(':privileged-api-lib')
|
||||||
// yes, this actually needs both quotes https://stackoverflow.com/a/41391841
|
|
||||||
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
|
compile 'com.android.support:support-v4:24.0.0'
|
||||||
|
compile 'com.android.support:appcompat-v7:24.0.0'
|
||||||
|
compile 'com.android.support:support-annotations:24.0.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.5'
|
||||||
|
compile 'commons-net:commons-net:3.5'
|
||||||
|
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'
|
||||||
|
|
||||||
|
testCompile "org.robolectric:robolectric:3.1"
|
||||||
|
|
||||||
|
testCompile "org.mockito:mockito-core:1.10.19"
|
||||||
|
|
||||||
|
androidTestCompile 'com.android.support:support-annotations:24.0.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:b6d3994d4bdea0ef2a221ecf9e0ddf150b99af189211c36418c81e248f3fed99',
|
||||||
|
'com.android.support:appcompat-v7:a30898bbec4d506f821055a362d62820b18f29e9cf0c077817e46cb5a03cde94',
|
||||||
|
'com.android.support:support-annotations:0790aced70e3ce82a6e466f0d4908cdd9e21bd925b4107437441d201f595a908',
|
||||||
|
'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:a10418348d234968600ccb1d988efcbbd08716e1d96936ccc1880e7d22513474',
|
||||||
|
'commons-net:commons-net:c25b0da668b3c5649f002d504def22d1b4cb30d206f05428d2fe168fa1a901c2',
|
||||||
|
'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 {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 23
|
||||||
|
buildToolsVersion '23.0.3'
|
||||||
defaultConfig {
|
useLibrary 'org.apache.http.legacy'
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
// use proguard on debug too since we have unknowingly broken
|
// use proguard on debug too since we have unknowingly broken
|
||||||
// release builds before.
|
// release builds before.
|
||||||
all {
|
all {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
useProguard true
|
||||||
shrinkResources 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'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'src/androidTest/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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
compileOptions.encoding = "UTF-8"
|
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 {
|
defaultConfig {
|
||||||
cruncherEnabled = false
|
versionCode 101001
|
||||||
}
|
versionName getVersionName()
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testOptions {
|
testOptions {
|
||||||
unitTests {
|
unitTests {
|
||||||
includeAndroidResources = true
|
|
||||||
// prevent tests from dying on android.util.Log calls
|
// prevent tests from dying on android.util.Log calls
|
||||||
returnDefaultValues = true
|
returnDefaultValues = true
|
||||||
all {
|
all {
|
||||||
@ -102,33 +179,27 @@ android {
|
|||||||
events "skipped", "failed", "standardOut", "standardError"
|
events "skipped", "failed", "standardOut", "standardError"
|
||||||
showStandardStreams = true
|
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
checkReleaseBuilds false
|
checkReleaseBuilds false
|
||||||
abortOnError true
|
abortOnError false
|
||||||
|
|
||||||
htmlReport true
|
htmlReport true
|
||||||
xmlReport false
|
xmlReport false
|
||||||
textReport 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 {
|
packagingOptions {
|
||||||
@ -141,61 +212,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 {
|
checkstyle {
|
||||||
toolVersion = '7.2'
|
toolVersion = '6.19'
|
||||||
}
|
}
|
||||||
|
|
||||||
task checkstyle(type: Checkstyle) {
|
task checkstyle(type: Checkstyle) {
|
||||||
@ -207,20 +225,18 @@ task checkstyle(type: Checkstyle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pmd {
|
pmd {
|
||||||
toolVersion = '6.20.0'
|
toolVersion = '5.4.2'
|
||||||
consoleOutput = true
|
consoleOutput = true
|
||||||
}
|
}
|
||||||
|
|
||||||
task pmdMain(type: Pmd) {
|
task pmdMain(type: Pmd, dependsOn: assembleDebug) {
|
||||||
dependsOn 'assembleDebug'
|
|
||||||
ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-main.xml")
|
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
|
ruleSets = [] // otherwise defaults clash with the list in rules.xml
|
||||||
source 'src/main/java'
|
source 'src/main/java'
|
||||||
include '**/*.java'
|
include '**/*.java'
|
||||||
}
|
}
|
||||||
|
|
||||||
task pmdTest(type: Pmd) {
|
task pmdTest(type: Pmd, dependsOn: assembleDebug) {
|
||||||
dependsOn 'assembleDebug'
|
|
||||||
ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-test.xml")
|
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
|
ruleSets = [] // otherwise defaults clash with the list in rules.xml
|
||||||
source 'src/test/java', 'src/androidTest/java'
|
source 'src/test/java', 'src/androidTest/java'
|
||||||
@ -228,3 +244,35 @@ task pmdTest(type: Pmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task pmd(dependsOn: [pmdMain, pmdTest]) {}
|
task pmd(dependsOn: [pmdMain, pmdTest]) {}
|
||||||
|
|
||||||
|
// 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("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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
app/libs/binaryDeps/nanohttpd-2.1.0.jar
Normal file
BIN
app/libs/binaryDeps/support-v4-preferencefragment-release.aar
Normal file
BIN
app/libs/binaryDeps/zipsigner.jar
Normal file
67
app/lint.xml
@ -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>
|
|
37
app/proguard-rules.pro
vendored
@ -4,16 +4,12 @@
|
|||||||
-keep class org.fdroid.fdroid.** {*;}
|
-keep class org.fdroid.fdroid.** {*;}
|
||||||
-dontskipnonpubliclibraryclassmembers
|
-dontskipnonpubliclibraryclassmembers
|
||||||
-dontwarn android.test.**
|
-dontwarn android.test.**
|
||||||
|
-dontwarn com.android.support.test.**
|
||||||
|
|
||||||
-dontwarn javax.naming.**
|
-dontwarn javax.naming.**
|
||||||
-dontwarn org.slf4j.**
|
-dontnote android.support.**
|
||||||
-dontnote org.apache.http.**
|
|
||||||
-dontnote android.net.http.**
|
|
||||||
-dontnote **ILicensingService
|
-dontnote **ILicensingService
|
||||||
|
|
||||||
# Needed for espresso https://stackoverflow.com/a/21706087
|
|
||||||
-dontwarn org.xmlpull.v1.**
|
|
||||||
|
|
||||||
# StrongHttpsClient and its support classes are totally unused, so the
|
# StrongHttpsClient and its support classes are totally unused, so the
|
||||||
# ch.boye.httpclientandroidlib.** classes are also unneeded
|
# ch.boye.httpclientandroidlib.** classes are also unneeded
|
||||||
-dontwarn info.guardianproject.netcipher.client.**
|
-dontwarn info.guardianproject.netcipher.client.**
|
||||||
@ -23,7 +19,7 @@
|
|||||||
# removed, proguard will strip classes which are required, which may result in
|
# removed, proguard will strip classes which are required, which may result in
|
||||||
# crashes.
|
# crashes.
|
||||||
-keep class kellinwood.security.zipsigner.** {*;}
|
-keep class kellinwood.security.zipsigner.** {*;}
|
||||||
-keep class org.bouncycastle.** {*;}
|
-keep class org.spongycastle.** {*;}
|
||||||
|
|
||||||
# This keeps class members used for SystemInstaller IPC.
|
# This keeps class members used for SystemInstaller IPC.
|
||||||
# Reference: https://gitlab.com/fdroid/fdroidclient/issues/79
|
# Reference: https://gitlab.com/fdroid/fdroidclient/issues/79
|
||||||
@ -31,17 +27,20 @@
|
|||||||
public *;
|
public *;
|
||||||
}
|
}
|
||||||
|
|
||||||
-keepattributes *Annotation*,EnclosingMethod,Signature
|
# Samsung Android 4.2 bug
|
||||||
-keepnames class com.fasterxml.jackson.** { *; }
|
# https://code.google.com/p/android/issues/detail?id=78377
|
||||||
-dontwarn com.fasterxml.jackson.databind.ext.**
|
-keepnames class !android.support.v7.internal.view.menu.**, ** {*;}
|
||||||
-keep class org.codehaus.** { *; }
|
|
||||||
-keepclassmembers public final enum org.codehaus.jackson.annotate.JsonAutoDetect$Visibility {
|
-keep public class android.support.v7.widget.** {*;}
|
||||||
public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
|
-keep public class android.support.v7.internal.widget.** {*;}
|
||||||
-keep public class your.class.** {
|
|
||||||
*;
|
-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)
|
# The rxjava library depends on sun.misc.Unsafe, which is unavailable on Android
|
||||||
-keep class androidx.work.multiprocess.RemoteWorkManagerClient {
|
# The rxjava team is aware of this, and mention in the docs that they only use
|
||||||
public <init>(...);
|
# 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.**
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us -->
|
<!-- 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"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
package="org.fdroid.fdroid.tests"
|
||||||
package="org.fdroid.fdroid.tests"
|
android:versionCode="1"
|
||||||
android:versionCode="1"
|
android:versionName="1.0">
|
||||||
android:versionName="1.0">
|
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="android_libs.ub_uiautomator" />
|
|
||||||
|
|
||||||
<!-- We add an application tag here just so that we can indicate that
|
<!-- We add an application tag here just so that we can indicate that
|
||||||
this package needs to link against the android.test library,
|
this package needs to link against the android.test library,
|
||||||
which is needed when building test cases. -->
|
which is needed when building test cases. -->
|
||||||
<application>
|
<application>
|
||||||
<uses-library
|
<uses-library android:name="android.test.runner"/>
|
||||||
android:name="android.test.runner"
|
|
||||||
android:required="false" />
|
|
||||||
</application>
|
</application>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_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>
|
</manifest>
|
||||||
|
@ -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>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +1,15 @@
|
|||||||
package org.fdroid.fdroid.compat;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import android.support.annotation.Nullable;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.AssetUtils;
|
import org.fdroid.fdroid.compat.FileCompatForTest;
|
||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -16,6 +17,10 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@ -40,8 +45,7 @@ public class FileCompatTest {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
File dir = getWriteableDir(instrumentation);
|
File dir = getWriteableDir(instrumentation);
|
||||||
sourceFile = SanitizedFile.knownSanitized(
|
sourceFile = SanitizedFile.knownSanitized(copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
|
||||||
AssetUtils.copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
|
|
||||||
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
|
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
|
||||||
assertFalse(destFile.exists());
|
assertFalse(destFile.exists());
|
||||||
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
|
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
|
||||||
@ -50,40 +54,60 @@ public class FileCompatTest {
|
|||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
if (!sourceFile.delete()) {
|
if (!sourceFile.delete()) {
|
||||||
Log.w(TAG, "Can't delete " + sourceFile.getAbsolutePath() + ".");
|
System.out.println("Can't delete " + sourceFile.getAbsolutePath() + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!destFile.delete()) {
|
if (!destFile.delete()) {
|
||||||
Log.w(TAG, "Can't delete " + destFile.getAbsolutePath() + ".");
|
System.out.println("Can't delete " + destFile.getAbsolutePath() + ".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSymlinkRuntime() {
|
public void testSymlinkRuntime() {
|
||||||
FileCompat.symlinkRuntime(sourceFile, destFile);
|
FileCompatForTest.symlinkRuntimeTest(sourceFile, destFile);
|
||||||
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSymlinkLibcore() {
|
public void testSymlinkLibcore() {
|
||||||
assumeTrue(Build.VERSION.SDK_INT >= 19);
|
assumeTrue(Build.VERSION.SDK_INT >= 19);
|
||||||
FileCompat.symlinkLibcore(sourceFile, destFile);
|
FileCompatForTest.symlinkLibcoreTest(sourceFile, destFile);
|
||||||
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSymlinkOs() {
|
public void testSymlinkOs() {
|
||||||
assumeTrue(Build.VERSION.SDK_INT >= 21);
|
assumeTrue(Build.VERSION.SDK_INT >= 21);
|
||||||
FileCompat.symlinkOs(sourceFile, destFile);
|
FileCompatForTest.symlinkOsTest(sourceFile, destFile);
|
||||||
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private 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.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) {
|
||||||
|
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,
|
* Prefer internal over external storage, because external tends to be FAT filesystems,
|
||||||
* which don't support symlinks (which we test using this method).
|
* which don't support symlinks (which we test using this method).
|
||||||
*/
|
*/
|
||||||
public static File getWriteableDir(Instrumentation instrumentation) {
|
private static File getWriteableDir(Instrumentation instrumentation) {
|
||||||
Context context = instrumentation.getContext();
|
Context context = instrumentation.getContext();
|
||||||
Context targetContext = instrumentation.getTargetContext();
|
Context targetContext = instrumentation.getTargetContext();
|
||||||
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 + " :'(");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,454 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 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.installer;
|
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import org.fdroid.fdroid.AssetUtils;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
import org.fdroid.fdroid.compat.FileCompatTest;
|
|
||||||
import org.fdroid.fdroid.data.Apk;
|
|
||||||
import org.fdroid.fdroid.data.Repo;
|
|
||||||
import org.fdroid.fdroid.data.RepoXMLHandler;
|
|
||||||
import org.fdroid.fdroid.mock.RepoDetails;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This test checks the ApkVerifier by parsing a repo from permissionsRepo.xml
|
|
||||||
* and checking the listed permissions against the ones specified in apks' AndroidManifest,
|
|
||||||
* which have been specifically generated for this test.
|
|
||||||
* <p>
|
|
||||||
* NOTE: This androidTest cannot run as a Robolectric test because the
|
|
||||||
* required methods from PackageManger are not included in Robolectric's Android API.
|
|
||||||
* java.lang.NoClassDefFoundError: java/util/jar/StrictJarFile
|
|
||||||
* at android.content.pm.PackageManager.getPackageArchiveInfo(PackageManager.java:3545)
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ApkVerifierTest {
|
|
||||||
public static final String TAG = "ApkVerifierTest";
|
|
||||||
|
|
||||||
Instrumentation instrumentation;
|
|
||||||
|
|
||||||
File sdk14Apk;
|
|
||||||
File minMaxApk;
|
|
||||||
private File extendedPermissionsApk;
|
|
||||||
private File extendedPermsXml;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
instrumentation = InstrumentationRegistry.getInstrumentation();
|
|
||||||
File dir = FileCompatTest.getWriteableDir(instrumentation);
|
|
||||||
assertTrue(dir.isDirectory());
|
|
||||||
assertTrue(dir.canWrite());
|
|
||||||
sdk14Apk = AssetUtils.copyAssetToDir(instrumentation.getContext(),
|
|
||||||
"org.fdroid.permissions.sdk14.apk",
|
|
||||||
dir
|
|
||||||
);
|
|
||||||
minMaxApk = AssetUtils.copyAssetToDir(instrumentation.getContext(),
|
|
||||||
"org.fdroid.permissions.minmax.apk",
|
|
||||||
dir
|
|
||||||
);
|
|
||||||
extendedPermissionsApk = AssetUtils.copyAssetToDir(instrumentation.getContext(),
|
|
||||||
"org.fdroid.extendedpermissionstest.apk",
|
|
||||||
dir
|
|
||||||
);
|
|
||||||
extendedPermsXml = AssetUtils.copyAssetToDir(instrumentation.getContext(),
|
|
||||||
"extendedPerms.xml",
|
|
||||||
dir
|
|
||||||
);
|
|
||||||
assertTrue(sdk14Apk.exists());
|
|
||||||
assertTrue(minMaxApk.exists());
|
|
||||||
assertTrue(extendedPermissionsApk.exists());
|
|
||||||
assertTrue(extendedPermsXml.exists());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNulls() {
|
|
||||||
assertTrue(ApkVerifier.requestedPermissionsEqual(null, null));
|
|
||||||
|
|
||||||
String[] perms = new String[]{"Blah"};
|
|
||||||
assertFalse(ApkVerifier.requestedPermissionsEqual(perms, null));
|
|
||||||
assertFalse(ApkVerifier.requestedPermissionsEqual(null, perms));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWithoutPrefix() {
|
|
||||||
Apk apk = new Apk();
|
|
||||||
apk.packageName = "org.fdroid.permissions.sdk14";
|
|
||||||
apk.targetSdkVersion = 14;
|
|
||||||
ArrayList<String> noPrefixPermissionsList = new ArrayList<>(Arrays.asList(
|
|
||||||
"AUTHENTICATE_ACCOUNTS",
|
|
||||||
"MANAGE_ACCOUNTS",
|
|
||||||
"READ_PROFILE",
|
|
||||||
"WRITE_PROFILE",
|
|
||||||
"GET_ACCOUNTS",
|
|
||||||
"READ_CONTACTS",
|
|
||||||
"WRITE_CONTACTS",
|
|
||||||
"WRITE_EXTERNAL_STORAGE",
|
|
||||||
"READ_EXTERNAL_STORAGE",
|
|
||||||
"INTERNET",
|
|
||||||
"ACCESS_NETWORK_STATE",
|
|
||||||
"NFC",
|
|
||||||
"READ_SYNC_SETTINGS",
|
|
||||||
"WRITE_SYNC_SETTINGS",
|
|
||||||
"WRITE_CALL_LOG", // implied-permission!
|
|
||||||
"READ_CALL_LOG" // implied-permission!
|
|
||||||
));
|
|
||||||
if (Build.VERSION.SDK_INT >= 29) {
|
|
||||||
noPrefixPermissionsList.add("android.permission.ACCESS_MEDIA_LOCATION");
|
|
||||||
}
|
|
||||||
String[] noPrefixPermissions = noPrefixPermissionsList.toArray(new String[0]);
|
|
||||||
|
|
||||||
for (int i = 0; i < noPrefixPermissions.length; i++) {
|
|
||||||
noPrefixPermissions[i] = RepoXMLHandler.fdroidToAndroidPermission(noPrefixPermissions[i]);
|
|
||||||
}
|
|
||||||
apk.requestedPermissions = noPrefixPermissions;
|
|
||||||
|
|
||||||
Uri uri = Uri.fromFile(sdk14Apk);
|
|
||||||
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
|
||||||
|
|
||||||
try {
|
|
||||||
apkVerifier.verifyApk();
|
|
||||||
} catch (ApkVerifier.ApkVerificationException | ApkVerifier.ApkPermissionUnequalException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = ApkVerifier.ApkPermissionUnequalException.class)
|
|
||||||
public void testWithMinMax()
|
|
||||||
throws ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
|
|
||||||
Apk apk = new Apk();
|
|
||||||
apk.packageName = "org.fdroid.permissions.minmax";
|
|
||||||
apk.targetSdkVersion = 24;
|
|
||||||
ArrayList<String> permissionsList = new ArrayList<>();
|
|
||||||
permissionsList.add("android.permission.READ_CALENDAR");
|
|
||||||
if (Build.VERSION.SDK_INT <= 18) {
|
|
||||||
permissionsList.add("android.permission.WRITE_EXTERNAL_STORAGE");
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
|
||||||
permissionsList.add("android.permission.ACCESS_FINE_LOCATION");
|
|
||||||
}
|
|
||||||
apk.requestedPermissions = permissionsList.toArray(new String[permissionsList.size()]);
|
|
||||||
|
|
||||||
Uri uri = Uri.fromFile(minMaxApk);
|
|
||||||
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
|
||||||
apkVerifier.verifyApk();
|
|
||||||
|
|
||||||
permissionsList.add("ADDITIONAL_PERMISSION");
|
|
||||||
apk.requestedPermissions = permissionsList.toArray(new String[permissionsList.size()]);
|
|
||||||
apkVerifier.verifyApk();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWithPrefix() {
|
|
||||||
Apk apk = new Apk();
|
|
||||||
apk.packageName = "org.fdroid.permissions.sdk14";
|
|
||||||
apk.targetSdkVersion = 14;
|
|
||||||
TreeSet<String> expectedSet = new TreeSet<>(Arrays.asList(
|
|
||||||
"android.permission.AUTHENTICATE_ACCOUNTS",
|
|
||||||
"android.permission.MANAGE_ACCOUNTS",
|
|
||||||
"android.permission.READ_PROFILE",
|
|
||||||
"android.permission.WRITE_PROFILE",
|
|
||||||
"android.permission.GET_ACCOUNTS",
|
|
||||||
"android.permission.READ_CONTACTS",
|
|
||||||
"android.permission.WRITE_CONTACTS",
|
|
||||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.READ_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.INTERNET",
|
|
||||||
"android.permission.ACCESS_NETWORK_STATE",
|
|
||||||
"android.permission.NFC",
|
|
||||||
"android.permission.READ_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
|
||||||
"android.permission.READ_CALL_LOG"// implied-permission!
|
|
||||||
));
|
|
||||||
if (Build.VERSION.SDK_INT >= 29) {
|
|
||||||
expectedSet.add("android.permission.ACCESS_MEDIA_LOCATION");
|
|
||||||
}
|
|
||||||
apk.requestedPermissions = expectedSet.toArray(new String[0]);
|
|
||||||
|
|
||||||
Uri uri = Uri.fromFile(sdk14Apk);
|
|
||||||
|
|
||||||
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
|
||||||
|
|
||||||
try {
|
|
||||||
apkVerifier.verifyApk();
|
|
||||||
} catch (ApkVerifier.ApkVerificationException | ApkVerifier.ApkPermissionUnequalException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional permissions are okay. The user is simply
|
|
||||||
* warned about a permission that is not used inside the apk
|
|
||||||
*/
|
|
||||||
@Test(expected = ApkVerifier.ApkPermissionUnequalException.class)
|
|
||||||
public void testAdditionalPermission()
|
|
||||||
throws ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
|
|
||||||
Apk apk = new Apk();
|
|
||||||
apk.packageName = "org.fdroid.permissions.sdk14";
|
|
||||||
apk.targetSdkVersion = 14;
|
|
||||||
apk.requestedPermissions = new String[]{
|
|
||||||
"android.permission.AUTHENTICATE_ACCOUNTS",
|
|
||||||
"android.permission.MANAGE_ACCOUNTS",
|
|
||||||
"android.permission.READ_PROFILE",
|
|
||||||
"android.permission.WRITE_PROFILE",
|
|
||||||
"android.permission.GET_ACCOUNTS",
|
|
||||||
"android.permission.READ_CONTACTS",
|
|
||||||
"android.permission.WRITE_CONTACTS",
|
|
||||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.READ_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.INTERNET",
|
|
||||||
"android.permission.ACCESS_NETWORK_STATE",
|
|
||||||
"android.permission.NFC",
|
|
||||||
"android.permission.READ_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
|
||||||
"android.permission.READ_CALL_LOG", // implied-permission!
|
|
||||||
"android.permission.FAKE_NEW_PERMISSION",
|
|
||||||
};
|
|
||||||
|
|
||||||
Uri uri = Uri.fromFile(sdk14Apk);
|
|
||||||
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
|
||||||
apkVerifier.verifyApk();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Missing permissions are not okay!
|
|
||||||
* The user is then not warned about a permission that the apk uses!
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testMissingPermission() {
|
|
||||||
Apk apk = new Apk();
|
|
||||||
apk.packageName = "org.fdroid.permissions.sdk14";
|
|
||||||
apk.targetSdkVersion = 14;
|
|
||||||
apk.requestedPermissions = new String[]{
|
|
||||||
//"android.permission.AUTHENTICATE_ACCOUNTS",
|
|
||||||
"android.permission.MANAGE_ACCOUNTS",
|
|
||||||
"android.permission.READ_PROFILE",
|
|
||||||
"android.permission.WRITE_PROFILE",
|
|
||||||
"android.permission.GET_ACCOUNTS",
|
|
||||||
"android.permission.READ_CONTACTS",
|
|
||||||
"android.permission.WRITE_CONTACTS",
|
|
||||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.READ_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.INTERNET",
|
|
||||||
"android.permission.ACCESS_NETWORK_STATE",
|
|
||||||
"android.permission.NFC",
|
|
||||||
"android.permission.READ_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
|
||||||
"android.permission.READ_CALL_LOG", // implied-permission!
|
|
||||||
};
|
|
||||||
|
|
||||||
Uri uri = Uri.fromFile(sdk14Apk);
|
|
||||||
|
|
||||||
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
|
||||||
|
|
||||||
try {
|
|
||||||
apkVerifier.verifyApk();
|
|
||||||
fail();
|
|
||||||
} catch (ApkVerifier.ApkVerificationException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail(e.getMessage());
|
|
||||||
} catch (ApkVerifier.ApkPermissionUnequalException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExtendedPerms() throws IOException,
|
|
||||||
ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
|
|
||||||
RepoDetails actualDetails = getFromFile(extendedPermsXml);
|
|
||||||
HashSet<String> expectedSet = new HashSet<>(Arrays.asList(
|
|
||||||
"android.permission.ACCESS_NETWORK_STATE",
|
|
||||||
"android.permission.ACCESS_WIFI_STATE",
|
|
||||||
"android.permission.INTERNET",
|
|
||||||
"android.permission.READ_SYNC_STATS",
|
|
||||||
"android.permission.READ_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_SYNC_SETTINGS",
|
|
||||||
"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
|
|
||||||
"android.permission.READ_CONTACTS",
|
|
||||||
"android.permission.WRITE_CONTACTS",
|
|
||||||
"android.permission.READ_CALENDAR",
|
|
||||||
"android.permission.WRITE_CALENDAR"
|
|
||||||
));
|
|
||||||
if (Build.VERSION.SDK_INT <= 18) {
|
|
||||||
expectedSet.add("android.permission.READ_EXTERNAL_STORAGE");
|
|
||||||
expectedSet.add("android.permission.WRITE_EXTERNAL_STORAGE");
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT <= 22) {
|
|
||||||
expectedSet.add("android.permission.GET_ACCOUNTS");
|
|
||||||
expectedSet.add("android.permission.AUTHENTICATE_ACCOUNTS");
|
|
||||||
expectedSet.add("android.permission.MANAGE_ACCOUNTS");
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
|
||||||
expectedSet.add("android.permission.CAMERA");
|
|
||||||
if (Build.VERSION.SDK_INT <= 23) {
|
|
||||||
expectedSet.add("android.permission.CALL_PHONE");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Apk apk = actualDetails.apks.get(0);
|
|
||||||
HashSet<String> actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
|
|
||||||
for (String permission : expectedSet) {
|
|
||||||
if (!actualSet.contains(permission)) {
|
|
||||||
Log.i(TAG, permission + " in expected but not actual! (android-"
|
|
||||||
+ Build.VERSION.SDK_INT + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String permission : actualSet) {
|
|
||||||
if (!expectedSet.contains(permission)) {
|
|
||||||
Log.i(TAG, permission + " in actual but not expected! (android-"
|
|
||||||
+ Build.VERSION.SDK_INT + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String[] expectedPermissions = expectedSet.toArray(new String[expectedSet.size()]);
|
|
||||||
assertTrue(ApkVerifier.requestedPermissionsEqual(expectedPermissions, apk.requestedPermissions));
|
|
||||||
|
|
||||||
String[] badPermissions = Arrays.copyOf(expectedPermissions, expectedPermissions.length + 1);
|
|
||||||
assertFalse(ApkVerifier.requestedPermissionsEqual(badPermissions, apk.requestedPermissions));
|
|
||||||
badPermissions[badPermissions.length - 1] = "notarealpermission";
|
|
||||||
assertFalse(ApkVerifier.requestedPermissionsEqual(badPermissions, apk.requestedPermissions));
|
|
||||||
|
|
||||||
Uri uri = Uri.fromFile(extendedPermissionsApk);
|
|
||||||
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
|
||||||
apkVerifier.verifyApk();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testImpliedPerms() throws IOException {
|
|
||||||
RepoDetails actualDetails = getFromFile(extendedPermsXml);
|
|
||||||
TreeSet<String> expectedSet = new TreeSet<>(Arrays.asList(
|
|
||||||
"android.permission.ACCESS_NETWORK_STATE",
|
|
||||||
"android.permission.ACCESS_WIFI_STATE",
|
|
||||||
"android.permission.INTERNET",
|
|
||||||
"android.permission.READ_CALENDAR",
|
|
||||||
"android.permission.READ_CONTACTS",
|
|
||||||
"android.permission.READ_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.READ_SYNC_SETTINGS",
|
|
||||||
"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
|
|
||||||
"android.permission.WRITE_CALENDAR",
|
|
||||||
"android.permission.WRITE_CONTACTS",
|
|
||||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.WRITE_SYNC_SETTINGS",
|
|
||||||
"org.dmfs.permission.READ_TASKS",
|
|
||||||
"org.dmfs.permission.WRITE_TASKS"
|
|
||||||
));
|
|
||||||
if (Build.VERSION.SDK_INT <= 22) { // maxSdkVersion="22"
|
|
||||||
expectedSet.addAll(Arrays.asList(
|
|
||||||
"android.permission.AUTHENTICATE_ACCOUNTS",
|
|
||||||
"android.permission.GET_ACCOUNTS",
|
|
||||||
"android.permission.MANAGE_ACCOUNTS"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= 29) {
|
|
||||||
expectedSet.add("android.permission.ACCESS_MEDIA_LOCATION");
|
|
||||||
}
|
|
||||||
Apk apk = actualDetails.apks.get(1);
|
|
||||||
Log.i(TAG, "APK: " + apk.apkName);
|
|
||||||
HashSet<String> actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
|
|
||||||
for (String permission : expectedSet) {
|
|
||||||
if (!actualSet.contains(permission)) {
|
|
||||||
Log.i(TAG, permission + " in expected but not actual! (android-"
|
|
||||||
+ Build.VERSION.SDK_INT + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String permission : actualSet) {
|
|
||||||
if (!expectedSet.contains(permission)) {
|
|
||||||
Log.i(TAG, permission + " in actual but not expected! (android-"
|
|
||||||
+ Build.VERSION.SDK_INT + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String[] expectedPermissions = expectedSet.toArray(new String[expectedSet.size()]);
|
|
||||||
assertTrue(ApkVerifier.requestedPermissionsEqual(expectedPermissions, apk.requestedPermissions));
|
|
||||||
|
|
||||||
expectedSet = new TreeSet<>(Arrays.asList(
|
|
||||||
"android.permission.ACCESS_NETWORK_STATE",
|
|
||||||
"android.permission.ACCESS_WIFI_STATE",
|
|
||||||
"android.permission.AUTHENTICATE_ACCOUNTS",
|
|
||||||
"android.permission.GET_ACCOUNTS",
|
|
||||||
"android.permission.INTERNET",
|
|
||||||
"android.permission.MANAGE_ACCOUNTS",
|
|
||||||
"android.permission.READ_CALENDAR",
|
|
||||||
"android.permission.READ_CONTACTS",
|
|
||||||
"android.permission.READ_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.READ_SYNC_SETTINGS",
|
|
||||||
"android.permission.WRITE_CALENDAR",
|
|
||||||
"android.permission.WRITE_CONTACTS",
|
|
||||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
|
||||||
"android.permission.WRITE_SYNC_SETTINGS",
|
|
||||||
"org.dmfs.permission.READ_TASKS",
|
|
||||||
"org.dmfs.permission.WRITE_TASKS"
|
|
||||||
));
|
|
||||||
if (Build.VERSION.SDK_INT >= 29) {
|
|
||||||
expectedSet.add("android.permission.ACCESS_MEDIA_LOCATION");
|
|
||||||
}
|
|
||||||
expectedPermissions = expectedSet.toArray(new String[expectedSet.size()]);
|
|
||||||
apk = actualDetails.apks.get(2);
|
|
||||||
Log.i(TAG, "APK: " + apk.apkName);
|
|
||||||
actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
|
|
||||||
for (String permission : expectedSet) {
|
|
||||||
if (!actualSet.contains(permission)) {
|
|
||||||
Log.i(TAG, permission + " in expected but not actual! (android-"
|
|
||||||
+ Build.VERSION.SDK_INT + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String permission : actualSet) {
|
|
||||||
if (!expectedSet.contains(permission)) {
|
|
||||||
Log.i(TAG, permission + " in actual but not expected! (android-"
|
|
||||||
+ Build.VERSION.SDK_INT + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertTrue(ApkVerifier.requestedPermissionsEqual(expectedPermissions, apk.requestedPermissions));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private RepoDetails getFromFile(File indexFile) throws IOException {
|
|
||||||
InputStream inputStream = null;
|
|
||||||
try {
|
|
||||||
inputStream = new FileInputStream(indexFile);
|
|
||||||
return RepoDetails.getFromFile(inputStream, Repo.PUSH_REQUEST_IGNORE);
|
|
||||||
} finally {
|
|
||||||
Utils.closeQuietly(inputStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.jmdns.ServiceEvent;
|
|
||||||
import javax.jmdns.ServiceListener;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class BonjourManagerTest {
|
|
||||||
|
|
||||||
private static final String NAME = "Robolectric-test";
|
|
||||||
private static final String LOCALHOST = "localhost";
|
|
||||||
private static final int PORT = 8888;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStartStop() throws InterruptedException {
|
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
FDroidApp.ipAddressString = LOCALHOST;
|
|
||||||
FDroidApp.port = PORT;
|
|
||||||
|
|
||||||
final CountDownLatch addedLatch = new CountDownLatch(1);
|
|
||||||
final CountDownLatch resolvedLatch = new CountDownLatch(1);
|
|
||||||
final CountDownLatch removedLatch = new CountDownLatch(1);
|
|
||||||
BonjourManager.start(context, NAME, false,
|
|
||||||
new ServiceListener() {
|
|
||||||
@Override
|
|
||||||
public void serviceAdded(ServiceEvent serviceEvent) {
|
|
||||||
System.out.println("Service added: " + serviceEvent.getInfo());
|
|
||||||
if (NAME.equals(serviceEvent.getName())) {
|
|
||||||
addedLatch.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serviceRemoved(ServiceEvent serviceEvent) {
|
|
||||||
System.out.println("Service removed: " + serviceEvent.getInfo());
|
|
||||||
removedLatch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serviceResolved(ServiceEvent serviceEvent) {
|
|
||||||
System.out.println("Service resolved: " + serviceEvent.getInfo());
|
|
||||||
if (NAME.equals(serviceEvent.getName())) {
|
|
||||||
resolvedLatch.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, getBlankServiceListener());
|
|
||||||
BonjourManager.setVisible(context, true);
|
|
||||||
assertTrue(addedLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
assertTrue(resolvedLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
BonjourManager.setVisible(context, false);
|
|
||||||
assertTrue(removedLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
BonjourManager.stop(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRestart() throws InterruptedException {
|
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
|
||||||
|
|
||||||
FDroidApp.ipAddressString = LOCALHOST;
|
|
||||||
FDroidApp.port = PORT;
|
|
||||||
|
|
||||||
BonjourManager.start(context, NAME, false, getBlankServiceListener(), getBlankServiceListener());
|
|
||||||
|
|
||||||
final CountDownLatch addedLatch = new CountDownLatch(1);
|
|
||||||
final CountDownLatch resolvedLatch = new CountDownLatch(1);
|
|
||||||
final CountDownLatch removedLatch = new CountDownLatch(1);
|
|
||||||
BonjourManager.restart(context, NAME, false,
|
|
||||||
new ServiceListener() {
|
|
||||||
@Override
|
|
||||||
public void serviceAdded(ServiceEvent serviceEvent) {
|
|
||||||
System.out.println("Service added: " + serviceEvent.getInfo());
|
|
||||||
if (NAME.equals(serviceEvent.getName())) {
|
|
||||||
addedLatch.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serviceRemoved(ServiceEvent serviceEvent) {
|
|
||||||
System.out.println("Service removed: " + serviceEvent.getInfo());
|
|
||||||
removedLatch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serviceResolved(ServiceEvent serviceEvent) {
|
|
||||||
System.out.println("Service resolved: " + serviceEvent.getInfo());
|
|
||||||
if (NAME.equals(serviceEvent.getName())) {
|
|
||||||
resolvedLatch.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, getBlankServiceListener());
|
|
||||||
BonjourManager.setVisible(context, true);
|
|
||||||
assertTrue(addedLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
assertTrue(resolvedLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
BonjourManager.setVisible(context, false);
|
|
||||||
assertTrue(removedLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
BonjourManager.stop(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ServiceListener getBlankServiceListener() {
|
|
||||||
return new ServiceListener() {
|
|
||||||
@Override
|
|
||||||
public void serviceAdded(ServiceEvent serviceEvent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serviceRemoved(ServiceEvent serviceEvent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serviceResolved(ServiceEvent serviceEvent) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,192 +0,0 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import android.util.Log;
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.Netstat;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
@LargeTest
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class LocalHTTPDManagerTest {
|
|
||||||
private static final String TAG = "LocalHTTPDManagerTest";
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private LocalBroadcastManager lbm;
|
|
||||||
|
|
||||||
private static final String LOCALHOST = "localhost";
|
|
||||||
private static final int PORT = 8888;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
context = ApplicationProvider.getApplicationContext();
|
|
||||||
lbm = LocalBroadcastManager.getInstance(context);
|
|
||||||
|
|
||||||
FDroidApp.ipAddressString = LOCALHOST;
|
|
||||||
FDroidApp.port = PORT;
|
|
||||||
|
|
||||||
for (Netstat.Connection connection : Netstat.getConnections()) { // NOPMD
|
|
||||||
Log.i("LocalHTTPDManagerTest", "connection: " + connection.toString());
|
|
||||||
}
|
|
||||||
assertFalse(Utils.isServerSocketInUse(PORT));
|
|
||||||
LocalHTTPDManager.stop(context);
|
|
||||||
|
|
||||||
for (Netstat.Connection connection : Netstat.getConnections()) { // NOPMD
|
|
||||||
Log.i("LocalHTTPDManagerTest", "connection: " + connection.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
lbm.unregisterReceiver(startedReceiver);
|
|
||||||
lbm.unregisterReceiver(stoppedReceiver);
|
|
||||||
lbm.unregisterReceiver(errorReceiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void testStartStop() throws InterruptedException {
|
|
||||||
Log.i(TAG, "testStartStop");
|
|
||||||
|
|
||||||
final CountDownLatch startLatch = new CountDownLatch(1);
|
|
||||||
BroadcastReceiver latchReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
startLatch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
|
||||||
lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
|
||||||
lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
|
||||||
LocalHTTPDManager.start(context, false);
|
|
||||||
assertTrue(startLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
assertTrue(Utils.isServerSocketInUse(PORT));
|
|
||||||
assertTrue(Utils.canConnectToSocket(LOCALHOST, PORT));
|
|
||||||
lbm.unregisterReceiver(latchReceiver);
|
|
||||||
lbm.unregisterReceiver(stoppedReceiver);
|
|
||||||
lbm.unregisterReceiver(errorReceiver);
|
|
||||||
|
|
||||||
final CountDownLatch stopLatch = new CountDownLatch(1);
|
|
||||||
latchReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
stopLatch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lbm.registerReceiver(startedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
|
||||||
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
|
||||||
lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
|
||||||
LocalHTTPDManager.stop(context);
|
|
||||||
assertTrue(stopLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
assertFalse(Utils.isServerSocketInUse(PORT));
|
|
||||||
assertFalse(Utils.canConnectToSocket(LOCALHOST, PORT)); // if this is flaky, just remove it
|
|
||||||
lbm.unregisterReceiver(latchReceiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testError() throws InterruptedException, IOException {
|
|
||||||
Log.i("LocalHTTPDManagerTest", "testError");
|
|
||||||
ServerSocket blockerSocket = new ServerSocket(PORT);
|
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
BroadcastReceiver latchReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lbm.registerReceiver(startedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
|
||||||
lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
|
||||||
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
|
||||||
LocalHTTPDManager.start(context, false);
|
|
||||||
assertTrue(latch.await(30, TimeUnit.SECONDS));
|
|
||||||
assertTrue(Utils.isServerSocketInUse(PORT));
|
|
||||||
assertNotEquals(PORT, FDroidApp.port);
|
|
||||||
assertFalse(Utils.isServerSocketInUse(FDroidApp.port));
|
|
||||||
lbm.unregisterReceiver(latchReceiver);
|
|
||||||
blockerSocket.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRestart() throws InterruptedException, IOException {
|
|
||||||
Log.i("LocalHTTPDManagerTest", "testRestart");
|
|
||||||
assertFalse(Utils.isServerSocketInUse(PORT));
|
|
||||||
final CountDownLatch startLatch = new CountDownLatch(1);
|
|
||||||
BroadcastReceiver latchReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
startLatch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
|
||||||
lbm.registerReceiver(stoppedReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STOPPED));
|
|
||||||
lbm.registerReceiver(errorReceiver, new IntentFilter(LocalHTTPDManager.ACTION_ERROR));
|
|
||||||
LocalHTTPDManager.start(context, false);
|
|
||||||
assertTrue(startLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
assertTrue(Utils.isServerSocketInUse(PORT));
|
|
||||||
lbm.unregisterReceiver(latchReceiver);
|
|
||||||
lbm.unregisterReceiver(stoppedReceiver);
|
|
||||||
|
|
||||||
final CountDownLatch restartLatch = new CountDownLatch(1);
|
|
||||||
latchReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
restartLatch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lbm.registerReceiver(latchReceiver, new IntentFilter(LocalHTTPDManager.ACTION_STARTED));
|
|
||||||
LocalHTTPDManager.restart(context, false);
|
|
||||||
assertTrue(restartLatch.await(30, TimeUnit.SECONDS));
|
|
||||||
lbm.unregisterReceiver(latchReceiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final BroadcastReceiver startedReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
|
|
||||||
Log.i(TAG, "startedReceiver: " + message);
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final BroadcastReceiver stoppedReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
|
|
||||||
Log.i(TAG, "stoppedReceiver: " + message);
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final BroadcastReceiver errorReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String message = intent.getStringExtra(Intent.EXTRA_TEXT);
|
|
||||||
Log.i(TAG, "errorReceiver: " + message);
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,159 +0,0 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid.net;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import org.fdroid.fdroid.ProgressListener;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
public class HttpDownloaderTest {
|
|
||||||
private static final String TAG = "HttpDownloaderTest";
|
|
||||||
|
|
||||||
static final String[] URLS;
|
|
||||||
|
|
||||||
// https://developer.android.com/reference/javax/net/ssl/SSLContext
|
|
||||||
static {
|
|
||||||
ArrayList<String> tempUrls = new ArrayList<>(Arrays.asList(
|
|
||||||
"https://f-droid.org/repo/index-v1.jar",
|
|
||||||
// sites that use SNI for HTTPS
|
|
||||||
"https://mirrors.kernel.org/debian/dists/stable/Release",
|
|
||||||
"https://fdroid.tetaneutral.net/fdroid/repo/index-v1.jar",
|
|
||||||
"https://ftp.fau.de/fdroid/repo/index-v1.jar",
|
|
||||||
//"https://microg.org/fdroid/repo/index-v1.jar",
|
|
||||||
//"https://grobox.de/fdroid/repo/index.jar",
|
|
||||||
"https://guardianproject.info/fdroid/repo/index-v1.jar"
|
|
||||||
));
|
|
||||||
if (Build.VERSION.SDK_INT >= 22) {
|
|
||||||
tempUrls.addAll(Arrays.asList(
|
|
||||||
"https://en.wikipedia.org/wiki/Index.html", // no SNI but weird ipv6 lookup issues
|
|
||||||
"https://mirror.cyberbits.eu/fdroid/repo/index-v1.jar" // TLSv1.2 only and SNI
|
|
||||||
));
|
|
||||||
}
|
|
||||||
URLS = tempUrls.toArray(new String[tempUrls.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean receivedProgress;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void downloadUninterruptedTest() throws IOException, InterruptedException {
|
|
||||||
for (String urlString : URLS) {
|
|
||||||
Log.i(TAG, "URL: " + urlString);
|
|
||||||
Uri uri = Uri.parse(urlString);
|
|
||||||
File destFile = File.createTempFile("dl-", "");
|
|
||||||
HttpDownloader httpDownloader = new HttpDownloader(uri, destFile);
|
|
||||||
httpDownloader.download();
|
|
||||||
assertTrue(destFile.exists());
|
|
||||||
assertTrue(destFile.canRead());
|
|
||||||
destFile.deleteOnExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void downloadUninterruptedTestWithProgress() throws IOException, InterruptedException {
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
String urlString = "https://f-droid.org/repo/index.jar";
|
|
||||||
receivedProgress = false;
|
|
||||||
Uri uri = Uri.parse(urlString);
|
|
||||||
File destFile = File.createTempFile("dl-", "");
|
|
||||||
final HttpDownloader httpDownloader = new HttpDownloader(uri, destFile);
|
|
||||||
httpDownloader.setListener(new ProgressListener() {
|
|
||||||
@Override
|
|
||||||
public void onProgress(long bytesRead, long totalBytes) {
|
|
||||||
receivedProgress = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
httpDownloader.download();
|
|
||||||
latch.countDown();
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
latch.await(100, TimeUnit.SECONDS); // either 2 progress reports or 100 seconds
|
|
||||||
assertTrue(destFile.exists());
|
|
||||||
assertTrue(destFile.canRead());
|
|
||||||
assertTrue(receivedProgress);
|
|
||||||
destFile.deleteOnExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void downloadHttpBasicAuth() throws IOException, InterruptedException {
|
|
||||||
Uri uri = Uri.parse("https://httpbin.org/basic-auth/myusername/supersecretpassword");
|
|
||||||
File destFile = File.createTempFile("dl-", "");
|
|
||||||
HttpDownloader httpDownloader = new HttpDownloader(uri, destFile, "myusername", "supersecretpassword");
|
|
||||||
httpDownloader.download();
|
|
||||||
assertTrue(destFile.exists());
|
|
||||||
assertTrue(destFile.canRead());
|
|
||||||
destFile.deleteOnExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void downloadHttpBasicAuthWrongPassword() throws IOException, InterruptedException {
|
|
||||||
Uri uri = Uri.parse("https://httpbin.org/basic-auth/myusername/supersecretpassword");
|
|
||||||
File destFile = File.createTempFile("dl-", "");
|
|
||||||
HttpDownloader httpDownloader = new HttpDownloader(uri, destFile, "myusername", "wrongpassword");
|
|
||||||
httpDownloader.download();
|
|
||||||
assertFalse(destFile.exists());
|
|
||||||
destFile.deleteOnExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void downloadHttpBasicAuthWrongUsername() throws IOException, InterruptedException {
|
|
||||||
Uri uri = Uri.parse("https://httpbin.org/basic-auth/myusername/supersecretpassword");
|
|
||||||
File destFile = File.createTempFile("dl-", "");
|
|
||||||
HttpDownloader httpDownloader = new HttpDownloader(uri, destFile, "wrongusername", "supersecretpassword");
|
|
||||||
httpDownloader.download();
|
|
||||||
assertFalse(destFile.exists());
|
|
||||||
destFile.deleteOnExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void downloadThenCancel() throws IOException, InterruptedException {
|
|
||||||
final CountDownLatch latch = new CountDownLatch(2);
|
|
||||||
Uri uri = Uri.parse("https://f-droid.org/repo/index.jar");
|
|
||||||
File destFile = File.createTempFile("dl-", "");
|
|
||||||
final HttpDownloader httpDownloader = new HttpDownloader(uri, destFile);
|
|
||||||
httpDownloader.setListener(new ProgressListener() {
|
|
||||||
@Override
|
|
||||||
public void onProgress(long bytesRead, long totalBytes) {
|
|
||||||
receivedProgress = true;
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
httpDownloader.download();
|
|
||||||
fail();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// success!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
latch.await(100, TimeUnit.SECONDS); // either 2 progress reports or 100 seconds
|
|
||||||
httpDownloader.cancelDownload();
|
|
||||||
assertTrue(receivedProgress);
|
|
||||||
destFile.deleteOnExit();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,203 +0,0 @@
|
|||||||
package org.fdroid.fdroid.updater;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.os.Looper;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.Hasher;
|
|
||||||
import org.fdroid.fdroid.IndexUpdater;
|
|
||||||
import org.fdroid.fdroid.Preferences;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
import org.fdroid.fdroid.data.Apk;
|
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
|
||||||
import org.fdroid.fdroid.data.App;
|
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
|
||||||
import org.fdroid.fdroid.data.Repo;
|
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
|
||||||
import org.fdroid.fdroid.data.Schema;
|
|
||||||
import org.fdroid.fdroid.nearby.LocalHTTPD;
|
|
||||||
import org.fdroid.fdroid.nearby.LocalRepoKeyStore;
|
|
||||||
import org.fdroid.fdroid.nearby.LocalRepoManager;
|
|
||||||
import org.fdroid.fdroid.nearby.LocalRepoService;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
@LargeTest
|
|
||||||
public class SwapRepoEmulatorTest {
|
|
||||||
public static final String TAG = "SwapRepoEmulatorTest";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see org.fdroid.fdroid.nearby.WifiStateChangeService.WifiInfoThread#run()
|
|
||||||
*/
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void testSwap()
|
|
||||||
throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException {
|
|
||||||
Looper.prepare();
|
|
||||||
LocalHTTPD localHttpd = null;
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "REPO: " + FDroidApp.repo);
|
|
||||||
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
||||||
Preferences.setupForTests(context);
|
|
||||||
|
|
||||||
FDroidApp.initWifiSettings();
|
|
||||||
assertNull(FDroidApp.repo.address);
|
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (FDroidApp.repo.address == null) {
|
|
||||||
try {
|
|
||||||
Log.i(TAG, "Waiting for IP address... " + FDroidApp.repo.address);
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
latch.await(10, TimeUnit.MINUTES);
|
|
||||||
assertNotNull(FDroidApp.repo.address);
|
|
||||||
|
|
||||||
LocalRepoService.runProcess(context, new String[]{context.getPackageName()});
|
|
||||||
Log.i(TAG, "REPO: " + FDroidApp.repo);
|
|
||||||
File indexJarFile = LocalRepoManager.get(context).getIndexJar();
|
|
||||||
assertTrue(indexJarFile.isFile());
|
|
||||||
|
|
||||||
localHttpd = new LocalHTTPD(
|
|
||||||
context,
|
|
||||||
null,
|
|
||||||
FDroidApp.port,
|
|
||||||
LocalRepoManager.get(context).getWebRoot(),
|
|
||||||
false);
|
|
||||||
localHttpd.start();
|
|
||||||
Thread.sleep(100); // give the server some tine to start.
|
|
||||||
assertTrue(localHttpd.isAlive());
|
|
||||||
|
|
||||||
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
|
|
||||||
Certificate localCert = localRepoKeyStore.getCertificate();
|
|
||||||
String signingCert = Hasher.hex(localCert);
|
|
||||||
assertFalse(TextUtils.isEmpty(signingCert));
|
|
||||||
assertFalse(TextUtils.isEmpty(Utils.calcFingerprint(localCert)));
|
|
||||||
|
|
||||||
Repo repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
|
|
||||||
while (repoToDelete != null) {
|
|
||||||
Log.d(TAG, "Removing old test swap repo matching this one: " + repoToDelete.address);
|
|
||||||
RepoProvider.Helper.remove(context, repoToDelete.getId());
|
|
||||||
repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentValues values = new ContentValues(4);
|
|
||||||
values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert);
|
|
||||||
values.put(Schema.RepoTable.Cols.ADDRESS, FDroidApp.repo.address);
|
|
||||||
values.put(Schema.RepoTable.Cols.NAME, FDroidApp.repo.name);
|
|
||||||
values.put(Schema.RepoTable.Cols.IS_SWAP, true);
|
|
||||||
final String lastEtag = UUID.randomUUID().toString();
|
|
||||||
values.put(Schema.RepoTable.Cols.LAST_ETAG, lastEtag);
|
|
||||||
RepoProvider.Helper.insert(context, values);
|
|
||||||
Repo repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
|
|
||||||
assertTrue(repo.isSwap);
|
|
||||||
assertNotEquals(-1, repo.getId());
|
|
||||||
assertTrue(repo.name.startsWith(FDroidApp.repo.name));
|
|
||||||
assertEquals(lastEtag, repo.lastetag);
|
|
||||||
assertNull(repo.lastUpdated);
|
|
||||||
|
|
||||||
assertTrue(isPortInUse(FDroidApp.ipAddressString, FDroidApp.port));
|
|
||||||
Thread.sleep(100);
|
|
||||||
IndexUpdater updater = new IndexUpdater(context, repo);
|
|
||||||
updater.update();
|
|
||||||
assertTrue(updater.hasChanged());
|
|
||||||
|
|
||||||
repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address);
|
|
||||||
final Date lastUpdated = repo.lastUpdated;
|
|
||||||
assertTrue("repo lastUpdated should be updated", new Date(2019, 5, 13).compareTo(repo.lastUpdated) > 0);
|
|
||||||
|
|
||||||
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(),
|
|
||||||
context.getPackageName(), repo.getId());
|
|
||||||
assertEquals(context.getPackageName(), app.packageName);
|
|
||||||
|
|
||||||
List<Apk> apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL);
|
|
||||||
assertEquals(1, apks.size());
|
|
||||||
for (Apk apk : apks) {
|
|
||||||
Log.i(TAG, "Apk: " + apk);
|
|
||||||
assertEquals(context.getPackageName(), apk.packageName);
|
|
||||||
assertEquals(BuildConfig.VERSION_NAME, apk.versionName);
|
|
||||||
assertEquals(BuildConfig.VERSION_CODE, apk.versionCode);
|
|
||||||
assertEquals(app.repoId, apk.repoId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
|
|
||||||
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
|
||||||
List<ResolveInfo> resolveInfoList = context.getPackageManager().queryIntentActivities(mainIntent, 0);
|
|
||||||
HashSet<String> packageNames = new HashSet<>();
|
|
||||||
for (ResolveInfo resolveInfo : resolveInfoList) {
|
|
||||||
if (!isSystemPackage(resolveInfo)) {
|
|
||||||
Log.i(TAG, "resolveInfo: " + resolveInfo);
|
|
||||||
packageNames.add(resolveInfo.activityInfo.packageName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LocalRepoService.runProcess(context, packageNames.toArray(new String[0]));
|
|
||||||
|
|
||||||
updater = new IndexUpdater(context, repo);
|
|
||||||
updater.update();
|
|
||||||
assertTrue(updater.hasChanged());
|
|
||||||
assertTrue("repo lastUpdated should be updated", lastUpdated.compareTo(repo.lastUpdated) < 0);
|
|
||||||
|
|
||||||
for (String packageName : packageNames) {
|
|
||||||
assertNotNull(ApkProvider.Helper.findByPackageName(context, packageName));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (localHttpd != null) {
|
|
||||||
localHttpd.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (localHttpd != null) {
|
|
||||||
assertFalse(localHttpd.isAlive());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPortInUse(String host, int port) {
|
|
||||||
boolean result = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
(new Socket(host, port)).close();
|
|
||||||
result = true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Could not connect.
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSystemPackage(ResolveInfo resolveInfo) {
|
|
||||||
return (resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
package org.fdroid.fdroid.work;
|
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import androidx.work.OneTimeWorkRequest;
|
|
||||||
import androidx.work.WorkInfo;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.fdroid.fdroid.compat.FileCompatTest;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This test cannot run on Robolectric unfortunately since it does not support
|
|
||||||
* getting the timestamps from the files completely.
|
|
||||||
* <p>
|
|
||||||
* This is marked with {@link LargeTest} because it always fails on the emulator
|
|
||||||
* tests on GitLab CI. That excludes it from the test run there.
|
|
||||||
*/
|
|
||||||
@LargeTest
|
|
||||||
public class CleanCacheWorkerTest {
|
|
||||||
public static final String TAG = "CleanCacheWorkerEmulatorTest";
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public WorkManagerTestRule workManagerTestRule = new WorkManagerTestRule();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWorkRequest() throws ExecutionException, InterruptedException {
|
|
||||||
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(CleanCacheWorker.class).build();
|
|
||||||
workManagerTestRule.workManager.enqueue(request).getResult();
|
|
||||||
ListenableFuture<WorkInfo> workInfo = workManagerTestRule.workManager.getWorkInfoById(request.getId());
|
|
||||||
assertEquals(WorkInfo.State.SUCCEEDED, workInfo.get().getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClearOldFiles() throws IOException, InterruptedException {
|
|
||||||
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
|
||||||
File tempDir = FileCompatTest.getWriteableDir(instrumentation);
|
|
||||||
assertTrue(tempDir.isDirectory());
|
|
||||||
assertTrue(tempDir.canWrite());
|
|
||||||
|
|
||||||
File dir = new File(tempDir, "F-Droid-test.clearOldFiles");
|
|
||||||
FileUtils.deleteQuietly(dir);
|
|
||||||
assertTrue(dir.mkdirs());
|
|
||||||
assertTrue(dir.isDirectory());
|
|
||||||
|
|
||||||
File first = new File(dir, "first");
|
|
||||||
first.deleteOnExit();
|
|
||||||
|
|
||||||
File second = new File(dir, "second");
|
|
||||||
second.deleteOnExit();
|
|
||||||
|
|
||||||
assertFalse(first.exists());
|
|
||||||
assertFalse(second.exists());
|
|
||||||
|
|
||||||
assertTrue(first.createNewFile());
|
|
||||||
assertTrue(first.exists());
|
|
||||||
|
|
||||||
Thread.sleep(7000);
|
|
||||||
assertTrue(second.createNewFile());
|
|
||||||
assertTrue(second.exists());
|
|
||||||
|
|
||||||
CleanCacheWorker.clearOldFiles(dir, 3000); // check all in dir
|
|
||||||
assertFalse(first.exists());
|
|
||||||
assertTrue(second.exists());
|
|
||||||
|
|
||||||
Thread.sleep(7000);
|
|
||||||
CleanCacheWorker.clearOldFiles(second, 3000); // check just second file
|
|
||||||
assertFalse(first.exists());
|
|
||||||
assertFalse(second.exists());
|
|
||||||
|
|
||||||
// make sure it doesn't freak out on a non-existent file
|
|
||||||
File nonexistent = new File(tempDir, "nonexistent");
|
|
||||||
CleanCacheWorker.clearOldFiles(nonexistent, 1);
|
|
||||||
CleanCacheWorker.clearOldFiles(null, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO enable this once getImageCacheDir() can be mocked or provide a writable dir in the test
|
|
||||||
@Test
|
|
||||||
public void testDeleteOldIcons() throws IOException {
|
|
||||||
Context context = InstrumentationRegistry.getInstrumentation().getContext();
|
|
||||||
File imageCacheDir = Utils.getImageCacheDir(context);
|
|
||||||
imageCacheDir.mkdirs();
|
|
||||||
assertTrue(imageCacheDir.isDirectory());
|
|
||||||
File oldIcon = new File(imageCacheDir, "old.png");
|
|
||||||
assertTrue(oldIcon.createNewFile());
|
|
||||||
Assume.assumeTrue("test environment must be able to set LastModified time",
|
|
||||||
oldIcon.setLastModified(System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 370)));
|
|
||||||
File currentIcon = new File(imageCacheDir, "current.png");
|
|
||||||
assertTrue(currentIcon.createNewFile());
|
|
||||||
CleanCacheWorker.deleteOldIcons(context);
|
|
||||||
assertTrue(currentIcon.exists());
|
|
||||||
assertFalse(oldIcon.exists());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 Hans-Christoph Steiner <hans@eds.org>
|
|
||||||
*
|
|
||||||
* 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.work;
|
|
||||||
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
|
|
||||||
import androidx.test.filters.LargeTest;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import androidx.work.OneTimeWorkRequest;
|
|
||||||
import androidx.work.WorkInfo;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This actually runs {@link FDroidMetricsWorker} on a device/emulator and
|
|
||||||
* submits a report to https://metrics.cleaninsights.org
|
|
||||||
* <p>
|
|
||||||
* This is marked with {@link LargeTest} to exclude it from running on GitLab CI
|
|
||||||
* because it always fails on the emulator tests there. Also, it actually submits
|
|
||||||
* a report.
|
|
||||||
*/
|
|
||||||
@LargeTest
|
|
||||||
public class FDroidMetricsWorkerTest {
|
|
||||||
public static final String TAG = "FDroidMetricsWorkerTest";
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public WorkManagerTestRule workManagerTestRule = new WorkManagerTestRule();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A test for easy manual testing.
|
|
||||||
*/
|
|
||||||
@Ignore
|
|
||||||
@Test
|
|
||||||
public void testGenerateReport() throws IOException {
|
|
||||||
String json = FDroidMetricsWorker.generateReport(
|
|
||||||
InstrumentationRegistry.getInstrumentation().getTargetContext());
|
|
||||||
System.out.println(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWorkRequest() throws ExecutionException, InterruptedException {
|
|
||||||
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(FDroidMetricsWorker.class).build();
|
|
||||||
workManagerTestRule.workManager.enqueue(request).getResult();
|
|
||||||
ListenableFuture<WorkInfo> workInfo = workManagerTestRule.workManager.getWorkInfoById(request.getId());
|
|
||||||
assertEquals(WorkInfo.State.SUCCEEDED, workInfo.get().getState());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package org.fdroid.fdroid.work;
|
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import androidx.work.Configuration;
|
|
||||||
import androidx.work.WorkManager;
|
|
||||||
import androidx.work.testing.SynchronousExecutor;
|
|
||||||
import androidx.work.testing.WorkManagerTestInitHelper;
|
|
||||||
import org.junit.rules.TestWatcher;
|
|
||||||
import org.junit.runner.Description;
|
|
||||||
|
|
||||||
public class WorkManagerTestRule extends TestWatcher {
|
|
||||||
Context targetContext;
|
|
||||||
Context testContext;
|
|
||||||
Configuration configuration;
|
|
||||||
WorkManager workManager;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void starting(Description description) {
|
|
||||||
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
|
||||||
targetContext = instrumentation.getTargetContext();
|
|
||||||
testContext = instrumentation.getContext();
|
|
||||||
configuration = new Configuration.Builder()
|
|
||||||
.setMinimumLoggingLevel(Log.DEBUG)
|
|
||||||
.setExecutor(new SynchronousExecutor())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
WorkManagerTestInitHelper.initializeTestWorkManager(targetContext, configuration);
|
|
||||||
workManager = WorkManager.getInstance(targetContext);
|
|
||||||
}
|
|
||||||
}
|
|
9
app/src/androidTest/proguard-rules.pro
vendored
@ -1,7 +1,3 @@
|
|||||||
-dontoptimize
|
|
||||||
-dontwarn
|
|
||||||
-dontobfuscate
|
|
||||||
|
|
||||||
-dontwarn android.test.**
|
-dontwarn android.test.**
|
||||||
-dontwarn android.support.test.**
|
-dontwarn android.support.test.**
|
||||||
-dontnote junit.framework.**
|
-dontnote junit.framework.**
|
||||||
@ -18,8 +14,3 @@
|
|||||||
|
|
||||||
-keep class junit.** { *; }
|
-keep class junit.** { *; }
|
||||||
-dontwarn junit.**
|
-dontwarn junit.**
|
||||||
|
|
||||||
# This is necessary so that RemoteWorkManager can be initialized (also marked with @Keep)
|
|
||||||
-keep class androidx.work.multiprocess.RemoteWorkManagerClient {
|
|
||||||
public <init>(...);
|
|
||||||
}
|
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Senecto Limited
|
|
||||||
*
|
|
||||||
* 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.nearby;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy version for basic app flavor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class BluetoothClient {
|
|
||||||
|
|
||||||
public BluetoothClient(String ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public BluetoothConnection openConnection() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
|
||||||
|
|
||||||
public class LocalRepoManager {
|
|
||||||
public static final String[] WEB_ROOT_ASSET_FILES = {};
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Hans-Christoph Steiner <hans@eds.org>
|
|
||||||
*
|
|
||||||
* 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.nearby;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy version for basic app flavor.
|
|
||||||
*/
|
|
||||||
public class SDCardScannerService {
|
|
||||||
public static void scan(Context context) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Hans-Christoph Steiner <hans@eds.org>
|
|
||||||
*
|
|
||||||
* 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.nearby;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy version for basic app flavor.
|
|
||||||
*/
|
|
||||||
public class SwapService {
|
|
||||||
public static void start(Context context) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Senecto Limited
|
|
||||||
*
|
|
||||||
* 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.nearby;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy version for basic app flavor.
|
|
||||||
*/
|
|
||||||
public class SwapWorkflowActivity {
|
|
||||||
|
|
||||||
public static final String EXTRA_PREVENT_FURTHER_SWAP_REQUESTS = "preventFurtherSwap";
|
|
||||||
|
|
||||||
public static void requestSwap(Context context, Uri uri) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Senecto Limited
|
|
||||||
*
|
|
||||||
* 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.nearby;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy version for basic app flavor.
|
|
||||||
*/
|
|
||||||
public class TreeUriScannerIntentService {
|
|
||||||
public static void onActivityResult(AppCompatActivity activity, Intent intent) {
|
|
||||||
throw new IllegalStateException("unimplemented");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Senecto Limited
|
|
||||||
*
|
|
||||||
* 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.nearby;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy version for basic app flavor.
|
|
||||||
*/
|
|
||||||
public class WifiStateChangeService {
|
|
||||||
public static void start(Context context, @Nullable Intent intent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WifiInfoThread extends Thread {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Senecto Limited
|
|
||||||
*
|
|
||||||
* 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.nearby.peers;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy version for basic app flavor.
|
|
||||||
*/
|
|
||||||
public class WifiPeer {
|
|
||||||
public WifiPeer(NewRepoConfig config) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Senecto Limited
|
|
||||||
*
|
|
||||||
* 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.views.main;
|
|
||||||
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.views.PreferencesFragment;
|
|
||||||
import org.fdroid.fdroid.views.updates.UpdatesViewBinder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides which view on the main screen to attach to a given {@link FrameLayout}. This class
|
|
||||||
* doesn't know which view it will be rendering at the time it is constructed. Rather, at some
|
|
||||||
* point in the future the {@link MainViewAdapter} will have information about which view we
|
|
||||||
* are required to render, and will invoke the relevant "bind*()" method on this class.
|
|
||||||
*/
|
|
||||||
class MainViewController extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final AppCompatActivity activity;
|
|
||||||
private final FrameLayout frame;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private UpdatesViewBinder updatesView = null;
|
|
||||||
|
|
||||||
MainViewController(AppCompatActivity activity, FrameLayout frame) {
|
|
||||||
super(frame);
|
|
||||||
this.activity = activity;
|
|
||||||
this.frame = frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see LatestViewBinder
|
|
||||||
*/
|
|
||||||
public void bindLatestView() {
|
|
||||||
new LatestViewBinder(activity, frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see UpdatesViewBinder
|
|
||||||
*/
|
|
||||||
public void bindUpdates() {
|
|
||||||
if (updatesView == null) {
|
|
||||||
updatesView = new UpdatesViewBinder(activity, frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
updatesView.bind();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unbindUpdates() {
|
|
||||||
if (updatesView != null) {
|
|
||||||
updatesView.unbind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bindCategoriesView() {
|
|
||||||
throw new IllegalStateException("unimplemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bindSwapView() {
|
|
||||||
throw new IllegalStateException("unimplemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches a {@link PreferencesFragment} to the view. Everything else is managed by the
|
|
||||||
* fragment itself, so no further work needs to be done by this view binder.
|
|
||||||
* <p>
|
|
||||||
* Note: It is tricky to attach a {@link Fragment} to a view from this view holder. This is due
|
|
||||||
* to the way in which the {@link RecyclerView} will reuse existing views and ask us to
|
|
||||||
* put a settings fragment in there at arbitrary times. Usually it wont be the same view we
|
|
||||||
* attached the fragment to last time, which causes weirdness. The solution is to use code from
|
|
||||||
* the com.lsjwzh.widget.recyclerviewpager.FragmentStatePagerAdapter which manages this.
|
|
||||||
* The code has been ported to {@link SettingsView}.
|
|
||||||
*
|
|
||||||
* @see SettingsView
|
|
||||||
*/
|
|
||||||
public void bindSettingsView() {
|
|
||||||
activity.getLayoutInflater().inflate(R.layout.main_tab_settings, frame, true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package org.fdroid.fdroid.views.main;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
class NearbyViewBinder {
|
|
||||||
public static void updateUsbOtg(Context context) {
|
|
||||||
throw new IllegalStateException("unimplemented");
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 65 KiB |
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
tools:ignore="MenuTitle">
|
|
||||||
<!-- android:title and android:icon are set dynamically in MainActivity -->
|
|
||||||
<item
|
|
||||||
app:showAsAction="ifRoom|withText"
|
|
||||||
android:id="@+id/latest"/>
|
|
||||||
<item
|
|
||||||
app:showAsAction="ifRoom|withText"
|
|
||||||
android:id="@+id/updates"/>
|
|
||||||
<item
|
|
||||||
app:showAsAction="ifRoom|withText"
|
|
||||||
android:id="@+id/settings"/>
|
|
||||||
</menu>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">F-Droid Basic</string>
|
|
||||||
<string name="about_title">About F-Droid Basic</string>
|
|
||||||
</resources>
|
|
@ -1,183 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<PreferenceScreen android:title="@string/about_title"
|
|
||||||
android:key="pref_about" />
|
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/preference_category__my_apps">
|
|
||||||
<PreferenceScreen android:title="@string/preference_manage_installed_apps">
|
|
||||||
<intent
|
|
||||||
android:action="android.intent.action.MAIN"
|
|
||||||
android:targetPackage="@string/applicationId"
|
|
||||||
android:targetClass="org.fdroid.fdroid.views.installed.InstalledAppsActivity"/>
|
|
||||||
</PreferenceScreen>
|
|
||||||
<PreferenceScreen
|
|
||||||
android:title="@string/menu_manage"
|
|
||||||
android:summary="@string/repositories_summary">
|
|
||||||
<intent
|
|
||||||
android:action="android.intent.action.MAIN"
|
|
||||||
android:targetPackage="@string/applicationId"
|
|
||||||
android:targetClass="org.fdroid.fdroid.views.ManageReposActivity"/>
|
|
||||||
</PreferenceScreen>
|
|
||||||
<PreferenceScreen
|
|
||||||
android:key="installHistory"
|
|
||||||
android:visible="false"
|
|
||||||
android:title="@string/install_history"
|
|
||||||
android:summary="@string/install_history_summary">
|
|
||||||
<intent
|
|
||||||
android:action="android.intent.action.MAIN"
|
|
||||||
android:targetPackage="@string/applicationId"
|
|
||||||
android:targetClass="org.fdroid.fdroid.views.InstallHistoryActivity"/>
|
|
||||||
</PreferenceScreen>
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/updates">
|
|
||||||
<org.fdroid.fdroid.views.LiveSeekBarPreference
|
|
||||||
android:key="overWifi"
|
|
||||||
android:title="@string/over_wifi"
|
|
||||||
android:defaultValue="@integer/defaultOverWifi"
|
|
||||||
android:layout="@layout/preference_seekbar"/>
|
|
||||||
<org.fdroid.fdroid.views.LiveSeekBarPreference
|
|
||||||
android:key="overData"
|
|
||||||
android:title="@string/over_data"
|
|
||||||
android:defaultValue="@integer/defaultOverData"
|
|
||||||
android:layout="@layout/preference_seekbar"/>
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:title="@string/update_auto_download"
|
|
||||||
android:summary="@string/update_auto_download_summary"
|
|
||||||
android:key="updateAutoDownload"/>
|
|
||||||
<org.fdroid.fdroid.views.LiveSeekBarPreference
|
|
||||||
android:key="updateIntervalSeekBarPosition"
|
|
||||||
android:title="@string/update_interval"
|
|
||||||
android:defaultValue="@integer/defaultUpdateInterval"
|
|
||||||
android:layout="@layout/preference_seekbar"/>
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:title="@string/notify"
|
|
||||||
android:defaultValue="true"
|
|
||||||
android:key="updateNotify"/>
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/display"
|
|
||||||
android:key="pref_category_display">
|
|
||||||
<ListPreference
|
|
||||||
android:title="@string/pref_language"
|
|
||||||
android:key="language"/>
|
|
||||||
<ListPreference
|
|
||||||
android:title="@string/theme"
|
|
||||||
android:key="theme"
|
|
||||||
android:defaultValue="light"
|
|
||||||
android:entries="@array/themeNames"
|
|
||||||
android:entryValues="@array/themeValues"/>
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/appcompatibility"
|
|
||||||
android:key="pref_category_appcompatibility">
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:title="@string/show_incompat_versions"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="incompatibleVersions"/>
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:title="@string/show_anti_feature_apps"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="showAntiFeatureApps"/>
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:title="@string/force_touch_apps"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="ignoreTouchscreen"/>
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/proxy">
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:key="useTor"
|
|
||||||
android:summary="@string/useTorSummary"
|
|
||||||
android:title="@string/useTor"/>
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="enableProxy"
|
|
||||||
android:title="@string/enable_proxy_title"
|
|
||||||
android:summary="@string/enable_proxy_summary"/>
|
|
||||||
<EditTextPreference
|
|
||||||
android:key="proxyHost"
|
|
||||||
android:title="@string/proxy_host"
|
|
||||||
android:summary="@string/proxy_host_summary"
|
|
||||||
android:dependency="enableProxy"/>
|
|
||||||
<EditTextPreference
|
|
||||||
android:key="proxyPort"
|
|
||||||
android:title="@string/proxy_port"
|
|
||||||
android:summary="@string/proxy_port_summary"
|
|
||||||
android:dependency="enableProxy"/>
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
android:key="pref_category_privacy"
|
|
||||||
android:title="@string/privacy">
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:key="promptToSendCrashReports"
|
|
||||||
android:title="@string/prompt_to_send_crash_reports"
|
|
||||||
android:summary="@string/prompt_to_send_crash_reports_summary"
|
|
||||||
android:defaultValue="true"/>
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="preventScreenshots"
|
|
||||||
android:summary="@string/preventScreenshots_summary"
|
|
||||||
android:title="@string/preventScreenshots_title"/>
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
android:title="@string/other"
|
|
||||||
android:key="pref_category_other">
|
|
||||||
<ListPreference
|
|
||||||
android:title="@string/cache_downloaded"
|
|
||||||
android:key="keepCacheFor"
|
|
||||||
android:defaultValue="86400000"
|
|
||||||
android:entries="@array/keepCacheNames"
|
|
||||||
android:entryValues="@array/keepCacheValues"/>
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:title="@string/expert"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="expert"/>
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="unstableUpdates"
|
|
||||||
android:title="@string/unstable_updates"
|
|
||||||
android:summary="@string/unstable_updates_summary"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:dependency="expert"/>
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="keepInstallHistory"
|
|
||||||
android:title="@string/keep_install_history"
|
|
||||||
android:summary="@string/keep_install_history_summary"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:dependency="expert"/>
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="sendToFdroidMetrics"
|
|
||||||
android:title="@string/send_to_fdroid_metrics"
|
|
||||||
android:summary="@string/send_to_fdroid_metrics_summary"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:dependency="expert"/>
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="hideAllNotifications"
|
|
||||||
android:title="@string/hide_all_notifications"
|
|
||||||
android:summary="@string/hide_all_notifications_summary"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:dependency="expert"/>
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="sendVersionAndUUIDToServers"
|
|
||||||
android:title="@string/send_version_and_uuid"
|
|
||||||
android:summary="@string/send_version_and_uuid_summary"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:dependency="expert"/>
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="forceOldIndex"
|
|
||||||
android:title="@string/force_old_index"
|
|
||||||
android:summary="@string/force_old_index_summary"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:dependency="expert"/>
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:title="@string/system_installer"
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="privilegedInstaller"
|
|
||||||
android:persistent="false"
|
|
||||||
android:dependency="expert"/>
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
|
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!-- This file should be outside of release manifest (in this case app/src/mock/Manifest.xml -->
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<!--required to enable/disable system animations from the app itself during Espresso test runs-->
|
|
||||||
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
|
|
||||||
</manifest>
|
|
@ -1,179 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
* Copyright (C) 2010-2012 Ciaran Gultnieks
|
|
||||||
* Copyright (C) 2013-2017 Peter Serwylo
|
|
||||||
* Copyright (C) 2014-2015 Daniel Martí
|
|
||||||
* Copyright (C) 2014-2018 Hans-Christoph Steiner
|
|
||||||
* Copyright (C) 2016 Dominik Schürmann
|
|
||||||
* Copyright (C) 2018 Torsten Grote
|
|
||||||
* Copyright (C) 2018 Senecto Limited
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
-->
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="org.fdroid.fdroid"
|
|
||||||
android:installLocation="auto">
|
|
||||||
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.nfc"
|
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.bluetooth"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.usb.host"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
|
||||||
<uses-permission
|
|
||||||
android:name="android.permission.USB_PERMISSION"
|
|
||||||
android:maxSdkVersion="22" /><!-- maybe unnecessary -->
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
|
|
||||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
||||||
|
|
||||||
<application>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".nearby.SwapWorkflowActivity"
|
|
||||||
android:configChanges="orientation|keyboardHidden"
|
|
||||||
android:label="@string/swap"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:parentActivityName=".views.main.MainActivity"
|
|
||||||
android:screenOrientation="portrait">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value=".views.main.MainActivity" />
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".panic.PanicPreferencesActivity"
|
|
||||||
android:label="@string/panic_settings"
|
|
||||||
android:parentActivityName=".views.main.MainActivity">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="info.guardianproject.panic.action.CONNECT" />
|
|
||||||
<action android:name="info.guardianproject.panic.action.DISCONNECT" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value=".views.main.MainActivity" />
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".panic.SelectInstalledAppsActivity"
|
|
||||||
android:parentActivityName=".panic.PanicPreferencesActivity" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".panic.PanicResponderActivity"
|
|
||||||
android:noHistory="true"
|
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
|
||||||
|
|
||||||
<!-- this can never have launchMode singleTask or singleInstance! -->
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".panic.ExitActivity"
|
|
||||||
android:theme="@android:style/Theme.NoDisplay" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".panic.CalculatorActivity"
|
|
||||||
android:enabled="false"
|
|
||||||
android:icon="@mipmap/ic_calculator_launcher"
|
|
||||||
android:label="@string/hiding_calculator">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<receiver android:name=".nearby.WifiStateChangeReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.net.wifi.STATE_CHANGE" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
<receiver android:name=".receiver.DeviceStorageReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".nearby.UsbDeviceAttachedReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data
|
|
||||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
|
||||||
android:resource="@xml/device_filter" />
|
|
||||||
</receiver>
|
|
||||||
<receiver android:name=".nearby.UsbDeviceDetachedReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data
|
|
||||||
android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"
|
|
||||||
android:resource="@xml/device_filter" />
|
|
||||||
</receiver>
|
|
||||||
<receiver android:name=".nearby.UsbDeviceMediaMountedReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MEDIA_EJECT" />
|
|
||||||
<action android:name="android.intent.action.MEDIA_REMOVED" />
|
|
||||||
<action android:name="android.intent.action.MEDIA_MOUNTED" />
|
|
||||||
<action android:name="android.intent.action.MEDIA_BAD_REMOVAL" />
|
|
||||||
|
|
||||||
<data android:scheme="content" />
|
|
||||||
<data android:scheme="file" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".nearby.WifiStateChangeService"
|
|
||||||
android:exported="false" />
|
|
||||||
<service android:name=".nearby.SwapService" />
|
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".nearby.LocalRepoService"
|
|
||||||
android:exported="false" />
|
|
||||||
<service
|
|
||||||
android:name=".nearby.TreeUriScannerIntentService"
|
|
||||||
android:exported="false" />
|
|
||||||
<service
|
|
||||||
android:name=".nearby.SDCardScannerService"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
@ -1,94 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 Ken Ellinwood.
|
|
||||||
*
|
|
||||||
* 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 kellinwood.logging;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public abstract class AbstractLogger implements LoggerInterface {
|
|
||||||
|
|
||||||
protected String category;
|
|
||||||
|
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.ENGLISH);
|
|
||||||
|
|
||||||
public AbstractLogger(String category) {
|
|
||||||
this.category = category;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String format(String level, String message) {
|
|
||||||
return String.format("%s %s %s: %s\n", dateFormat.format(new Date()), level, category, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void write(String level, String message, Throwable t);
|
|
||||||
|
|
||||||
protected void writeFixNullMessage(String level, String message, Throwable t) {
|
|
||||||
if (message == null) {
|
|
||||||
if (t != null) message = t.getClass().getName();
|
|
||||||
else message = "null";
|
|
||||||
}
|
|
||||||
write(level, message, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void debug(String message, Throwable t) {
|
|
||||||
writeFixNullMessage(DEBUG, message, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void debug(String message) {
|
|
||||||
writeFixNullMessage(DEBUG, message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void error(String message, Throwable t) {
|
|
||||||
writeFixNullMessage(ERROR, message, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void error(String message) {
|
|
||||||
writeFixNullMessage(ERROR, message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void info(String message, Throwable t) {
|
|
||||||
writeFixNullMessage(INFO, message, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void info(String message) {
|
|
||||||
writeFixNullMessage(INFO, message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void warning(String message, Throwable t) {
|
|
||||||
writeFixNullMessage(WARNING, message, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void warning(String message) {
|
|
||||||
writeFixNullMessage(WARNING, message, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDebugEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isErrorEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInfoEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isWarningEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 Ken Ellinwood.
|
|
||||||
*
|
|
||||||
* 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 kellinwood.logging;
|
|
||||||
|
|
||||||
public interface LoggerInterface {
|
|
||||||
|
|
||||||
public static final String ERROR = "ERROR";
|
|
||||||
public static final String WARNING = "WARNING";
|
|
||||||
public static final String INFO = "INFO";
|
|
||||||
public static final String DEBUG = "DEBUG";
|
|
||||||
|
|
||||||
public boolean isErrorEnabled();
|
|
||||||
|
|
||||||
public void error(String message);
|
|
||||||
|
|
||||||
public void error(String message, Throwable t);
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isWarningEnabled();
|
|
||||||
|
|
||||||
public void warning(String message);
|
|
||||||
|
|
||||||
public void warning(String message, Throwable t);
|
|
||||||
|
|
||||||
public boolean isInfoEnabled();
|
|
||||||
|
|
||||||
public void info(String message);
|
|
||||||
|
|
||||||
public void info(String message, Throwable t);
|
|
||||||
|
|
||||||
public boolean isDebugEnabled();
|
|
||||||
|
|
||||||
public void debug(String message);
|
|
||||||
|
|
||||||
public void debug(String message, Throwable t);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 Ken Ellinwood.
|
|
||||||
*
|
|
||||||
* 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 kellinwood.logging;
|
|
||||||
|
|
||||||
public class NullLoggerFactory implements LoggerFactory {
|
|
||||||
|
|
||||||
static LoggerInterface logger = new LoggerInterface() {
|
|
||||||
|
|
||||||
public void debug(String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void debug(String message, Throwable t) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void error(String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void error(String message, Throwable t) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void info(String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void info(String message, Throwable t) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDebugEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isErrorEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInfoEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isWarningEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void warning(String message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void warning(String message, Throwable t) {
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public LoggerInterface getLogger(String category) {
|
|
||||||
return logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
|
||||||
|
|
||||||
import kellinwood.security.zipsigner.ZipSigner;
|
|
||||||
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public class CustomKeySigner {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* KeyStore-type agnostic. This method will sign the zip file, automatically handling JKS or BKS keystores.
|
|
||||||
*/
|
|
||||||
public static void signZip(ZipSigner zipSigner,
|
|
||||||
String keystorePath,
|
|
||||||
char[] keystorePw,
|
|
||||||
String certAlias,
|
|
||||||
char[] certPw,
|
|
||||||
String signatureAlgorithm,
|
|
||||||
String inputZipFilename,
|
|
||||||
String outputZipFilename)
|
|
||||||
throws Exception {
|
|
||||||
zipSigner.issueLoadingCertAndKeysProgressEvent();
|
|
||||||
KeyStore keystore = KeyStoreFileManager.loadKeyStore(keystorePath, keystorePw);
|
|
||||||
Certificate cert = keystore.getCertificate(certAlias);
|
|
||||||
X509Certificate publicKey = (X509Certificate) cert;
|
|
||||||
Key key = keystore.getKey(certAlias, certPw);
|
|
||||||
PrivateKey privateKey = (PrivateKey) key;
|
|
||||||
|
|
||||||
zipSigner.setKeys("custom", publicKey, privateKey, signatureAlgorithm, null);
|
|
||||||
zipSigner.signZip(inputZipFilename, outputZipFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
|
|
||||||
package kellinwood.security.zipsigner.optional;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
|
||||||
import org.bouncycastle.jce.X509Principal;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for dealing with the distinguished name RDNs.
|
|
||||||
*/
|
|
||||||
public class DistinguishedNameValues extends LinkedHashMap<ASN1ObjectIdentifier, String> {
|
|
||||||
|
|
||||||
public DistinguishedNameValues() {
|
|
||||||
put(BCStyle.C, null);
|
|
||||||
put(BCStyle.ST, null);
|
|
||||||
put(BCStyle.L, null);
|
|
||||||
put(BCStyle.STREET, null);
|
|
||||||
put(BCStyle.O, null);
|
|
||||||
put(BCStyle.OU, null);
|
|
||||||
put(BCStyle.CN, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String put(ASN1ObjectIdentifier oid, String value) {
|
|
||||||
if (value != null && value.equals("")) value = null;
|
|
||||||
if (containsKey(oid)) super.put(oid, value); // preserve original ordering
|
|
||||||
else {
|
|
||||||
super.put(oid, value);
|
|
||||||
// String cn = remove(BCStyle.CN); // CN will always be last.
|
|
||||||
// put(BCStyle.CN,cn);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCountry(String country) {
|
|
||||||
put(BCStyle.C, country);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setState(String state) {
|
|
||||||
put(BCStyle.ST, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocality(String locality) {
|
|
||||||
put(BCStyle.L, locality);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStreet(String street) {
|
|
||||||
put(BCStyle.STREET, street);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrganization(String organization) {
|
|
||||||
put(BCStyle.O, organization);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrganizationalUnit(String organizationalUnit) {
|
|
||||||
put(BCStyle.OU, organizationalUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCommonName(String commonName) {
|
|
||||||
put(BCStyle.CN, commonName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
int result = 0;
|
|
||||||
for (String value : values()) {
|
|
||||||
if (value != null) result += 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Principal getPrincipal() {
|
|
||||||
Vector<ASN1ObjectIdentifier> oids = new Vector<ASN1ObjectIdentifier>();
|
|
||||||
Vector<String> values = new Vector<String>();
|
|
||||||
|
|
||||||
for (Map.Entry<ASN1ObjectIdentifier, String> entry : entrySet()) {
|
|
||||||
if (entry.getValue() != null && !entry.getValue().equals("")) {
|
|
||||||
oids.add(entry.getKey());
|
|
||||||
values.add(entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new X509Principal(oids, values);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothSocket;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class BluetoothClient {
|
|
||||||
private static final String TAG = "BluetoothClient";
|
|
||||||
|
|
||||||
private final BluetoothDevice device;
|
|
||||||
|
|
||||||
public BluetoothClient(String macAddress) {
|
|
||||||
device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BluetoothConnection openConnection() throws IOException {
|
|
||||||
|
|
||||||
BluetoothConnection connection = null;
|
|
||||||
try {
|
|
||||||
BluetoothSocket socket = device.createInsecureRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
|
|
||||||
connection = new BluetoothConnection(socket);
|
|
||||||
connection.open();
|
|
||||||
return connection;
|
|
||||||
} finally {
|
|
||||||
if (connection != null) {
|
|
||||||
connection.closeQuietly();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|