Compare commits
No commits in common. "master" and "1.7-alpha0" have entirely different histories.
master
...
1.7-alpha0
1
.gitattributes
vendored
@ -1 +0,0 @@
|
|||||||
*.gpg binary
|
|
1
.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
|
||||||
|
152
.gitlab-ci.yml
@ -1,36 +1,26 @@
|
|||||||
stages:
|
image: registry.gitlab.com/fdroid/ci-images-client:latest
|
||||||
- test
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
.base:
|
cache:
|
||||||
image: registry.gitlab.com/fdroid/ci-images-client:latest
|
|
||||||
before_script:
|
|
||||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
|
||||||
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
|
|
||||||
- alias sdkmanager="sdkmanager --no_https"
|
|
||||||
- echo y | sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null
|
|
||||||
# limit RAM usage for all gradle runs
|
|
||||||
- export maxmem=$(expr $(sed -n 's,^MemAvailable:[^0-9]*\([0-9][0-9]*\)[^0-9]*$,\1,p' /proc/meminfo) / 1024 / 2 / 1024 \* 1024)
|
|
||||||
- printf "\norg.gradle.jvmargs=-Xmx${maxmem}m -XX:MaxPermSize=${maxmem}m\norg.gradle.daemon=false\norg.gradle.parallel=false\n" >> gradle.properties
|
|
||||||
after_script:
|
|
||||||
# this file changes every time but should not be cached
|
|
||||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
|
||||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
|
||||||
cache:
|
|
||||||
paths:
|
paths:
|
||||||
- .gradle/wrapper
|
- .gradle/wrapper
|
||||||
- .gradle/caches
|
- .gradle/caches
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
|
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
|
||||||
|
- alias sdkmanager="sdkmanager --no_https"
|
||||||
|
- echo y | sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null
|
||||||
|
|
||||||
.test-template: &test-template
|
.test-template: &test-template
|
||||||
extends: .base
|
|
||||||
stage: test
|
stage: test
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
|
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
|
||||||
paths:
|
paths:
|
||||||
- kernel.log
|
|
||||||
- logcat.txt
|
- logcat.txt
|
||||||
- app/core*
|
|
||||||
- app/*.log
|
|
||||||
- app/build/reports
|
- app/build/reports
|
||||||
- app/build/outputs/*ml
|
- app/build/outputs/*ml
|
||||||
- app/build/outputs/apk
|
- app/build/outputs/apk
|
||||||
@ -45,40 +35,29 @@ test_lint_pmd_checkstyle:
|
|||||||
<<: *test-template
|
<<: *test-template
|
||||||
script:
|
script:
|
||||||
- export EXITVALUE=0
|
- export EXITVALUE=0
|
||||||
- function set_error() { export EXITVALUE=1; printf "\x1b[31mERROR `history|tail -2|head -1|cut -b 6-500`\x1b[0m\n"; }
|
|
||||||
- ./gradlew assemble
|
- ./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 testFullDebugUnitTest
|
||||||
- ./gradlew lint || set_error
|
- ./gradlew lint
|
||||||
- ./gradlew pmd || set_error
|
- ./gradlew pmd || export EXITVALUE=1
|
||||||
- ./gradlew checkstyle || set_error
|
- ./gradlew checkstyle || export EXITVALUE=1
|
||||||
- ./tools/check-format-strings.py || set_error
|
- ./tools/check-format-strings.py || export EXITVALUE=1
|
||||||
- ./tools/check-fastlane-whitespace.py || set_error
|
- ./tools/check-fastlane-whitespace.py || export EXITVALUE=1
|
||||||
- ./tools/remove-unused-and-blank-translations.py || set_error
|
- ./tools/remove-unused-and-blank-translations.py || export EXITVALUE=1
|
||||||
- echo "These are unused or blank translations that should be removed:"
|
- 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
|
- git --no-pager diff --ignore-all-space --name-only --exit-code app/src/*/res/values*/strings.xml || export EXITVALUE=1
|
||||||
- exit $EXITVALUE
|
- exit $EXITVALUE
|
||||||
|
|
||||||
errorprone:
|
errorprone:
|
||||||
extends: .base
|
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- apt-get update
|
|
||||||
- apt-get install -t stretch-backports openjdk-11-jdk-headless
|
|
||||||
- update-java-alternatives --set java-1.11.0-openjdk-amd64
|
|
||||||
- export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64
|
|
||||||
- cat config/errorprone.gradle >> app/build.gradle
|
- cat config/errorprone.gradle >> app/build.gradle
|
||||||
- ./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug
|
- ./gradlew assembleDebug
|
||||||
|
|
||||||
# Run the tests in the emulator. Each step is broken out to run on
|
|
||||||
# its own since the CI runner can have limited RAM, and the emulator
|
|
||||||
# can take a while to start.
|
|
||||||
#
|
|
||||||
# once these prove stable, the task should be switched to
|
# once these prove stable, the task should be switched to
|
||||||
# connectedCheck to test all the build flavors
|
# connectedCheck to test all the build flavors
|
||||||
.connected-template: &connected-template
|
.connected-template: &connected-template
|
||||||
extends: .base
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew assembleFullDebug
|
- ./gradlew assembleFullDebug
|
||||||
- export AVD_SDK=`echo $CI_JOB_NAME | awk '{print $2}'`
|
- export AVD_SDK=`echo $CI_JOB_NAME | awk '{print $2}'`
|
||||||
@ -86,23 +65,63 @@ errorprone:
|
|||||||
- export AVD_ARCH=`echo $CI_JOB_NAME | awk '{print $4}'`
|
- export AVD_ARCH=`echo $CI_JOB_NAME | awk '{print $4}'`
|
||||||
- export AVD_PACKAGE="system-images;android-${AVD_SDK};${AVD_TAG};${AVD_ARCH}"
|
- export AVD_PACKAGE="system-images;android-${AVD_SDK};${AVD_TAG};${AVD_ARCH}"
|
||||||
- echo $AVD_PACKAGE
|
- echo $AVD_PACKAGE
|
||||||
|
- emulator -accel-check || true
|
||||||
|
|
||||||
|
# Use bleeding edge for Q, or download proven emulator version, based on
|
||||||
|
# https://aur.archlinux.org/android-emulator.git
|
||||||
|
- if [[ "$AVD_SDK" == Q ]]; then
|
||||||
|
set -x; echo y | sdkmanager --channel=3 "emulator" > /dev/null; set +x;
|
||||||
|
else
|
||||||
|
set -x;
|
||||||
|
rm -rf $ANDROID_HOME/emulator;
|
||||||
|
wget -q http://dl.google.com/android/repository/emulator-linux-5264690.zip;
|
||||||
|
echo "48c1cda2bdf3095d9d9d5c010fbfb3d6d673e3ea emulator-linux-5264690.zip" | sha1sum -c;
|
||||||
|
unzip -qq -d $ANDROID_HOME emulator-linux-5264690.zip;
|
||||||
|
set +x;
|
||||||
|
fi
|
||||||
|
- grep -v '^License' $ANDROID_HOME/tools/source.properties
|
||||||
|
$ANDROID_HOME/emulator/source.properties
|
||||||
|
|
||||||
- alias sdkmanager
|
- alias sdkmanager
|
||||||
- ls -l ~/.android
|
- echo y | sdkmanager "platforms;android-$AVD_SDK" > /dev/null
|
||||||
|
- if ! avdmanager list avd | grep "Name. avd$AVD_SDK$"; then
|
||||||
|
set -x;
|
||||||
|
rm -rf ~/.android/avd $ANDROID_HOME/system-images;
|
||||||
|
echo y | sdkmanager "$AVD_PACKAGE" > /dev/null;
|
||||||
|
echo no | avdmanager create avd --name avd$AVD_SDK --tag "$AVD_TAG" --package "$AVD_PACKAGE" --sdcard 64M --device "Nexus 5";
|
||||||
|
export RAMSIZE="`sed -n 's,^MemAvailable:[^0-9]*\([0-9][0-9]*\)[^0-9]*$,\1,p' /proc/meminfo`";
|
||||||
|
if [ $RAMSIZE -le 2048 ]; then
|
||||||
|
sed -i '/^hw\.ramSize\s*=.*/d' ~/.android/avd/*.avd/config.ini;
|
||||||
|
echo "hw.ramSize=1024" >> ~/.android/avd/*.avd/config.ini;
|
||||||
|
fi;
|
||||||
|
avdmanager list avd;
|
||||||
|
set +x;
|
||||||
|
fi
|
||||||
|
- grep -v '^License' $ANDROID_HOME/system-images/android-$AVD_SDK/$AVD_TAG/$AVD_ARCH/source.properties
|
||||||
- adb start-server
|
- adb start-server
|
||||||
- start-emulator
|
- ls -l ~/.android
|
||||||
|
- emulator -version
|
||||||
|
- emulator -avd avd$AVD_SDK
|
||||||
|
-no-audio
|
||||||
|
-no-jni
|
||||||
|
-no-snapstorage
|
||||||
|
-no-window
|
||||||
|
-skip-adb-auth
|
||||||
|
-verbose
|
||||||
|
-wipe-data
|
||||||
|
-debug all,-hw_control,-qemud,-sensors,-surface
|
||||||
|
&
|
||||||
- wait-for-emulator
|
- wait-for-emulator
|
||||||
- adb devices
|
- adb devices
|
||||||
- adb shell input keyevent 82 &
|
- adb shell input keyevent 82 &
|
||||||
- ./gradlew installFullDebug
|
- test $AVD_SDK -ge 25 || export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=android.support.test.filters.LargeTest
|
||||||
- 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
|
- ./gradlew connectedFullDebugAndroidTest $FLAG
|
||||||
|
|| ./gradlew connectedFullDebugAndroidTest $FLAG
|
||||||
|
|| ./gradlew connectedFullDebugAndroidTest $FLAG
|
||||||
|
|| (adb -e logcat -d > logcat.txt; exit 1)
|
||||||
|
|
||||||
no-accel 22 default x86:
|
connected 22 default armeabi-v7a:
|
||||||
|
retry: 1
|
||||||
<<: *test-template
|
<<: *test-template
|
||||||
<<: *connected-template
|
<<: *connected-template
|
||||||
|
|
||||||
@ -110,17 +129,35 @@ no-accel 22 default x86:
|
|||||||
tags:
|
tags:
|
||||||
- fdroid
|
- fdroid
|
||||||
- kvm
|
- kvm
|
||||||
|
allow_failure: true
|
||||||
only:
|
only:
|
||||||
variables:
|
- branches@eighthave/fdroidclient
|
||||||
- $RUN_KVM_JOBS
|
|
||||||
<<: *test-template
|
<<: *test-template
|
||||||
<<: *connected-template
|
<<: *connected-template
|
||||||
|
|
||||||
kvm 29 microg x86_64:
|
connected 23 default x86:
|
||||||
<<: *kvm-template
|
<<: *kvm-template
|
||||||
|
|
||||||
|
connected 25 default x86:
|
||||||
|
<<: *kvm-template
|
||||||
|
|
||||||
|
connected 26 google_apis x86:
|
||||||
|
<<: *kvm-template
|
||||||
|
only:
|
||||||
|
- branches@fdroid/fdroidclient
|
||||||
|
- branches@eighthave/fdroidclient
|
||||||
|
|
||||||
|
connected 27 google_apis_playstore x86:
|
||||||
|
<<: *kvm-template
|
||||||
|
|
||||||
|
connected 28 default x86_64:
|
||||||
|
<<: *kvm-template
|
||||||
|
|
||||||
|
connected Q default x86_64:
|
||||||
|
<<: *kvm-template
|
||||||
|
|
||||||
|
|
||||||
deploy_nightly:
|
deploy_nightly:
|
||||||
extends: .base
|
|
||||||
stage: deploy
|
stage: deploy
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
@ -140,3 +177,8 @@ deploy_nightly:
|
|||||||
# build the APKs!
|
# build the APKs!
|
||||||
- ./gradlew assembleDebug
|
- ./gradlew assembleDebug
|
||||||
- fdroid nightly -v
|
- fdroid nightly -v
|
||||||
|
|
||||||
|
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/
|
||||||
|
170
CHANGELOG.md
@ -1,173 +1,3 @@
|
|||||||
### 1.13-alpha1 (2021-06-02)
|
|
||||||
|
|
||||||
* Stop repeated updates of Trichrome Library
|
|
||||||
|
|
||||||
* More changes to follow Material Design (@proletarius101)
|
|
||||||
|
|
||||||
* Improve OpenCollective badge (@ConnyDuck)
|
|
||||||
|
|
||||||
### 1.13-alpha0 (2021-04-22)
|
|
||||||
|
|
||||||
* Theme support tied to built-in Android themes (@proletarius101)
|
|
||||||
|
|
||||||
* New top banner notifications: "No Internet" and "No Data or WiFi enabled"
|
|
||||||
|
|
||||||
* Improved handling of USB-OTG and SD Card repos and mirrors
|
|
||||||
|
|
||||||
### 1.12.1 (2021-04-12)
|
|
||||||
|
|
||||||
* Fix trove4j verification error
|
|
||||||
|
|
||||||
### 1.12 (2021-04-06)
|
|
||||||
|
|
||||||
* Sync translations
|
|
||||||
|
|
||||||
### 1.12-alpha3 (2021-03-10)
|
|
||||||
|
|
||||||
* Opt-in F-Droid Metrics
|
|
||||||
|
|
||||||
### 1.12-alpha2 (2021-03-03)
|
|
||||||
|
|
||||||
* Overhaul clean up of cached files
|
|
||||||
|
|
||||||
* Support updating "shared library packages" like Trichrome (@uldiniad)
|
|
||||||
|
|
||||||
### 1.12-alpha1 (2021-02-25)
|
|
||||||
|
|
||||||
* Add extra sanitation to search terms to prevent vulnerabilities.
|
|
||||||
|
|
||||||
* Fix Nearby Swap's close button (@proletarius101)
|
|
||||||
|
|
||||||
* Bump to compileSdkVersion 29 to support Java8
|
|
||||||
|
|
||||||
* Set up WorkManager on demand to avoid slowing down starts
|
|
||||||
|
|
||||||
* Prefer system keys when APKs are signed by them (@glennmen)
|
|
||||||
|
|
||||||
### 1.12-alpha0 (2021-02-08)
|
|
||||||
|
|
||||||
* App description localization now fully respects lists of languages in Android
|
|
||||||
Language Settings
|
|
||||||
|
|
||||||
* Latest Tab lists results based on the Language Settings
|
|
||||||
|
|
||||||
* Latest Tab now shows results ordered newest first (@TheLastProject @IzzySoft)
|
|
||||||
|
|
||||||
* Theme support modernized and tied to the built-in Android themes (@proletarius101)
|
|
||||||
|
|
||||||
* Search results greatly improved (@Tvax @gcbrown76)
|
|
||||||
|
|
||||||
* Let Android efficiently schedule background cache cleanup operations (@Isira-Seneviratne)
|
|
||||||
|
|
||||||
* Overhaul repo URL parsing for reliable repo adding (@projectgus)
|
|
||||||
|
|
||||||
### 1.11 (2020-12-29)
|
|
||||||
|
|
||||||
* Improved linkifying of URLs in app descriptions
|
|
||||||
|
|
||||||
* Improved handling of SDCards and USG-OTG in Nearby
|
|
||||||
|
|
||||||
* Modernized code and switched PNGs to vectors (thanks @isira-seneviratne!)
|
|
||||||
|
|
||||||
* Recognize longer repo URLs to support GitCDN/RawGit/etc mirrors
|
|
||||||
|
|
||||||
### 1.10 (2020-10-20)
|
|
||||||
|
|
||||||
* Improved language selection with multiple locales
|
|
||||||
(thanks @spacecowboy and @bubu!)
|
|
||||||
|
|
||||||
### 1.10-alpha1 (2020-09-29)
|
|
||||||
|
|
||||||
* use notification channels for fine-grained control (@Isira-Seneviratne)
|
|
||||||
|
|
||||||
### 1.10-alpha0 (2020-07-20)
|
|
||||||
|
|
||||||
* Latest Tab will show better results on non-English devices
|
|
||||||
|
|
||||||
* updates to core libraries (Jackson, androidx, gradle, etc)
|
|
||||||
|
|
||||||
* use Gradle's new dependency verification
|
|
||||||
|
|
||||||
* polish whitelabeling support
|
|
||||||
|
|
||||||
### 1.9 (2020-06-25)
|
|
||||||
|
|
||||||
* Removed "Android App Link" support since it cannot work with
|
|
||||||
F-Droid, and it was triggering DNS leaks.
|
|
||||||
|
|
||||||
* Archive Repos are now lower priority than the Repo (higher on the
|
|
||||||
Manage Repos screen), fixing issues where it looked for icons,
|
|
||||||
screenshots and other information in the Archive rather than the
|
|
||||||
Repo itself.
|
|
||||||
|
|
||||||
* Fixed hopefully all occurrences where F-Droid client couldn't show an icon.
|
|
||||||
The remaining cases of missing icons are now caused either by
|
|
||||||
icons not included in upstream repo or by temporary network failures.
|
|
||||||
(After updating this requires one additional repo update to take effect.)
|
|
||||||
|
|
||||||
* Fixed a problem where repository updates would never trigger
|
|
||||||
when either "Over Data" or "Over Wifi" were disabled.
|
|
||||||
|
|
||||||
* Support OpenCollective donation option and highlight
|
|
||||||
free software donation platforms
|
|
||||||
|
|
||||||
* Fix for when the app update button wasn't showing up or working
|
|
||||||
in some cases (thanks @di72nn)
|
|
||||||
|
|
||||||
* Stop cropping feature header image (thanks @ByteHamster!)
|
|
||||||
|
|
||||||
* Make navigation bar match dark mode (thanks @MatthieuB!)
|
|
||||||
|
|
||||||
* Cleaned out obsolete code (thanks @Isira-Seneviratne!)
|
|
||||||
|
|
||||||
### 1.8-alpha2 (2020-02-04)
|
|
||||||
|
|
||||||
* stop showing Unknown Sources with Privileged Extension on Android 10 #1833
|
|
||||||
|
|
||||||
* add standard ripple effect to links on app details activity
|
|
||||||
|
|
||||||
* fix displaying default icon for apps without icons
|
|
||||||
|
|
||||||
### 1.8-alpha1 (2020-01-10)
|
|
||||||
|
|
||||||
* handle Android 10 permission config to stop Unknown Sources prompts
|
|
||||||
|
|
||||||
* keyboard opens when search is cleared
|
|
||||||
|
|
||||||
* translation sync with Android strings
|
|
||||||
|
|
||||||
* force common repo domains to HTTPS (GitLab, GitHub, Amazon)
|
|
||||||
|
|
||||||
### 1.8-alpha0 (2019-11-20)
|
|
||||||
|
|
||||||
* fix seekbar preference on recent Android versions (thanks @dkanada)
|
|
||||||
|
|
||||||
* handle API 29 split-permissions: fine location now implies coarse location
|
|
||||||
|
|
||||||
* define backup rules to avoid saving the swap repo
|
|
||||||
|
|
||||||
### 1.7.1 (2019-07-31)
|
|
||||||
|
|
||||||
* fix crashes from ACRA report emails
|
|
||||||
|
|
||||||
### 1.7 (2019-07-06)
|
|
||||||
|
|
||||||
* fix crash in Panic Settings
|
|
||||||
|
|
||||||
* catch random crashes related to WifiApControl
|
|
||||||
|
|
||||||
### 1.7-alpha2 (2019-06-18)
|
|
||||||
|
|
||||||
* USB OTG flash drives can be used as nearby repos and mirrors
|
|
||||||
|
|
||||||
### 1.7-alpha1 (2019-06-14)
|
|
||||||
|
|
||||||
* overhauled nearby swap using the device's hotspot AP
|
|
||||||
|
|
||||||
* add new panic responses: app uninstalls and reset repos to default
|
|
||||||
|
|
||||||
* fix proxy support on first start
|
|
||||||
|
|
||||||
### 1.7-alpha0 (2019-05-20)
|
### 1.7-alpha0 (2019-05-20)
|
||||||
|
|
||||||
* major refactor of "Nearby" UI code, to prepare for rewriting guts
|
* major refactor of "Nearby" UI code, to prepare for rewriting guts
|
||||||
|
@ -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,76 @@ 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:
|
### Running tests in Android Studio
|
||||||
|
|
||||||
./gradlew testFullDebugUnitTest --tests *LocaleSelectionTest*
|
Later versions of Android Studio require tests to be run with a "Working directory"
|
||||||
|
of `$MODULE_DIR$`.
|
||||||
|
[To make this the default behaviour](https://code.google.com/p/android/issues/detail?id=158015#c11),
|
||||||
|
close any projects to get the Welcome dialog. Then choose _Configure > Project Defaults >
|
||||||
|
Run Configurations > Defaults > Android JUnit_, and change "Working directory"
|
||||||
|
to `$MODULE_DIR$`. If you already have a project setup in Android Studio, you
|
||||||
|
may also need to change the default in _Run > Edit Configurations... > Defaults >
|
||||||
|
Android JUnit_.
|
||||||
|
|
||||||
For a quick way to run a specific emulator test:
|
## Versioning
|
||||||
|
|
||||||
./gradlew connectedFullDebugAndroidTest \
|
Each stable version follows the `X.Y` pattern. Hotfix releases - i.e. when a
|
||||||
-Pandroid.testInstrumentationRunnerArguments.class=org.fdroid.fdroid.MainActivityExpressoTest
|
stable has an important bug that needs immediate fixing - will follow the
|
||||||
|
`X.Y.Z` pattern.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Making releases
|
The version codes use a number of digits per each of these keys: `XXXYYYZNN`.
|
||||||
|
So for example, 1.3.1 would be `1003150` and 0.95-alpha13 would be `95013`
|
||||||
|
(leading zeros are omitted).
|
||||||
|
|
||||||
See https://gitlab.com/fdroid/wiki/-/wikis/Internal/Release-Process#fdroidclient
|
Note that we use a trailing `50` for actual stable releases, so alphas are
|
||||||
|
limited to `-alpha49`.
|
||||||
|
|
||||||
|
This is an example of a release process for **0.95**:
|
||||||
|
|
||||||
|
* 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
|
|
@ -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
|
||||||
|
@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
|
|||||||
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 {
|
||||||
@ -12,8 +12,8 @@ def getVersionName = { ->
|
|||||||
return stdout.toString().trim()
|
return stdout.toString().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
def isCi = "true" == System.getenv("CI")
|
def isCi = "true".equals(System.getenv("CI"))
|
||||||
def preDexEnabled = "true" == System.getProperty("pre-dex", "true")
|
def preDexEnabled = "true".equals(System.getProperty("pre-dex", "true"))
|
||||||
|
|
||||||
def fullApplicationId = "org.fdroid.fdroid"
|
def fullApplicationId = "org.fdroid.fdroid"
|
||||||
def basicApplicationId = "org.fdroid.basic"
|
def basicApplicationId = "org.fdroid.basic"
|
||||||
@ -21,16 +21,14 @@ def basicApplicationId = "org.fdroid.basic"
|
|||||||
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
|
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 27
|
||||||
|
buildToolsVersion '27.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode 1013001
|
versionCode 1007000
|
||||||
versionName getVersionName()
|
versionName getVersionName()
|
||||||
|
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
minSdkVersion 24
|
|
||||||
//noinspection ExpiredTargetSdkVersion
|
|
||||||
targetSdkVersion 28
|
|
||||||
/*
|
/*
|
||||||
The Android Testing Support Library collects analytics to continuously improve the testing
|
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
|
experience. More specifically, it uploads a hash of the package name of the application
|
||||||
@ -38,7 +36,6 @@ android {
|
|||||||
passing the following argument to the test runner: disableAnalytics "true".
|
passing the following argument to the test runner: disableAnalytics "true".
|
||||||
*/
|
*/
|
||||||
testInstrumentationRunnerArguments disableAnalytics: 'true'
|
testInstrumentationRunnerArguments disableAnalytics: 'true'
|
||||||
vectorDrawables.useSupportLibrary = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -46,9 +43,9 @@ android {
|
|||||||
// 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", "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'
|
||||||
}
|
}
|
||||||
@ -76,8 +73,6 @@ android {
|
|||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
compileOptions.encoding = "UTF-8"
|
compileOptions.encoding = "UTF-8"
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
@ -102,10 +97,6 @@ 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,56 +133,50 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
implementation 'com.android.support:support-v4:27.1.1'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'com.android.support:gridlayout-v7:27.1.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'com.android.support:support-annotations:27.1.1'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'com.android.support:recyclerview-v7:27.1.1'
|
||||||
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
|
implementation 'com.android.support:cardview-v7:27.1.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'com.android.support:design:27.1.1'
|
||||||
implementation 'androidx.palette:palette:1.0.0'
|
implementation 'com.android.support:support-vector-drawable:27.1.1'
|
||||||
implementation 'androidx.work:work-runtime:2.4.0'
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
|
implementation 'com.android.support:palette-v7:27.1.1'
|
||||||
implementation 'com.google.android.material:material:1.3.0'
|
implementation 'com.android.support:preference-v14:27.1.1'
|
||||||
|
|
||||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||||
implementation 'com.google.zxing:core:3.3.3'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
implementation 'info.guardianproject.netcipher:netcipher:2.2.0-alpha'
|
implementation 'info.guardianproject.netcipher:netcipher:2.0.0-beta1'
|
||||||
implementation 'info.guardianproject.panic:panic:1.0'
|
implementation 'info.guardianproject.panic:panic:0.5'
|
||||||
implementation 'commons-io:commons-io:2.6'
|
implementation 'commons-io:commons-io:2.6'
|
||||||
implementation 'commons-net:commons-net:3.6'
|
implementation 'commons-net:commons-net:3.6'
|
||||||
implementation 'ch.acra:acra:4.9.1'
|
implementation 'ch.acra:acra:4.9.1'
|
||||||
|
implementation 'io.reactivex:rxjava:1.1.0'
|
||||||
implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1'
|
implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1'
|
||||||
|
implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.5'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
|
implementation 'com.fasterxml.jackson.core:jackson-core:2.8.11'
|
||||||
implementation 'io.reactivex.rxjava3:rxjava:3.0.9'
|
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.8.11'
|
||||||
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.11'
|
||||||
|
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
|
fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.60'
|
||||||
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 'cc.mvdan.accesspoint:library:0.2.0'
|
||||||
fullImplementation 'org.jmdns:jmdns:3.5.5'
|
fullImplementation 'org.jmdns:jmdns:3.5.5'
|
||||||
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
|
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
|
||||||
|
|
||||||
testImplementation 'androidx.test:core:1.3.0'
|
testImplementation 'org.robolectric:robolectric:3.8'
|
||||||
testImplementation 'junit:junit:4.13.1'
|
testImplementation "com.android.support.test:monitor:1.0.2"
|
||||||
testImplementation 'org.robolectric:robolectric:4.3'
|
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60'
|
||||||
testImplementation 'org.mockito:mockito-core:3.3.3'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
testImplementation 'org.mockito:mockito-core:2.7.22'
|
||||||
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
|
||||||
|
|
||||||
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
|
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
|
||||||
androidTestImplementation 'androidx.test:core:1.3.0'
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||||
androidTestImplementation 'androidx.test:monitor:1.3.0'
|
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
|
||||||
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 {
|
||||||
@ -207,7 +192,7 @@ task checkstyle(type: Checkstyle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pmd {
|
pmd {
|
||||||
toolVersion = '6.20.0'
|
toolVersion = '5.5.1'
|
||||||
consoleOutput = true
|
consoleOutput = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +202,7 @@ task pmdMain(type: Pmd) {
|
|||||||
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'
|
||||||
|
exclude '**/kellinwood/**/*.java'
|
||||||
}
|
}
|
||||||
|
|
||||||
task pmdTest(type: Pmd) {
|
task pmdTest(type: Pmd) {
|
||||||
|
25
app/proguard-rules.pro
vendored
@ -4,11 +4,13 @@
|
|||||||
-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.**
|
-dontwarn org.slf4j.**
|
||||||
-dontnote org.apache.http.**
|
-dontnote org.apache.http.**
|
||||||
-dontnote android.net.http.**
|
-dontnote android.net.http.**
|
||||||
|
-dontnote android.support.**
|
||||||
-dontnote **ILicensingService
|
-dontnote **ILicensingService
|
||||||
|
|
||||||
# Needed for espresso https://stackoverflow.com/a/21706087
|
# Needed for espresso https://stackoverflow.com/a/21706087
|
||||||
@ -31,6 +33,24 @@
|
|||||||
public *;
|
public *;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Samsung Android 4.2 bug
|
||||||
|
# https://code.google.com/p/android/issues/detail?id=78377
|
||||||
|
-keepnames class !android.support.v7.internal.view.menu.**, ** {*;}
|
||||||
|
|
||||||
|
-keep public class android.support.v7.widget.** {*;}
|
||||||
|
-keep public class android.support.v7.internal.widget.** {*;}
|
||||||
|
|
||||||
|
-keep public class * extends android.support.v4.view.ActionProvider {
|
||||||
|
public <init>(android.content.Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
# The rxjava library depends on sun.misc.Unsafe, which is unavailable on Android
|
||||||
|
# The rxjava team is aware of this, and mention in the docs that they only use
|
||||||
|
# the unsafe functionality if the platform supports it.
|
||||||
|
# - https://github.com/ReactiveX/RxJava/issues/1415#issuecomment-48390883
|
||||||
|
# - https://github.com/ReactiveX/RxJava/blob/1.x/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java#L23
|
||||||
|
-dontwarn rx.internal.util.**
|
||||||
|
|
||||||
-keepattributes *Annotation*,EnclosingMethod,Signature
|
-keepattributes *Annotation*,EnclosingMethod,Signature
|
||||||
-keepnames class com.fasterxml.jackson.** { *; }
|
-keepnames class com.fasterxml.jackson.** { *; }
|
||||||
-dontwarn com.fasterxml.jackson.databind.ext.**
|
-dontwarn com.fasterxml.jackson.databind.ext.**
|
||||||
@ -40,8 +60,3 @@ public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
|
|||||||
-keep public class your.class.** {
|
-keep public class your.class.** {
|
||||||
*;
|
*;
|
||||||
}
|
}
|
||||||
|
|
||||||
# This is necessary so that RemoteWorkManager can be initialized (also marked with @Keep)
|
|
||||||
-keep class androidx.work.multiprocess.RemoteWorkManagerClient {
|
|
||||||
public <init>(...);
|
|
||||||
}
|
|
@ -1,24 +1,17 @@
|
|||||||
<?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"
|
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.support.test.uiautomator.v18"/>
|
||||||
<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" />
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@ -16,9 +16,6 @@ public class AssetUtils {
|
|||||||
|
|
||||||
private static final String TAG = "Utils";
|
private static final String TAG = "Utils";
|
||||||
|
|
||||||
/**
|
|
||||||
* This requires {@link Context} from {@link android.app.Instrumentation#getContext()}
|
|
||||||
*/
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static File copyAssetToDir(Context context, String assetName, File directory) {
|
public static File copyAssetToDir(Context context, String assetName, File directory) {
|
||||||
File tempFile = null;
|
File tempFile = null;
|
||||||
@ -31,7 +28,6 @@ public class AssetUtils {
|
|||||||
output = new FileOutputStream(tempFile);
|
output = new FileOutputStream(tempFile);
|
||||||
Utils.copy(input, output);
|
Utils.copy(input, output);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Check the context is from Instrumentation.getContext()");
|
|
||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
Utils.closeQuietly(output);
|
Utils.closeQuietly(output);
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.fdroid.fdroid.compat.FileCompatTest;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class CleanCacheServiceTest {
|
||||||
|
|
||||||
|
public static final String TAG = "CleanCacheServiceTest";
|
||||||
|
|
||||||
|
@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());
|
||||||
|
|
||||||
|
CleanCacheService.clearOldFiles(dir, 3000); // check all in dir
|
||||||
|
assertFalse(first.exists());
|
||||||
|
assertTrue(second.exists());
|
||||||
|
|
||||||
|
Thread.sleep(7000);
|
||||||
|
CleanCacheService.clearOldFiles(second, 3000); // check just second file
|
||||||
|
assertFalse(first.exists());
|
||||||
|
assertFalse(second.exists());
|
||||||
|
|
||||||
|
// make sure it doesn't freak out on a non-existant file
|
||||||
|
File nonexistant = new File(tempDir, "nonexistant");
|
||||||
|
CleanCacheService.clearOldFiles(nonexistant, 1);
|
||||||
|
CleanCacheService.clearOldFiles(null, 1);
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,8 @@ import android.content.Context;
|
|||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -179,9 +179,6 @@ public class LocalizationTest {
|
|||||||
case "dd":
|
case "dd":
|
||||||
resources.getString(resId, 1, 2);
|
resources.getString(resId, 1, 2);
|
||||||
break;
|
break;
|
||||||
case "ds":
|
|
||||||
resources.getString(resId, 1, "TWO");
|
|
||||||
break;
|
|
||||||
case "dds":
|
case "dds":
|
||||||
resources.getString(resId, 1, 2, "THREE");
|
resources.getString(resId, 1, 2, "THREE");
|
||||||
break;
|
break;
|
||||||
|
@ -5,23 +5,20 @@ import android.app.ActivityManager;
|
|||||||
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.support.test.InstrumentationRegistry;
|
||||||
import androidx.core.content.ContextCompat;
|
import android.support.test.espresso.IdlingPolicies;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import android.support.test.espresso.ViewInteraction;
|
||||||
import androidx.test.filters.LargeTest;
|
import android.support.test.filters.LargeTest;
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import android.support.test.rule.ActivityTestRule;
|
||||||
import androidx.test.espresso.IdlingPolicies;
|
import android.support.test.rule.GrantPermissionRule;
|
||||||
import androidx.test.espresso.ViewInteraction;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import androidx.test.rule.ActivityTestRule;
|
import android.support.test.uiautomator.UiDevice;
|
||||||
import androidx.test.rule.GrantPermissionRule;
|
import android.support.test.uiautomator.UiObject;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||||
import androidx.test.uiautomator.UiDevice;
|
import android.support.test.uiautomator.UiSelector;
|
||||||
import androidx.test.uiautomator.UiObject;
|
|
||||||
import androidx.test.uiautomator.UiObjectNotFoundException;
|
|
||||||
import androidx.test.uiautomator.UiSelector;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import org.fdroid.fdroid.views.StatusBanner;
|
import org.fdroid.fdroid.views.BannerUpdatingRepos;
|
||||||
import org.fdroid.fdroid.views.main.MainActivity;
|
import org.fdroid.fdroid.views.main.MainActivity;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
@ -35,19 +32,18 @@ import org.junit.runner.RunWith;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
import static androidx.test.espresso.action.ViewActions.swipeDown;
|
import static android.support.test.espresso.action.ViewActions.swipeDown;
|
||||||
import static androidx.test.espresso.action.ViewActions.swipeLeft;
|
import static android.support.test.espresso.action.ViewActions.swipeLeft;
|
||||||
import static androidx.test.espresso.action.ViewActions.swipeRight;
|
import static android.support.test.espresso.action.ViewActions.swipeRight;
|
||||||
import static androidx.test.espresso.action.ViewActions.swipeUp;
|
import static android.support.test.espresso.action.ViewActions.swipeUp;
|
||||||
import static androidx.test.espresso.action.ViewActions.typeText;
|
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||||
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
|
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
|
||||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
|
||||||
import static org.hamcrest.Matchers.allOf;
|
import static org.hamcrest.Matchers.allOf;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@ -91,7 +87,7 @@ public class MainActivityEspressoTest {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
SystemAnimations.disableAll(ApplicationProvider.getApplicationContext());
|
SystemAnimations.disableAll(InstrumentationRegistry.getTargetContext());
|
||||||
|
|
||||||
// dismiss the ANR or any other system dialogs that might be there
|
// dismiss the ANR or any other system dialogs that might be there
|
||||||
UiObject button = new UiObject(new UiSelector().text("Wait").enabled(true));
|
UiObject button = new UiObject(new UiSelector().text("Wait").enabled(true));
|
||||||
@ -104,7 +100,7 @@ public class MainActivityEspressoTest {
|
|||||||
|
|
||||||
Context context = instrumentation.getTargetContext();
|
Context context = instrumentation.getTargetContext();
|
||||||
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
|
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
|
||||||
ActivityManager activityManager = ContextCompat.getSystemService(context, ActivityManager.class);
|
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
activityManager.getMemoryInfo(mi);
|
activityManager.getMemoryInfo(mi);
|
||||||
long percentAvail = mi.availMem / mi.totalMem;
|
long percentAvail = mi.availMem / mi.totalMem;
|
||||||
Log.i(TAG, "RAM: " + mi.availMem + " / " + mi.totalMem + " = " + percentAvail);
|
Log.i(TAG, "RAM: " + mi.availMem + " / " + mi.totalMem + " = " + percentAvail);
|
||||||
@ -112,7 +108,7 @@ public class MainActivityEspressoTest {
|
|||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void classTearDown() {
|
public static void classTearDown() {
|
||||||
SystemAnimations.enableAll(ApplicationProvider.getApplicationContext());
|
SystemAnimations.enableAll(InstrumentationRegistry.getTargetContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEmulator() {
|
public static boolean isEmulator() {
|
||||||
@ -177,7 +173,6 @@ public class MainActivityEspressoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
|
||||||
public void showSettings() {
|
public void showSettings() {
|
||||||
ViewInteraction settingsBottonNavButton = onView(
|
ViewInteraction settingsBottonNavButton = onView(
|
||||||
allOf(withText(R.string.menu_settings), isDisplayed()));
|
allOf(withText(R.string.menu_settings), isDisplayed()));
|
||||||
@ -192,27 +187,9 @@ public class MainActivityEspressoTest {
|
|||||||
allOf(withText(R.string.preference_manage_installed_apps), isDisplayed()));
|
allOf(withText(R.string.preference_manage_installed_apps), isDisplayed()));
|
||||||
manageInstalledAppsButton.perform(click());
|
manageInstalledAppsButton.perform(click());
|
||||||
onView(withText(R.string.installed_apps__activity_title)).check(matches(isDisplayed()));
|
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
|
@LargeTest
|
||||||
@Test
|
|
||||||
public void showUpdates() {
|
public void showUpdates() {
|
||||||
ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed()));
|
ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed()));
|
||||||
updatesBottonNavButton.perform(click());
|
updatesBottonNavButton.perform(click());
|
||||||
@ -220,7 +197,6 @@ public class MainActivityEspressoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
|
||||||
public void startSwap() {
|
public void startSwap() {
|
||||||
if (!BuildConfig.FLAVOR.startsWith("full")) {
|
if (!BuildConfig.FLAVOR.startsWith("full")) {
|
||||||
return;
|
return;
|
||||||
@ -236,7 +212,6 @@ public class MainActivityEspressoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
|
||||||
public void showCategories() {
|
public void showCategories() {
|
||||||
if (!BuildConfig.FLAVOR.startsWith("full")) {
|
if (!BuildConfig.FLAVOR.startsWith("full")) {
|
||||||
return;
|
return;
|
||||||
@ -262,12 +237,11 @@ public class MainActivityEspressoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
|
||||||
public void showLatest() {
|
public void showLatest() {
|
||||||
if (!BuildConfig.FLAVOR.startsWith("full")) {
|
if (!BuildConfig.FLAVOR.startsWith("full")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onView(Matchers.<View>instanceOf(StatusBanner.class)).check(matches(not(isDisplayed())));
|
onView(Matchers.<View>instanceOf(BannerUpdatingRepos.class)).check(matches(not(isDisplayed())));
|
||||||
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
|
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
|
||||||
onView(allOf(withText(R.string.main_menu__latest_apps), isDisplayed())).perform(click());
|
onView(allOf(withText(R.string.main_menu__latest_apps), isDisplayed())).perform(click());
|
||||||
onView(allOf(withId(R.id.swipe_to_refresh), isDisplayed()))
|
onView(allOf(withId(R.id.swipe_to_refresh), isDisplayed()))
|
||||||
@ -285,7 +259,6 @@ public class MainActivityEspressoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@Test
|
|
||||||
public void showSearch() {
|
public void showSearch() {
|
||||||
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
|
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
|
||||||
onView(withId(R.id.fab_search)).check(doesNotExist());
|
onView(withId(R.id.fab_search)).check(doesNotExist());
|
||||||
|
@ -123,7 +123,7 @@ public class Netstat {
|
|||||||
try {
|
try {
|
||||||
BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp"));
|
BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp"));
|
||||||
String line;
|
String line;
|
||||||
while ((line = in.readLine()) != null) { // NOPMD
|
while ((line = in.readLine()) != null) {
|
||||||
Matcher matcher = NET_PATTERN.matcher(line);
|
Matcher matcher = NET_PATTERN.matcher(line);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
final Connection c = new Connection();
|
final Connection c = new Connection();
|
||||||
@ -141,7 +141,7 @@ public class Netstat {
|
|||||||
try {
|
try {
|
||||||
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
c.setStatus(STATES[11]); // unknown
|
c.setStatus(STATES[11]); // unknwon
|
||||||
}
|
}
|
||||||
c.setPID(-1); // unknown
|
c.setPID(-1); // unknown
|
||||||
c.setPName("UNKNOWN");
|
c.setPName("UNKNOWN");
|
||||||
@ -156,7 +156,7 @@ public class Netstat {
|
|||||||
try {
|
try {
|
||||||
BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp"));
|
BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp"));
|
||||||
String line;
|
String line;
|
||||||
while ((line = in.readLine()) != null) { // NOPMD
|
while ((line = in.readLine()) != null) {
|
||||||
Matcher matcher = NET_PATTERN.matcher(line);
|
Matcher matcher = NET_PATTERN.matcher(line);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
final Connection c = new Connection();
|
final Connection c = new Connection();
|
||||||
@ -174,7 +174,7 @@ public class Netstat {
|
|||||||
try {
|
try {
|
||||||
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
c.setStatus(STATES[11]); // unknown
|
c.setStatus(STATES[11]); // unknwon
|
||||||
}
|
}
|
||||||
c.setPID(-1); // unknown
|
c.setPID(-1); // unknown
|
||||||
c.setPName("UNKNOWN");
|
c.setPName("UNKNOWN");
|
||||||
@ -189,7 +189,7 @@ public class Netstat {
|
|||||||
try {
|
try {
|
||||||
BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw"));
|
BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw"));
|
||||||
String line;
|
String line;
|
||||||
while ((line = in.readLine()) != null) { // NOPMD
|
while ((line = in.readLine()) != null) {
|
||||||
Matcher matcher = NET_PATTERN.matcher(line);
|
Matcher matcher = NET_PATTERN.matcher(line);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
final Connection c = new Connection();
|
final Connection c = new Connection();
|
||||||
@ -208,7 +208,7 @@ public class Netstat {
|
|||||||
try {
|
try {
|
||||||
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
c.setStatus(STATES[11]); // unknown
|
c.setStatus(STATES[11]); // unknwon
|
||||||
}
|
}
|
||||||
c.setPID(-1); // unknown
|
c.setPID(-1); // unknown
|
||||||
c.setPName("UNKNOWN");
|
c.setPName("UNKNOWN");
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import androidx.test.uiautomator.UiDevice;
|
import android.support.test.uiautomator.UiDevice;
|
||||||
import androidx.test.uiautomator.UiObject;
|
import android.support.test.uiautomator.UiObject;
|
||||||
import androidx.test.uiautomator.UiObjectNotFoundException;
|
import android.support.test.uiautomator.UiObjectNotFoundException;
|
||||||
import androidx.test.uiautomator.UiSelector;
|
import android.support.test.uiautomator.UiSelector;
|
||||||
import androidx.test.uiautomator.UiWatcher;
|
import android.support.test.uiautomator.UiWatcher;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -117,7 +117,7 @@ public class UiWatchers {
|
|||||||
return false; // no trigger
|
return false; // no trigger
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Log.i(LOG_TAG, "Registered GUI Exception watchers");
|
Log.i(LOG_TAG, "Registed GUI Exception watchers");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAnrDetected(String errorText) {
|
public void onAnrDetected(String errorText) {
|
||||||
|
@ -4,8 +4,8 @@ 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.test.InstrumentationRegistry;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.AssetUtils;
|
import org.fdroid.fdroid.AssetUtils;
|
||||||
|
@ -22,10 +22,10 @@ package org.fdroid.fdroid.installer;
|
|||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.util.Log;
|
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.AssetUtils;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.compat.FileCompatTest;
|
import org.fdroid.fdroid.compat.FileCompatTest;
|
||||||
@ -113,7 +113,7 @@ public class ApkVerifierTest {
|
|||||||
Apk apk = new Apk();
|
Apk apk = new Apk();
|
||||||
apk.packageName = "org.fdroid.permissions.sdk14";
|
apk.packageName = "org.fdroid.permissions.sdk14";
|
||||||
apk.targetSdkVersion = 14;
|
apk.targetSdkVersion = 14;
|
||||||
ArrayList<String> noPrefixPermissionsList = new ArrayList<>(Arrays.asList(
|
String[] noPrefixPermissions = new String[]{
|
||||||
"AUTHENTICATE_ACCOUNTS",
|
"AUTHENTICATE_ACCOUNTS",
|
||||||
"MANAGE_ACCOUNTS",
|
"MANAGE_ACCOUNTS",
|
||||||
"READ_PROFILE",
|
"READ_PROFILE",
|
||||||
@ -129,13 +129,8 @@ public class ApkVerifierTest {
|
|||||||
"READ_SYNC_SETTINGS",
|
"READ_SYNC_SETTINGS",
|
||||||
"WRITE_SYNC_SETTINGS",
|
"WRITE_SYNC_SETTINGS",
|
||||||
"WRITE_CALL_LOG", // implied-permission!
|
"WRITE_CALL_LOG", // implied-permission!
|
||||||
"READ_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++) {
|
for (int i = 0; i < noPrefixPermissions.length; i++) {
|
||||||
noPrefixPermissions[i] = RepoXMLHandler.fdroidToAndroidPermission(noPrefixPermissions[i]);
|
noPrefixPermissions[i] = RepoXMLHandler.fdroidToAndroidPermission(noPrefixPermissions[i]);
|
||||||
}
|
}
|
||||||
@ -182,7 +177,7 @@ public class ApkVerifierTest {
|
|||||||
Apk apk = new Apk();
|
Apk apk = new Apk();
|
||||||
apk.packageName = "org.fdroid.permissions.sdk14";
|
apk.packageName = "org.fdroid.permissions.sdk14";
|
||||||
apk.targetSdkVersion = 14;
|
apk.targetSdkVersion = 14;
|
||||||
TreeSet<String> expectedSet = new TreeSet<>(Arrays.asList(
|
apk.requestedPermissions = new String[]{
|
||||||
"android.permission.AUTHENTICATE_ACCOUNTS",
|
"android.permission.AUTHENTICATE_ACCOUNTS",
|
||||||
"android.permission.MANAGE_ACCOUNTS",
|
"android.permission.MANAGE_ACCOUNTS",
|
||||||
"android.permission.READ_PROFILE",
|
"android.permission.READ_PROFILE",
|
||||||
@ -198,12 +193,8 @@ public class ApkVerifierTest {
|
|||||||
"android.permission.READ_SYNC_SETTINGS",
|
"android.permission.READ_SYNC_SETTINGS",
|
||||||
"android.permission.WRITE_SYNC_SETTINGS",
|
"android.permission.WRITE_SYNC_SETTINGS",
|
||||||
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
||||||
"android.permission.READ_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);
|
Uri uri = Uri.fromFile(sdk14Apk);
|
||||||
|
|
||||||
@ -380,9 +371,6 @@ public class ApkVerifierTest {
|
|||||||
"android.permission.MANAGE_ACCOUNTS"
|
"android.permission.MANAGE_ACCOUNTS"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 29) {
|
|
||||||
expectedSet.add("android.permission.ACCESS_MEDIA_LOCATION");
|
|
||||||
}
|
|
||||||
Apk apk = actualDetails.apks.get(1);
|
Apk apk = actualDetails.apks.get(1);
|
||||||
Log.i(TAG, "APK: " + apk.apkName);
|
Log.i(TAG, "APK: " + apk.apkName);
|
||||||
HashSet<String> actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
|
HashSet<String> actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
|
||||||
@ -419,9 +407,6 @@ public class ApkVerifierTest {
|
|||||||
"org.dmfs.permission.READ_TASKS",
|
"org.dmfs.permission.READ_TASKS",
|
||||||
"org.dmfs.permission.WRITE_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()]);
|
expectedPermissions = expectedSet.toArray(new String[expectedSet.size()]);
|
||||||
apk = actualDetails.apks.get(2);
|
apk = actualDetails.apks.get(2);
|
||||||
Log.i(TAG, "APK: " + apk.apkName);
|
Log.i(TAG, "APK: " + apk.apkName);
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.jmdns.ServiceEvent;
|
import javax.jmdns.ServiceEvent;
|
||||||
import javax.jmdns.ServiceListener;
|
import javax.jmdns.ServiceListener;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@ -26,7 +23,7 @@ public class BonjourManagerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartStop() throws InterruptedException {
|
public void testStartStop() throws InterruptedException {
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
FDroidApp.ipAddressString = LOCALHOST;
|
FDroidApp.ipAddressString = LOCALHOST;
|
||||||
FDroidApp.port = PORT;
|
FDroidApp.port = PORT;
|
||||||
@ -68,7 +65,7 @@ public class BonjourManagerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRestart() throws InterruptedException {
|
public void testRestart() throws InterruptedException {
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
FDroidApp.ipAddressString = LOCALHOST;
|
FDroidApp.ipAddressString = LOCALHOST;
|
||||||
FDroidApp.port = PORT;
|
FDroidApp.port = PORT;
|
@ -1,21 +1,19 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import android.support.test.filters.LargeTest;
|
||||||
import androidx.test.filters.LargeTest;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Netstat;
|
import org.fdroid.fdroid.Netstat;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@ -42,7 +40,7 @@ public class LocalHTTPDManagerTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
context = ApplicationProvider.getApplicationContext();
|
context = InstrumentationRegistry.getTargetContext();
|
||||||
lbm = LocalBroadcastManager.getInstance(context);
|
lbm = LocalBroadcastManager.getInstance(context);
|
||||||
|
|
||||||
FDroidApp.ipAddressString = LOCALHOST;
|
FDroidApp.ipAddressString = LOCALHOST;
|
||||||
@ -66,7 +64,6 @@ public class LocalHTTPDManagerTest {
|
|||||||
lbm.unregisterReceiver(errorReceiver);
|
lbm.unregisterReceiver(errorReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartStop() throws InterruptedException {
|
public void testStartStop() throws InterruptedException {
|
||||||
Log.i(TAG, "testStartStop");
|
Log.i(TAG, "testStartStop");
|
@ -6,8 +6,8 @@ import android.content.Intent;
|
|||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import androidx.test.filters.LargeTest;
|
import android.support.test.filters.LargeTest;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
@ -23,11 +23,10 @@ import org.fdroid.fdroid.data.AppProvider;
|
|||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
import org.fdroid.fdroid.data.Schema;
|
import org.fdroid.fdroid.data.Schema;
|
||||||
import org.fdroid.fdroid.nearby.LocalHTTPD;
|
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
||||||
import org.fdroid.fdroid.nearby.LocalRepoKeyStore;
|
import org.fdroid.fdroid.localrepo.LocalRepoManager;
|
||||||
import org.fdroid.fdroid.nearby.LocalRepoManager;
|
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
||||||
import org.fdroid.fdroid.nearby.LocalRepoService;
|
import org.fdroid.fdroid.net.LocalHTTPD;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -53,9 +52,8 @@ public class SwapRepoEmulatorTest {
|
|||||||
public static final String TAG = "SwapRepoEmulatorTest";
|
public static final String TAG = "SwapRepoEmulatorTest";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.fdroid.fdroid.nearby.WifiStateChangeService.WifiInfoThread#run()
|
* @see org.fdroid.fdroid.net.WifiStateChangeService.WifiInfoThread#run()
|
||||||
*/
|
*/
|
||||||
@Ignore
|
|
||||||
@Test
|
@Test
|
||||||
public void testSwap()
|
public void testSwap()
|
||||||
throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException {
|
throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException {
|
||||||
|
@ -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>(...);
|
|
||||||
}
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
@ -17,7 +17,7 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.nearby.peers;
|
package org.fdroid.fdroid.localrepo.peers;
|
||||||
|
|
||||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||||
|
|
@ -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 SwapService {
|
|
||||||
public static void start(Context context) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,11 +17,11 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.net;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import androidx.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dummy version for basic app flavor.
|
* Dummy version for basic app flavor.
|
@ -17,7 +17,7 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.net.bluetooth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dummy version for basic app flavor.
|
* Dummy version for basic app flavor.
|
@ -17,7 +17,7 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.panic;
|
package org.fdroid.fdroid.views.hiding;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
@ -19,11 +19,11 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid.views.main;
|
package org.fdroid.fdroid.views.main;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.widget.FrameLayout;
|
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.R;
|
||||||
import org.fdroid.fdroid.views.PreferencesFragment;
|
import org.fdroid.fdroid.views.PreferencesFragment;
|
||||||
import org.fdroid.fdroid.views.updates.UpdatesViewBinder;
|
import org.fdroid.fdroid.views.updates.UpdatesViewBinder;
|
||||||
@ -49,10 +49,10 @@ class MainViewController extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see LatestViewBinder
|
* @see WhatsNewViewBinder
|
||||||
*/
|
*/
|
||||||
public void bindLatestView() {
|
public void bindWhatsNewView() {
|
||||||
new LatestViewBinder(activity, frame);
|
new WhatsNewViewBinder(activity, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package org.fdroid.fdroid.views.main;
|
package org.fdroid.fdroid.views.main;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
class NearbyViewBinder {
|
class NearbyViewBinder {
|
||||||
public static void updateUsbOtg(Context context) {
|
static void onActivityResult(Activity activity, Intent data) {
|
||||||
throw new IllegalStateException("unimplemented");
|
throw new IllegalStateException("unimplemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -26,9 +26,8 @@ import android.net.Uri;
|
|||||||
* Dummy version for basic app flavor.
|
* Dummy version for basic app flavor.
|
||||||
*/
|
*/
|
||||||
public class SwapWorkflowActivity {
|
public class SwapWorkflowActivity {
|
||||||
|
|
||||||
public static final String EXTRA_PREVENT_FURTHER_SWAP_REQUESTS = "preventFurtherSwap";
|
public static final String EXTRA_PREVENT_FURTHER_SWAP_REQUESTS = "preventFurtherSwap";
|
||||||
|
public static final String EXTRA_CONFIRM = "EXTRA_CONFIRM";
|
||||||
public static void requestSwap(Context context, Uri uri) {
|
public static void requestSwap(Context context, Uri uri) {
|
||||||
}
|
};
|
||||||
}
|
}
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/basic/res/drawable-ldpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 7.4 KiB |
@ -6,7 +6,7 @@
|
|||||||
<!-- android:title and android:icon are set dynamically in MainActivity -->
|
<!-- android:title and android:icon are set dynamically in MainActivity -->
|
||||||
<item
|
<item
|
||||||
app:showAsAction="ifRoom|withText"
|
app:showAsAction="ifRoom|withText"
|
||||||
android:id="@+id/latest"/>
|
android:id="@+id/whats_new"/>
|
||||||
<item
|
<item
|
||||||
app:showAsAction="ifRoom|withText"
|
app:showAsAction="ifRoom|withText"
|
||||||
android:id="@+id/updates"/>
|
android:id="@+id/updates"/>
|
||||||
|
@ -1,25 +1,30 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
<android.support.v7.preference.PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<PreferenceScreen android:title="@string/about_title"
|
<android.support.v7.preference.PreferenceScreen android:title="@string/about_title">
|
||||||
android:key="pref_about" />
|
<intent
|
||||||
|
android:action="android.intent.action.MAIN"
|
||||||
|
android:targetPackage="@string/applicationId"
|
||||||
|
android:targetClass="org.fdroid.fdroid.AboutActivity"/>
|
||||||
|
</android.support.v7.preference.PreferenceScreen>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/preference_category__my_apps">
|
<android.support.v7.preference.PreferenceCategory android:title="@string/preference_category__my_apps">
|
||||||
<PreferenceScreen android:title="@string/preference_manage_installed_apps">
|
<android.support.v7.preference.PreferenceScreen android:title="@string/preference_manage_installed_apps">
|
||||||
<intent
|
<intent
|
||||||
android:action="android.intent.action.MAIN"
|
android:action="android.intent.action.MAIN"
|
||||||
android:targetPackage="@string/applicationId"
|
android:targetPackage="@string/applicationId"
|
||||||
android:targetClass="org.fdroid.fdroid.views.installed.InstalledAppsActivity"/>
|
android:targetClass="org.fdroid.fdroid.views.installed.InstalledAppsActivity"/>
|
||||||
</PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
||||||
<PreferenceScreen
|
<android.support.v7.preference.PreferenceScreen
|
||||||
android:title="@string/menu_manage"
|
android:title="@string/menu_manage"
|
||||||
android:summary="@string/repositories_summary">
|
android:summary="@string/repositories_summary">
|
||||||
<intent
|
<intent
|
||||||
android:action="android.intent.action.MAIN"
|
android:action="android.intent.action.MAIN"
|
||||||
android:targetPackage="@string/applicationId"
|
android:targetPackage="@string/applicationId"
|
||||||
android:targetClass="org.fdroid.fdroid.views.ManageReposActivity"/>
|
android:targetClass="org.fdroid.fdroid.views.ManageReposActivity"/>
|
||||||
</PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
||||||
<PreferenceScreen
|
<android.support.v7.preference.PreferenceScreen
|
||||||
android:key="installHistory"
|
android:key="installHistory"
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
android:title="@string/install_history"
|
android:title="@string/install_history"
|
||||||
@ -28,10 +33,10 @@
|
|||||||
android:action="android.intent.action.MAIN"
|
android:action="android.intent.action.MAIN"
|
||||||
android:targetPackage="@string/applicationId"
|
android:targetPackage="@string/applicationId"
|
||||||
android:targetClass="org.fdroid.fdroid.views.InstallHistoryActivity"/>
|
android:targetClass="org.fdroid.fdroid.views.InstallHistoryActivity"/>
|
||||||
</PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
||||||
</PreferenceCategory>
|
</android.support.v7.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/updates">
|
<android.support.v7.preference.PreferenceCategory android:title="@string/updates">
|
||||||
<org.fdroid.fdroid.views.LiveSeekBarPreference
|
<org.fdroid.fdroid.views.LiveSeekBarPreference
|
||||||
android:key="overWifi"
|
android:key="overWifi"
|
||||||
android:title="@string/over_wifi"
|
android:title="@string/over_wifi"
|
||||||
@ -42,7 +47,7 @@
|
|||||||
android:title="@string/over_data"
|
android:title="@string/over_data"
|
||||||
android:defaultValue="@integer/defaultOverData"
|
android:defaultValue="@integer/defaultOverData"
|
||||||
android:layout="@layout/preference_seekbar"/>
|
android:layout="@layout/preference_seekbar"/>
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:title="@string/update_auto_download"
|
android:title="@string/update_auto_download"
|
||||||
android:summary="@string/update_auto_download_summary"
|
android:summary="@string/update_auto_download_summary"
|
||||||
android:key="updateAutoDownload"/>
|
android:key="updateAutoDownload"/>
|
||||||
@ -51,13 +56,13 @@
|
|||||||
android:title="@string/update_interval"
|
android:title="@string/update_interval"
|
||||||
android:defaultValue="@integer/defaultUpdateInterval"
|
android:defaultValue="@integer/defaultUpdateInterval"
|
||||||
android:layout="@layout/preference_seekbar"/>
|
android:layout="@layout/preference_seekbar"/>
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:title="@string/notify"
|
android:title="@string/notify"
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="updateNotify"/>
|
android:key="updateNotify"/>
|
||||||
</PreferenceCategory>
|
</android.support.v7.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/display"
|
<android.support.v7.preference.PreferenceCategory android:title="@string/display"
|
||||||
android:key="pref_category_display">
|
android:key="pref_category_display">
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:title="@string/pref_language"
|
android:title="@string/pref_language"
|
||||||
@ -68,30 +73,30 @@
|
|||||||
android:defaultValue="light"
|
android:defaultValue="light"
|
||||||
android:entries="@array/themeNames"
|
android:entries="@array/themeNames"
|
||||||
android:entryValues="@array/themeValues"/>
|
android:entryValues="@array/themeValues"/>
|
||||||
</PreferenceCategory>
|
</android.support.v7.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/appcompatibility"
|
<android.support.v7.preference.PreferenceCategory android:title="@string/appcompatibility"
|
||||||
android:key="pref_category_appcompatibility">
|
android:key="pref_category_appcompatibility">
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:title="@string/show_incompat_versions"
|
android:title="@string/show_incompat_versions"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="incompatibleVersions"/>
|
android:key="incompatibleVersions"/>
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:title="@string/show_anti_feature_apps"
|
android:title="@string/show_anti_feature_apps"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="showAntiFeatureApps"/>
|
android:key="showAntiFeatureApps"/>
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:title="@string/force_touch_apps"
|
android:title="@string/force_touch_apps"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="ignoreTouchscreen"/>
|
android:key="ignoreTouchscreen"/>
|
||||||
</PreferenceCategory>
|
</android.support.v7.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/proxy">
|
<android.support.v7.preference.PreferenceCategory android:title="@string/proxy">
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:key="useTor"
|
android:key="useTor"
|
||||||
android:summary="@string/useTorSummary"
|
android:summary="@string/useTorSummary"
|
||||||
android:title="@string/useTor"/>
|
android:title="@string/useTor"/>
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="enableProxy"
|
android:key="enableProxy"
|
||||||
android:title="@string/enable_proxy_title"
|
android:title="@string/enable_proxy_title"
|
||||||
@ -106,24 +111,24 @@
|
|||||||
android:title="@string/proxy_port"
|
android:title="@string/proxy_port"
|
||||||
android:summary="@string/proxy_port_summary"
|
android:summary="@string/proxy_port_summary"
|
||||||
android:dependency="enableProxy"/>
|
android:dependency="enableProxy"/>
|
||||||
</PreferenceCategory>
|
</android.support.v7.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<android.support.v7.preference.PreferenceCategory
|
||||||
android:key="pref_category_privacy"
|
android:key="pref_category_privacy"
|
||||||
android:title="@string/privacy">
|
android:title="@string/privacy">
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:key="promptToSendCrashReports"
|
android:key="promptToSendCrashReports"
|
||||||
android:title="@string/prompt_to_send_crash_reports"
|
android:title="@string/prompt_to_send_crash_reports"
|
||||||
android:summary="@string/prompt_to_send_crash_reports_summary"
|
android:summary="@string/prompt_to_send_crash_reports_summary"
|
||||||
android:defaultValue="true"/>
|
android:defaultValue="true"/>
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="preventScreenshots"
|
android:key="preventScreenshots"
|
||||||
android:summary="@string/preventScreenshots_summary"
|
android:summary="@string/preventScreenshots_summary"
|
||||||
android:title="@string/preventScreenshots_title"/>
|
android:title="@string/preventScreenshots_title"/>
|
||||||
</PreferenceCategory>
|
</android.support.v7.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<android.support.v7.preference.PreferenceCategory
|
||||||
android:title="@string/other"
|
android:title="@string/other"
|
||||||
android:key="pref_category_other">
|
android:key="pref_category_other">
|
||||||
<ListPreference
|
<ListPreference
|
||||||
@ -132,7 +137,7 @@
|
|||||||
android:defaultValue="86400000"
|
android:defaultValue="86400000"
|
||||||
android:entries="@array/keepCacheNames"
|
android:entries="@array/keepCacheNames"
|
||||||
android:entryValues="@array/keepCacheValues"/>
|
android:entryValues="@array/keepCacheValues"/>
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:title="@string/expert"
|
android:title="@string/expert"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="expert"/>
|
android:key="expert"/>
|
||||||
@ -148,12 +153,6 @@
|
|||||||
android:summary="@string/keep_install_history_summary"
|
android:summary="@string/keep_install_history_summary"
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:dependency="expert"/>
|
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
|
<CheckBoxPreference
|
||||||
android:key="hideAllNotifications"
|
android:key="hideAllNotifications"
|
||||||
android:title="@string/hide_all_notifications"
|
android:title="@string/hide_all_notifications"
|
||||||
@ -178,6 +177,6 @@
|
|||||||
android:key="privilegedInstaller"
|
android:key="privilegedInstaller"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:dependency="expert"/>
|
android:dependency="expert"/>
|
||||||
</PreferenceCategory>
|
</android.support.v7.preference.PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</android.support.v7.preference.PreferenceScreen>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<!-- This file should be outside of release manifest (in this case app/src/mock/Manifest.xml -->
|
<!-- 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">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<!--required to enable/disable system animations from the app itself during Espresso test runs-->
|
<!--required to enable/disable system animations from the app itself during Espresso test runs-->
|
||||||
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
|
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
* Copyright (C) 2010-2012 Ciaran Gultnieks
|
* Copyright (C) 2010-2012 Ciaran Gultnieks
|
||||||
* Copyright (C) 2013-2017 Peter Serwylo
|
* Copyright (C) 2013-2017 Peter Serwylo
|
||||||
@ -27,152 +26,111 @@
|
|||||||
package="org.fdroid.fdroid"
|
package="org.fdroid.fdroid"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature android:name="android.hardware.nfc" android:required="false"/>
|
||||||
android:name="android.hardware.nfc"
|
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||||
android:required="false" />
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.bluetooth"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<uses-feature
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
android:name="android.hardware.usb.host"
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
android:required="false" />
|
<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.WAKE_LOCK"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<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>
|
<application>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".nearby.SwapWorkflowActivity"
|
|
||||||
android:configChanges="orientation|keyboardHidden"
|
|
||||||
android:label="@string/swap"
|
android:label="@string/swap"
|
||||||
android:launchMode="singleTask"
|
android:name=".views.swap.SwapWorkflowActivity"
|
||||||
android:parentActivityName=".views.main.MainActivity"
|
android:parentActivityName=".views.main.MainActivity"
|
||||||
android:screenOrientation="portrait">
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/SwapTheme.Wizard"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:configChanges="orientation|keyboardHidden">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".views.main.MainActivity" />
|
android:value=".views.main.MainActivity"/>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<receiver android:name=".receiver.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>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".net.WifiStateChangeService"
|
||||||
|
android:exported="false"/>
|
||||||
|
<service android:name=".localrepo.SwapService"/>
|
||||||
|
|
||||||
|
<service android:name=".localrepo.LocalHTTPDManager"/>
|
||||||
|
<service
|
||||||
|
android:name=".localrepo.LocalRepoService"
|
||||||
|
android:exported="false"/>
|
||||||
|
<service
|
||||||
|
android:name=".localrepo.TreeUriScannerIntentService"
|
||||||
|
android:exported="false"/>
|
||||||
|
<service
|
||||||
|
android:name=".localrepo.SDCardScannerService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".panic.PanicPreferencesActivity"
|
android:name=".views.panic.PanicPreferencesActivity"
|
||||||
android:label="@string/panic_settings"
|
android:label="@string/panic_settings"
|
||||||
android:parentActivityName=".views.main.MainActivity">
|
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
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".views.main.MainActivity" />
|
android:value=".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>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".panic.SelectInstalledAppsActivity"
|
android:name=".views.panic.PanicResponderActivity"
|
||||||
android:parentActivityName=".panic.PanicPreferencesActivity" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".panic.PanicResponderActivity"
|
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@android:style/Theme.NoDisplay">
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
|
||||||
<!-- this can never have launchMode singleTask or singleInstance! -->
|
<!-- this can never have launchMode singleTask or singleInstance! -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".panic.ExitActivity"
|
android:name=".views.panic.ExitActivity"
|
||||||
android:theme="@android:style/Theme.NoDisplay" />
|
android:theme="@android:style/Theme.NoDisplay"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".panic.CalculatorActivity"
|
android:name=".views.hiding.CalculatorActivity"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:icon="@mipmap/ic_calculator_launcher"
|
android:icon="@mipmap/ic_calculator_launcher"
|
||||||
android:label="@string/hiding_calculator">
|
android:label="@string/hiding_calculator"
|
||||||
|
android:theme="@style/AppThemeLight">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</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>
|
</application>
|
||||||
|
|
||||||
|
@ -4,13 +4,12 @@ import android.os.Parcel;
|
|||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
import javax.jmdns.impl.util.ByteWrangler;
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import javax.jmdns.ServiceInfo;
|
|
||||||
import javax.jmdns.impl.util.ByteWrangler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ServiceInfo class needs to be serialized in order to be sent as an Android broadcast.
|
* The ServiceInfo class needs to be serialized in order to be sent as an Android broadcast.
|
||||||
* In order to make it Parcelable (or Serializable for that matter), there are some package-scope
|
* In order to make it Parcelable (or Serializable for that matter), there are some package-scope
|
||||||
|
@ -113,7 +113,7 @@ public class KeyStoreFileManager {
|
|||||||
File keystoreFile = new File(keystorePath);
|
File keystoreFile = new File(keystorePath);
|
||||||
try {
|
try {
|
||||||
if (keystoreFile.exists()) {
|
if (keystoreFile.exists()) {
|
||||||
// I've had some trouble saving new versions of the keystore file in which the file becomes empty/corrupt.
|
// I've had some trouble saving new verisons of the keystore file in which the file becomes empty/corrupt.
|
||||||
// Saving the new version to a new file and creating a backup of the old version.
|
// Saving the new version to a new file and creating a backup of the old version.
|
||||||
File tmpFile = File.createTempFile(keystoreFile.getName(), null, keystoreFile.getParentFile());
|
File tmpFile = File.createTempFile(keystoreFile.getName(), null, keystoreFile.getParentFile());
|
||||||
FileOutputStream fos = new FileOutputStream(tmpFile);
|
FileOutputStream fos = new FileOutputStream(tmpFile);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
@ -10,17 +10,16 @@ import android.os.Handler;
|
|||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.nearby.peers.BluetoothPeer;
|
import org.fdroid.fdroid.localrepo.peers.BluetoothPeer;
|
||||||
|
import org.fdroid.fdroid.net.bluetooth.BluetoothServer;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage the {@link android.bluetooth.BluetoothAdapter}in a {@link HandlerThread}.
|
* Manage the {@link android.bluetooth.BluetoothAdapter}in a {@link HandlerThread}.
|
||||||
* The start process is in {@link HandlerThread#onLooperPrepared()} so that it is
|
* The start process is in {@link HandlerThread#onLooperPrepared()} so that it is
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -7,26 +7,22 @@ import android.os.Handler;
|
|||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.nearby.peers.BonjourPeer;
|
import org.fdroid.fdroid.localrepo.peers.BonjourPeer;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import javax.jmdns.JmDNS;
|
import javax.jmdns.JmDNS;
|
||||||
import javax.jmdns.ServiceEvent;
|
import javax.jmdns.ServiceEvent;
|
||||||
import javax.jmdns.ServiceInfo;
|
import javax.jmdns.ServiceInfo;
|
||||||
import javax.jmdns.ServiceListener;
|
import javax.jmdns.ServiceListener;
|
||||||
|
import java.io.IOException;
|
||||||
import androidx.core.content.ContextCompat;
|
import java.lang.ref.WeakReference;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import java.net.InetAddress;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage {@link JmDNS} in a {@link HandlerThread}. The start process is in
|
* Manage {@link JmDNS} in a {@link HandlerThread}. The start process is in
|
||||||
@ -123,7 +119,8 @@ public class BonjourManager {
|
|||||||
}
|
}
|
||||||
sendBroadcast(STATUS_STARTING, null);
|
sendBroadcast(STATUS_STARTING, null);
|
||||||
|
|
||||||
final WifiManager wifiManager = ContextCompat.getSystemService(context, WifiManager.class);
|
final WifiManager wifiManager = (WifiManager) context.getApplicationContext()
|
||||||
|
.getSystemService(Context.WIFI_SERVICE);
|
||||||
handlerThread = new HandlerThread("BonjourManager", Process.THREAD_PRIORITY_LESS_FAVORABLE) {
|
handlerThread = new HandlerThread("BonjourManager", Process.THREAD_PRIORITY_LESS_FAVORABLE) {
|
||||||
@Override
|
@Override
|
||||||
protected void onLooperPrepared() {
|
protected void onLooperPrepared() {
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -6,17 +6,17 @@ import android.os.Handler;
|
|||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.net.LocalHTTPD;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.BindException;
|
import java.net.BindException;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage {@link LocalHTTPD} in a {@link HandlerThread};
|
* Manage {@link LocalHTTPD} in a {@link HandlerThread};
|
||||||
*/
|
*/
|
@ -1,8 +1,10 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import kellinwood.security.zipsigner.ZipSigner;
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.bouncycastle.asn1.ASN1Sequence;
|
import org.bouncycastle.asn1.ASN1Sequence;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
import org.bouncycastle.asn1.x509.GeneralName;
|
||||||
@ -16,9 +18,10 @@ import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
|||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
import org.bouncycastle.operator.OperatorCreationException;
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.X509KeyManager;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@ -46,12 +49,6 @@ import java.util.Date;
|
|||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
|
||||||
import javax.net.ssl.X509KeyManager;
|
|
||||||
|
|
||||||
import kellinwood.security.zipsigner.ZipSigner;
|
|
||||||
|
|
||||||
// TODO Address exception handling in a uniform way throughout
|
// TODO Address exception handling in a uniform way throughout
|
||||||
|
|
||||||
@SuppressWarnings("LineLength")
|
@SuppressWarnings("LineLength")
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@ -10,9 +10,10 @@ import android.graphics.Bitmap.Config;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Hasher;
|
import org.fdroid.fdroid.Hasher;
|
||||||
import org.fdroid.fdroid.IndexUpdater;
|
import org.fdroid.fdroid.IndexUpdater;
|
||||||
@ -49,15 +50,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link SwapService} deals with managing the entire workflow from selecting apps to
|
* The {@link SwapService} deals with managing the entire workflow from selecting apps to
|
||||||
* swap, to invoking this class to prepare the webroot, to enabling various communication protocols.
|
* swap, to invoking this class to prepare the webroot, to enabling various communication protocols.
|
||||||
* This class deals specifically with the webroot side of things, ensuring we have a valid index.jar
|
* This class deals specifically with the webroot side of things, ensuring we have a valid index.jar
|
||||||
* and the relevant .apk and icon files available.
|
* and the relevant .apk and icon files available.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("LineLength")
|
||||||
public final class LocalRepoManager {
|
public final class LocalRepoManager {
|
||||||
private static final String TAG = "LocalRepoManager";
|
private static final String TAG = "LocalRepoManager";
|
||||||
|
|
||||||
@ -66,7 +65,7 @@ public final class LocalRepoManager {
|
|||||||
private final AssetManager assetManager;
|
private final AssetManager assetManager;
|
||||||
private final String fdroidPackageName;
|
private final String fdroidPackageName;
|
||||||
|
|
||||||
public static final String[] WEB_ROOT_ASSET_FILES = {
|
private static final String[] WEB_ROOT_ASSET_FILES = {
|
||||||
"swap-icon.png",
|
"swap-icon.png",
|
||||||
"swap-tick-done.png",
|
"swap-tick-done.png",
|
||||||
"swap-tick-not-done.png",
|
"swap-tick-not-done.png",
|
||||||
@ -125,7 +124,7 @@ public final class LocalRepoManager {
|
|||||||
|
|
||||||
private String writeFdroidApkToWebroot() {
|
private String writeFdroidApkToWebroot() {
|
||||||
ApplicationInfo appInfo;
|
ApplicationInfo appInfo;
|
||||||
String fdroidClientURL = "https://f-droid.org/F-Droid.apk";
|
String fdroidClientURL = "https://f-droid.org/FDroid.apk";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
appInfo = pm.getApplicationInfo(fdroidPackageName, PackageManager.GET_META_DATA);
|
appInfo = pm.getApplicationInfo(fdroidPackageName, PackageManager.GET_META_DATA);
|
||||||
@ -349,8 +348,7 @@ public final class LocalRepoManager {
|
|||||||
serializer = XmlPullParserFactory.newInstance().newSerializer();
|
serializer = XmlPullParserFactory.newInstance().newSerializer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(Context context, Map<String, App> apps, OutputStream output)
|
public void build(Context context, Map<String, App> apps, OutputStream output) throws IOException, LocalRepoKeyStore.InitException {
|
||||||
throws IOException, LocalRepoKeyStore.InitException {
|
|
||||||
serializer.setOutput(output, "UTF-8");
|
serializer.setOutput(output, "UTF-8");
|
||||||
serializer.startDocument(null, null);
|
serializer.startDocument(null, null);
|
||||||
serializer.startTag("", "fdroid");
|
serializer.startTag("", "fdroid");
|
||||||
@ -358,14 +356,12 @@ public final class LocalRepoManager {
|
|||||||
// <repo> block
|
// <repo> block
|
||||||
serializer.startTag("", "repo");
|
serializer.startTag("", "repo");
|
||||||
serializer.attribute("", "icon", "blah.png");
|
serializer.attribute("", "icon", "blah.png");
|
||||||
serializer.attribute("", "name", Preferences.get().getLocalRepoName()
|
serializer.attribute("", "name", Preferences.get().getLocalRepoName() + " on " + FDroidApp.ipAddressString);
|
||||||
+ " on " + FDroidApp.ipAddressString);
|
|
||||||
serializer.attribute("", "pubkey", Hasher.hex(LocalRepoKeyStore.get(context).getCertificate()));
|
serializer.attribute("", "pubkey", Hasher.hex(LocalRepoKeyStore.get(context).getCertificate()));
|
||||||
long timestamp = System.currentTimeMillis() / 1000L;
|
long timestamp = System.currentTimeMillis() / 1000L;
|
||||||
serializer.attribute("", "timestamp", String.valueOf(timestamp));
|
serializer.attribute("", "timestamp", String.valueOf(timestamp));
|
||||||
serializer.attribute("", "version", "10");
|
serializer.attribute("", "version", "10");
|
||||||
tag("description", "A local FDroid repo generated from apps installed on "
|
tag("description", "A local FDroid repo generated from apps installed on " + Preferences.get().getLocalRepoName());
|
||||||
+ Preferences.get().getLocalRepoName());
|
|
||||||
serializer.endTag("", "repo");
|
serializer.endTag("", "repo");
|
||||||
|
|
||||||
// <application> blocks
|
// <application> blocks
|
||||||
@ -390,7 +386,7 @@ public final class LocalRepoManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for {@link org.fdroid.fdroid.nearby.LocalRepoManager.IndexXmlBuilder#tag(String, String)}
|
* Alias for {@link org.fdroid.fdroid.localrepo.LocalRepoManager.IndexXmlBuilder#tag(String, String)}
|
||||||
* That accepts a number instead of string.
|
* That accepts a number instead of string.
|
||||||
*
|
*
|
||||||
* @see IndexXmlBuilder#tag(String, String)
|
* @see IndexXmlBuilder#tag(String, String)
|
||||||
@ -400,7 +396,7 @@ public final class LocalRepoManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for {@link org.fdroid.fdroid.nearby.LocalRepoManager.IndexXmlBuilder#tag(String, String)}
|
* Alias for {@link org.fdroid.fdroid.localrepo.LocalRepoManager.IndexXmlBuilder#tag(String, String)}
|
||||||
* that accepts a date instead of a string.
|
* that accepts a date instead of a string.
|
||||||
*
|
*
|
||||||
* @see IndexXmlBuilder#tag(String, String)
|
* @see IndexXmlBuilder#tag(String, String)
|
||||||
@ -418,7 +414,7 @@ public final class LocalRepoManager {
|
|||||||
tag("lastupdated", app.lastUpdated);
|
tag("lastupdated", app.lastUpdated);
|
||||||
tag("name", app.name);
|
tag("name", app.name);
|
||||||
tag("summary", app.summary);
|
tag("summary", app.summary);
|
||||||
tag("icon", app.iconFromApk);
|
tag("icon", app.icon);
|
||||||
tag("desc", app.description);
|
tag("desc", app.description);
|
||||||
tag("license", "Unknown");
|
tag("license", "Unknown");
|
||||||
tag("categories", "LocalRepo," + Preferences.get().getLocalRepoName());
|
tag("categories", "LocalRepo," + Preferences.get().getLocalRepoName());
|
@ -1,10 +1,10 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
@ -15,8 +15,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles setting up and generating the local repo used to swap apps, including
|
* Handles setting up and generating the local repo used to swap apps, including
|
||||||
* the {@code index.jar}, the symlinks to the shared APKs, etc.
|
* the {@code index.jar}, the symlinks to the shared APKs, etc.
|
||||||
@ -31,8 +29,8 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|||||||
public class LocalRepoService extends IntentService {
|
public class LocalRepoService extends IntentService {
|
||||||
public static final String TAG = "LocalRepoService";
|
public static final String TAG = "LocalRepoService";
|
||||||
|
|
||||||
public static final String ACTION_CREATE = "org.fdroid.fdroid.nearby.action.CREATE";
|
public static final String ACTION_CREATE = "org.fdroid.fdroid.localrepo.action.CREATE";
|
||||||
public static final String EXTRA_PACKAGE_NAMES = "org.fdroid.fdroid.nearby.extra.PACKAGE_NAMES";
|
public static final String EXTRA_PACKAGE_NAMES = "org.fdroid.fdroid.localrepo.extra.PACKAGE_NAMES";
|
||||||
|
|
||||||
public static final String ACTION_STATUS = "localRepoStatusAction";
|
public static final String ACTION_STATUS = "localRepoStatusAction";
|
||||||
public static final String EXTRA_STATUS = "localRepoStatusExtra";
|
public static final String EXTRA_STATUS = "localRepoStatusExtra";
|
@ -17,7 +17,7 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
@ -28,10 +28,11 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.IndexUpdater;
|
import org.fdroid.fdroid.IndexUpdater;
|
||||||
import org.fdroid.fdroid.IndexV1Updater;
|
import org.fdroid.fdroid.IndexV1Updater;
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -44,11 +45,9 @@ import java.util.Collections;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link IntentService} subclass for scanning removable "external storage"
|
* An {@link IntentService} subclass for scanning removable "external storage"
|
||||||
* for F-Droid package repos, e.g. SD Cards. This is intended to support
|
* for F-Droid package repos, e.g. SD Cards. This is intented to support
|
||||||
* sharable package repos, so it ignores non-removable storage, like the fake
|
* sharable package repos, so it ignores non-removable storage, like the fake
|
||||||
* emulated sdcard from devices with only built-in storage. This method will
|
* emulated sdcard from devices with only built-in storage. This method will
|
||||||
* only ever allow for reading repos, never writing. It also will not work
|
* only ever allow for reading repos, never writing. It also will not work
|
||||||
@ -56,7 +55,7 @@ import androidx.core.content.ContextCompat;
|
|||||||
* "External Storage"
|
* "External Storage"
|
||||||
* <p>
|
* <p>
|
||||||
* Scanning the removable storage requires that the user allowed it. This
|
* Scanning the removable storage requires that the user allowed it. This
|
||||||
* requires both the {@link org.fdroid.fdroid.Preferences#isScanRemovableStorageEnabled()}
|
* requires both the {@link Preferences#isScanRemovableStorageEnabled()}
|
||||||
* and the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}
|
* and the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}
|
||||||
* permission to be enabled.
|
* permission to be enabled.
|
||||||
*
|
*
|
||||||
@ -67,7 +66,7 @@ import androidx.core.content.ContextCompat;
|
|||||||
public class SDCardScannerService extends IntentService {
|
public class SDCardScannerService extends IntentService {
|
||||||
public static final String TAG = "SDCardScannerService";
|
public static final String TAG = "SDCardScannerService";
|
||||||
|
|
||||||
private static final String ACTION_SCAN = "org.fdroid.fdroid.nearby.SCAN";
|
private static final String ACTION_SCAN = "org.fdroid.fdroid.localrepo.SCAN";
|
||||||
|
|
||||||
private static final List<String> SKIP_DIRS = Arrays.asList(".android_secure", "LOST.DIR");
|
private static final List<String> SKIP_DIRS = Arrays.asList(".android_secure", "LOST.DIR");
|
||||||
|
|
||||||
@ -76,10 +75,12 @@ public class SDCardScannerService extends IntentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void scan(Context context) {
|
public static void scan(Context context) {
|
||||||
|
if (Preferences.get().isScanRemovableStorageEnabled()) {
|
||||||
Intent intent = new Intent(context, SDCardScannerService.class);
|
Intent intent = new Intent(context, SDCardScannerService.class);
|
||||||
intent.setAction(ACTION_SCAN);
|
intent.setAction(ACTION_SCAN);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onHandleIntent(Intent intent) {
|
protected void onHandleIntent(Intent intent) {
|
@ -1,5 +1,6 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
@ -12,19 +13,16 @@ import android.content.IntentFilter;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.app.ServiceCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.NotificationHelper;
|
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.UpdateService;
|
import org.fdroid.fdroid.UpdateService;
|
||||||
@ -32,9 +30,12 @@ import org.fdroid.fdroid.Utils;
|
|||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
import org.fdroid.fdroid.data.Schema;
|
import org.fdroid.fdroid.data.Schema;
|
||||||
import org.fdroid.fdroid.nearby.peers.Peer;
|
import org.fdroid.fdroid.localrepo.peers.Peer;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
@ -45,12 +46,6 @@ import java.util.Set;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
import cc.mvdan.accesspoint.WifiApControl;
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.rxjava3.core.Completable;
|
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central service which manages all of the different moving parts of swap which are required
|
* Central service which manages all of the different moving parts of swap which are required
|
||||||
* to enable p2p swapping of apps.
|
* to enable p2p swapping of apps.
|
||||||
@ -62,11 +57,9 @@ public class SwapService extends Service {
|
|||||||
private static final String KEY_APPS_TO_SWAP = "appsToSwap";
|
private static final String KEY_APPS_TO_SWAP = "appsToSwap";
|
||||||
private static final String KEY_BLUETOOTH_ENABLED = "bluetoothEnabled";
|
private static final String KEY_BLUETOOTH_ENABLED = "bluetoothEnabled";
|
||||||
private static final String KEY_WIFI_ENABLED = "wifiEnabled";
|
private static final String KEY_WIFI_ENABLED = "wifiEnabled";
|
||||||
private static final String KEY_HOTSPOT_ACTIVATED = "hotspotEnabled";
|
|
||||||
private static final String KEY_BLUETOOTH_ENABLED_BEFORE_SWAP = "bluetoothEnabledBeforeSwap";
|
private static final String KEY_BLUETOOTH_ENABLED_BEFORE_SWAP = "bluetoothEnabledBeforeSwap";
|
||||||
private static final String KEY_BLUETOOTH_NAME_BEFORE_SWAP = "bluetoothNameBeforeSwap";
|
private static final String KEY_BLUETOOTH_NAME_BEFORE_SWAP = "bluetoothNameBeforeSwap";
|
||||||
private static final String KEY_WIFI_ENABLED_BEFORE_SWAP = "wifiEnabledBeforeSwap";
|
private static final String KEY_WIFI_ENABLED_BEFORE_SWAP = "wifiEnabledBeforeSwap";
|
||||||
private static final String KEY_HOTSPOT_ACTIVATED_BEFORE_SWAP = "hotspotEnabledBeforeSwap";
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Set<String> appsToSwap = new HashSet<>();
|
private final Set<String> appsToSwap = new HashSet<>();
|
||||||
@ -78,6 +71,15 @@ public class SwapService extends Service {
|
|||||||
private static WifiManager wifiManager;
|
private static WifiManager wifiManager;
|
||||||
private static Timer pollConnectedSwapRepoTimer;
|
private static Timer pollConnectedSwapRepoTimer;
|
||||||
|
|
||||||
|
public static void start(Context context) {
|
||||||
|
Intent intent = new Intent(context, SwapService.class);
|
||||||
|
if (Build.VERSION.SDK_INT < 26) {
|
||||||
|
context.startService(intent);
|
||||||
|
} else {
|
||||||
|
context.startForegroundService(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void stop(Context context) {
|
public static void stop(Context context) {
|
||||||
Intent intent = new Intent(context, SwapService.class);
|
Intent intent = new Intent(context, SwapService.class);
|
||||||
context.stopService(intent);
|
context.stopService(intent);
|
||||||
@ -111,6 +113,46 @@ public class SwapService extends Service {
|
|||||||
UpdateService.updateRepoNow(this, peer.getRepoAddress());
|
UpdateService.updateRepoNow(this, peer.getRepoAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
private void askServerToSwapWithUs(final Repo repo) {
|
||||||
|
new AsyncTask<Void, Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... args) {
|
||||||
|
String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
|
||||||
|
HttpURLConnection conn = null;
|
||||||
|
try {
|
||||||
|
URL url = new URL(repo.address.replace("/fdroid/repo", "/request-swap"));
|
||||||
|
conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("POST");
|
||||||
|
conn.setDoInput(true);
|
||||||
|
conn.setDoOutput(true);
|
||||||
|
|
||||||
|
OutputStream outputStream = conn.getOutputStream();
|
||||||
|
OutputStreamWriter writer = new OutputStreamWriter(outputStream);
|
||||||
|
writer.write("repo=" + swapBackUri);
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
outputStream.close();
|
||||||
|
|
||||||
|
int responseCode = conn.getResponseCode();
|
||||||
|
Utils.debugLog(TAG, "Asking server at " + repo.address + " to swap with us in return (by " +
|
||||||
|
"POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\"): " + responseCode);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error while asking server to swap with us", e);
|
||||||
|
Intent intent = new Intent(Downloader.ACTION_INTERRUPTED);
|
||||||
|
intent.setData(Uri.parse(repo.address));
|
||||||
|
intent.putExtra(Downloader.EXTRA_ERROR_MESSAGE, e.getLocalizedMessage());
|
||||||
|
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
private Repo ensureRepoExists(@NonNull Peer peer) {
|
private Repo ensureRepoExists(@NonNull Peer peer) {
|
||||||
// TODO: newRepoConfig.getParsedUri() will include a fingerprint, which may not match with
|
// TODO: newRepoConfig.getParsedUri() will include a fingerprint, which may not match with
|
||||||
// the repos address in the database. Not sure on best behaviour in this situation.
|
// the repos address in the database. Not sure on best behaviour in this situation.
|
||||||
@ -252,14 +294,6 @@ public class SwapService extends Service {
|
|||||||
swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED, visible).apply();
|
swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED, visible).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getHotspotActivatedUserPreference() {
|
|
||||||
return swapPreferences.getBoolean(SwapService.KEY_HOTSPOT_ACTIVATED, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putHotspotActivatedUserPreference(boolean visible) {
|
|
||||||
swapPreferences.edit().putBoolean(SwapService.KEY_HOTSPOT_ACTIVATED, visible).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean wasBluetoothEnabledBeforeSwap() {
|
public static boolean wasBluetoothEnabledBeforeSwap() {
|
||||||
return swapPreferences.getBoolean(SwapService.KEY_BLUETOOTH_ENABLED_BEFORE_SWAP, false);
|
return swapPreferences.getBoolean(SwapService.KEY_BLUETOOTH_ENABLED_BEFORE_SWAP, false);
|
||||||
}
|
}
|
||||||
@ -284,14 +318,6 @@ public class SwapService extends Service {
|
|||||||
swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED_BEFORE_SWAP, visible).apply();
|
swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED_BEFORE_SWAP, visible).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean wasHotspotEnabledBeforeSwap() {
|
|
||||||
return swapPreferences.getBoolean(SwapService.KEY_HOTSPOT_ACTIVATED_BEFORE_SWAP, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putHotspotEnabledBeforeSwap(boolean visible) {
|
|
||||||
swapPreferences.edit().putBoolean(SwapService.KEY_HOTSPOT_ACTIVATED_BEFORE_SWAP, visible).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int NOTIFICATION = 1;
|
private static final int NOTIFICATION = 1;
|
||||||
|
|
||||||
private final Binder binder = new Binder();
|
private final Binder binder = new Binder();
|
||||||
@ -304,15 +330,12 @@ public class SwapService extends Service {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private Timer timer;
|
private Timer timer;
|
||||||
|
|
||||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
public class Binder extends android.os.Binder {
|
public class Binder extends android.os.Binder {
|
||||||
public SwapService getService() {
|
public SwapService getService() {
|
||||||
return SwapService.this;
|
return SwapService.this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
startForeground(NOTIFICATION, createNotification());
|
startForeground(NOTIFICATION, createNotification());
|
||||||
@ -331,7 +354,7 @@ public class SwapService extends Service {
|
|||||||
new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
|
new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
wifiManager = ContextCompat.getSystemService(getApplicationContext(), WifiManager.class);
|
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
if (wifiManager != null) {
|
if (wifiManager != null) {
|
||||||
SwapService.putWifiEnabledBeforeSwap(wifiManager.isWifiEnabled());
|
SwapService.putWifiEnabledBeforeSwap(wifiManager.isWifiEnabled());
|
||||||
}
|
}
|
||||||
@ -347,58 +370,8 @@ public class SwapService extends Service {
|
|||||||
localBroadcastManager.registerReceiver(bonjourPeerRemoved, new IntentFilter(BonjourManager.ACTION_REMOVED));
|
localBroadcastManager.registerReceiver(bonjourPeerRemoved, new IntentFilter(BonjourManager.ACTION_REMOVED));
|
||||||
localBroadcastManager.registerReceiver(localRepoStatus, new IntentFilter(LocalRepoService.ACTION_STATUS));
|
localBroadcastManager.registerReceiver(localRepoStatus, new IntentFilter(LocalRepoService.ACTION_STATUS));
|
||||||
|
|
||||||
if (getHotspotActivatedUserPreference()) {
|
|
||||||
WifiApControl wifiApControl = WifiApControl.getInstance(this);
|
|
||||||
if (wifiApControl != null) {
|
|
||||||
wifiApControl.enable();
|
|
||||||
}
|
|
||||||
} else if (getWifiVisibleUserPreference()) {
|
|
||||||
if (wifiManager != null) {
|
|
||||||
wifiManager.setWifiEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BonjourManager.start(this);
|
BonjourManager.start(this);
|
||||||
BonjourManager.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference());
|
BonjourManager.setVisible(this, getWifiVisibleUserPreference());
|
||||||
}
|
|
||||||
|
|
||||||
private void askServerToSwapWithUs(final Repo repo) {
|
|
||||||
compositeDisposable.add(
|
|
||||||
Completable.fromAction(() -> {
|
|
||||||
String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
|
|
||||||
HttpURLConnection conn = null;
|
|
||||||
try {
|
|
||||||
URL url = new URL(repo.address.replace("/fdroid/repo", "/request-swap"));
|
|
||||||
conn = (HttpURLConnection) url.openConnection();
|
|
||||||
conn.setRequestMethod("POST");
|
|
||||||
conn.setDoInput(true);
|
|
||||||
conn.setDoOutput(true);
|
|
||||||
|
|
||||||
try (OutputStream outputStream = conn.getOutputStream();
|
|
||||||
OutputStreamWriter writer = new OutputStreamWriter(outputStream)) {
|
|
||||||
writer.write("repo=" + swapBackUri);
|
|
||||||
writer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
|
||||||
Utils.debugLog(TAG, "Asking server at " + repo.address + " to swap with us in return (by " +
|
|
||||||
"POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\"): " + responseCode);
|
|
||||||
} finally {
|
|
||||||
if (conn != null) {
|
|
||||||
conn.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnError(e -> {
|
|
||||||
Intent intent = new Intent(Downloader.ACTION_INTERRUPTED);
|
|
||||||
intent.setData(Uri.parse(repo.address));
|
|
||||||
intent.putExtra(Downloader.EXTRA_ERROR_MESSAGE, e.getLocalizedMessage());
|
|
||||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
|
|
||||||
})
|
|
||||||
.subscribe()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -409,9 +382,7 @@ public class SwapService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
deleteAllSwapRepos();
|
deleteAllSwapRepos();
|
||||||
Intent startUiIntent = new Intent(this, SwapWorkflowActivity.class);
|
startActivity(new Intent(this, SwapWorkflowActivity.class));
|
||||||
startUiIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
startActivity(startUiIntent);
|
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,8 +395,6 @@ public class SwapService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
compositeDisposable.dispose();
|
|
||||||
|
|
||||||
Utils.debugLog(TAG, "Destroying service, will disable swapping if required, and unregister listeners.");
|
Utils.debugLog(TAG, "Destroying service, will disable swapping if required, and unregister listeners.");
|
||||||
Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
|
Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
|
||||||
localBroadcastManager.unregisterReceiver(onWifiChange);
|
localBroadcastManager.unregisterReceiver(onWifiChange);
|
||||||
@ -434,9 +403,7 @@ public class SwapService extends Service {
|
|||||||
localBroadcastManager.unregisterReceiver(bonjourPeerFound);
|
localBroadcastManager.unregisterReceiver(bonjourPeerFound);
|
||||||
localBroadcastManager.unregisterReceiver(bonjourPeerRemoved);
|
localBroadcastManager.unregisterReceiver(bonjourPeerRemoved);
|
||||||
|
|
||||||
if (bluetoothAdapter != null) {
|
|
||||||
unregisterReceiver(bluetoothScanModeChanged);
|
unregisterReceiver(bluetoothScanModeChanged);
|
||||||
}
|
|
||||||
|
|
||||||
BluetoothManager.stop(this);
|
BluetoothManager.stop(this);
|
||||||
|
|
||||||
@ -446,21 +413,12 @@ public class SwapService extends Service {
|
|||||||
wifiManager.setWifiEnabled(false);
|
wifiManager.setWifiEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
WifiApControl ap = WifiApControl.getInstance(this);
|
|
||||||
if (ap != null) {
|
|
||||||
if (wasHotspotEnabledBeforeSwap()) {
|
|
||||||
ap.enable();
|
|
||||||
} else {
|
|
||||||
ap.disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stopPollingConnectedSwapRepo();
|
stopPollingConnectedSwapRepo();
|
||||||
|
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
}
|
}
|
||||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
|
stopForeground(true);
|
||||||
|
|
||||||
deleteAllSwapRepos();
|
deleteAllSwapRepos();
|
||||||
|
|
||||||
@ -471,7 +429,7 @@ public class SwapService extends Service {
|
|||||||
Intent intent = new Intent(this, SwapWorkflowActivity.class);
|
Intent intent = new Intent(this, SwapWorkflowActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
return new NotificationCompat.Builder(this, NotificationHelper.CHANNEL_SWAPS)
|
return new NotificationCompat.Builder(this)
|
||||||
.setContentTitle(getText(R.string.local_repo_running))
|
.setContentTitle(getText(R.string.local_repo_running))
|
||||||
.setContentText(getText(R.string.touch_to_configure_local_repo))
|
.setContentText(getText(R.string.touch_to_configure_local_repo))
|
||||||
.setSmallIcon(R.drawable.ic_nearby)
|
.setSmallIcon(R.drawable.ic_nearby)
|
||||||
@ -546,7 +504,7 @@ public class SwapService extends Service {
|
|||||||
if (hasIp) {
|
if (hasIp) {
|
||||||
LocalHTTPDManager.restart(this);
|
LocalHTTPDManager.restart(this);
|
||||||
BonjourManager.restart(this);
|
BonjourManager.restart(this);
|
||||||
BonjourManager.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference());
|
BonjourManager.setVisible(this, getWifiVisibleUserPreference());
|
||||||
} else {
|
} else {
|
||||||
BonjourManager.stop(this);
|
BonjourManager.stop(this);
|
||||||
LocalHTTPDManager.stop(this);
|
LocalHTTPDManager.stop(this);
|
@ -1,16 +1,14 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.support.annotation.ColorInt;
|
||||||
|
import android.support.annotation.LayoutRes;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.LayoutRes;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link android.view.View} that registers to handle the swap events from
|
* A {@link android.view.View} that registers to handle the swap events from
|
||||||
@ -35,19 +33,8 @@ public class SwapView extends RelativeLayout {
|
|||||||
this(context, attrs, 0);
|
this(context, attrs, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* In order to support Android < 21, this calls {@code super} rather than
|
|
||||||
* {@code this}. {@link RelativeLayout}'s methods just use a 0 for the
|
|
||||||
* fourth argument, just like this used to.
|
|
||||||
*/
|
|
||||||
public SwapView(Context context, AttributeSet attrs, int defStyleAttr) {
|
public SwapView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
this(context, attrs, defStyleAttr, 0);
|
||||||
final TypedArray a = context.obtainStyledAttributes(
|
|
||||||
attrs, R.styleable.SwapView, 0, 0);
|
|
||||||
toolbarColor = a.getColor(R.styleable.SwapView_toolbarColor,
|
|
||||||
ContextCompat.getColor(context, R.color.swap_blue));
|
|
||||||
toolbarTitle = a.getString(R.styleable.SwapView_toolbarTitle);
|
|
||||||
a.recycle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
@ -56,7 +43,7 @@ public class SwapView extends RelativeLayout {
|
|||||||
final TypedArray a = context.obtainStyledAttributes(
|
final TypedArray a = context.obtainStyledAttributes(
|
||||||
attrs, R.styleable.SwapView, 0, 0);
|
attrs, R.styleable.SwapView, 0, 0);
|
||||||
toolbarColor = a.getColor(R.styleable.SwapView_toolbarColor,
|
toolbarColor = a.getColor(R.styleable.SwapView_toolbarColor,
|
||||||
ContextCompat.getColor(context, R.color.swap_blue));
|
getResources().getColor(R.color.swap_blue));
|
||||||
toolbarTitle = a.getString(R.styleable.SwapView_toolbarTitle);
|
toolbarTitle = a.getString(R.styleable.SwapView_toolbarTitle);
|
||||||
a.recycle();
|
a.recycle();
|
||||||
}
|
}
|
@ -17,25 +17,22 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.support.v4.provider.DocumentFile;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.fdroid.fdroid.AddRepoIntentService;
|
import org.fdroid.fdroid.AddRepoIntentService;
|
||||||
import org.fdroid.fdroid.IndexUpdater;
|
import org.fdroid.fdroid.IndexUpdater;
|
||||||
import org.fdroid.fdroid.IndexV1Updater;
|
import org.fdroid.fdroid.IndexV1Updater;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
@ -44,13 +41,10 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import java.util.jar.JarInputStream;
|
import java.util.jar.JarInputStream;
|
||||||
|
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link IntentService} subclass for handling asynchronous scanning of a
|
* An {@link IntentService} subclass for handling asynchronous scanning of a
|
||||||
* removable storage device like an SD Card or USB OTG thumb drive using the
|
* removable storage device like an SD Card or USB OTG thumb drive using the
|
||||||
@ -63,11 +57,8 @@ import androidx.documentfile.provider.DocumentFile;
|
|||||||
* {@link android.os.Build.VERSION_CODES#KITKAT android-19}, this approach is only
|
* {@link android.os.Build.VERSION_CODES#KITKAT android-19}, this approach is only
|
||||||
* workable if {@link android.content.Intent#ACTION_OPEN_DOCUMENT_TREE} is available.
|
* workable if {@link android.content.Intent#ACTION_OPEN_DOCUMENT_TREE} is available.
|
||||||
* It was added in {@link android.os.Build.VERSION_CODES#LOLLIPOP android-21}.
|
* It was added in {@link android.os.Build.VERSION_CODES#LOLLIPOP android-21}.
|
||||||
* {@link android.os.storage.StorageVolume#createAccessIntent(String)} is also
|
|
||||||
* necessary to do this with any kind of rational UX.
|
|
||||||
*
|
*
|
||||||
* @see <a href="https://commonsware.com/blog/2017/11/15/storage-situation-removable-storage.html">The Storage Situation: Removable Storage </a>
|
* @see <a href="https://commonsware.com/blog/2017/11/15/storage-situation-removable-storage.html"> The Storage Situation: Removable Storage </a>
|
||||||
* @see <a href="https://commonsware.com/blog/2016/11/18/be-careful-scoped-directory-access.html">Be Careful with Scoped Directory Access</a>
|
|
||||||
* @see <a href="https://developer.android.com/training/articles/scoped-directory-access.html">Using Scoped Directory Access</a>
|
* @see <a href="https://developer.android.com/training/articles/scoped-directory-access.html">Using Scoped Directory Access</a>
|
||||||
* @see <a href="https://developer.android.com/guide/topics/providers/document-provider.html">Open Files using Storage Access Framework</a>
|
* @see <a href="https://developer.android.com/guide/topics/providers/document-provider.html">Open Files using Storage Access Framework</a>
|
||||||
*/
|
*/
|
||||||
@ -75,44 +66,19 @@ import androidx.documentfile.provider.DocumentFile;
|
|||||||
public class TreeUriScannerIntentService extends IntentService {
|
public class TreeUriScannerIntentService extends IntentService {
|
||||||
public static final String TAG = "TreeUriScannerIntentSer";
|
public static final String TAG = "TreeUriScannerIntentSer";
|
||||||
|
|
||||||
private static final String ACTION_SCAN_TREE_URI = "org.fdroid.fdroid.nearby.action.SCAN_TREE_URI";
|
private static final String ACTION_SCAN_TREE_URI = "org.fdroid.fdroid.localrepo.action.SCAN_TREE_URI";
|
||||||
/**
|
|
||||||
* @see <a href="https://android.googlesource.com/platform/frameworks/base/+/android-10.0.0_r38/core/java/android/provider/DocumentsContract.java#238">DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY</a>
|
|
||||||
* @see <a href="https://android.googlesource.com/platform/frameworks/base/+/android-10.0.0_r38/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java#70">ExternalStorageProvider.AUTHORITY</a>
|
|
||||||
*/
|
|
||||||
public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents";
|
|
||||||
|
|
||||||
public TreeUriScannerIntentService() {
|
public TreeUriScannerIntentService() {
|
||||||
super("TreeUriScannerIntentService");
|
super("TreeUriScannerIntentService");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void scan(Context context, Uri data) {
|
public static void scan(Context context, Uri data) {
|
||||||
|
if (Preferences.get().isScanRemovableStorageEnabled()) {
|
||||||
Intent intent = new Intent(context, TreeUriScannerIntentService.class);
|
Intent intent = new Intent(context, TreeUriScannerIntentService.class);
|
||||||
intent.setAction(ACTION_SCAN_TREE_URI);
|
intent.setAction(ACTION_SCAN_TREE_URI);
|
||||||
intent.setData(data);
|
intent.setData(data);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Now determine if it is External Storage that must be handled by the
|
|
||||||
* {@link TreeUriScannerIntentService} or whether it is External Storage
|
|
||||||
* like an SD Card that can be directly accessed via the file system.
|
|
||||||
*/
|
|
||||||
public static void onActivityResult(Context context, Intent intent) {
|
|
||||||
if (intent == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Uri uri = intent.getData();
|
|
||||||
if (uri != null) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 19) {
|
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
|
||||||
int perms = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|
||||||
contentResolver.takePersistableUriPermission(uri, perms);
|
|
||||||
}
|
|
||||||
String msg = String.format(context.getString(R.string.swap_toast_using_path), uri.toString());
|
|
||||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
|
||||||
scan(context, uri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -129,34 +95,20 @@ public class TreeUriScannerIntentService extends IntentService {
|
|||||||
searchDirectory(treeFile);
|
searchDirectory(treeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively search for {@link IndexV1Updater#SIGNED_FILE_NAME} starting
|
|
||||||
* from the given directory, looking at files first before recursing into
|
|
||||||
* directories. This is "depth last" since the index file is much more
|
|
||||||
* likely to be shallow than deep, and there can be a lot of files to
|
|
||||||
* search through starting at 4 or more levels deep, like the fdroid
|
|
||||||
* icons dirs and the per-app "external storage" dirs.
|
|
||||||
*/
|
|
||||||
private void searchDirectory(DocumentFile documentFileDir) {
|
private void searchDirectory(DocumentFile documentFileDir) {
|
||||||
DocumentFile[] documentFiles = documentFileDir.listFiles();
|
DocumentFile[] documentFiles = documentFileDir.listFiles();
|
||||||
if (documentFiles == null) {
|
if (documentFiles == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean foundIndex = false;
|
|
||||||
ArrayList<DocumentFile> dirs = new ArrayList<>();
|
|
||||||
for (DocumentFile documentFile : documentFiles) {
|
for (DocumentFile documentFile : documentFiles) {
|
||||||
if (documentFile.isDirectory()) {
|
if (documentFile.isDirectory()) {
|
||||||
dirs.add(documentFile);
|
searchDirectory(documentFile);
|
||||||
} else if (!foundIndex) {
|
} else {
|
||||||
if (IndexV1Updater.SIGNED_FILE_NAME.equals(documentFile.getName())) {
|
if (IndexV1Updater.SIGNED_FILE_NAME.equals(documentFile.getName())) {
|
||||||
registerRepo(documentFile);
|
registerRepo(documentFile);
|
||||||
foundIndex = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (DocumentFile dir : dirs) {
|
|
||||||
searchDirectory(dir);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,7 +123,9 @@ public class TreeUriScannerIntentService extends IntentService {
|
|||||||
private void registerRepo(DocumentFile index) {
|
private void registerRepo(DocumentFile index) {
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
|
Log.i(TAG, "FOUND: " + index.getUri());
|
||||||
inputStream = getContentResolver().openInputStream(index.getUri());
|
inputStream = getContentResolver().openInputStream(index.getUri());
|
||||||
|
Log.i(TAG, "repo URL: " + index.getParentFile().getUri());
|
||||||
registerRepo(this, inputStream, index.getParentFile().getUri());
|
registerRepo(this, inputStream, index.getParentFile().getUri());
|
||||||
} catch (IOException | IndexUpdater.SigningException e) {
|
} catch (IOException | IndexUpdater.SigningException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -191,6 +145,7 @@ public class TreeUriScannerIntentService extends IntentService {
|
|||||||
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexV1Updater.DATA_FILE_NAME);
|
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexV1Updater.DATA_FILE_NAME);
|
||||||
IOUtils.readLines(jarFile.getInputStream(indexEntry));
|
IOUtils.readLines(jarFile.getInputStream(indexEntry));
|
||||||
Certificate certificate = IndexUpdater.getSigningCertFromJar(indexEntry);
|
Certificate certificate = IndexUpdater.getSigningCertFromJar(indexEntry);
|
||||||
|
Log.i(TAG, "Got certificate: " + certificate);
|
||||||
String fingerprint = Utils.calcFingerprint(certificate);
|
String fingerprint = Utils.calcFingerprint(certificate);
|
||||||
Log.i(TAG, "Got fingerprint: " + fingerprint);
|
Log.i(TAG, "Got fingerprint: " + fingerprint);
|
||||||
destFile.delete();
|
destFile.delete();
|
@ -1,14 +1,12 @@
|
|||||||
package org.fdroid.fdroid.nearby.peers;
|
package org.fdroid.fdroid.localrepo.peers;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothClass.Device;
|
import android.bluetooth.BluetoothClass.Device;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public class BluetoothPeer implements Peer {
|
public class BluetoothPeer implements Peer {
|
||||||
|
|
||||||
private static final String BLUETOOTH_NAME_TAG = "FDroid:";
|
private static final String BLUETOOTH_NAME_TAG = "FDroid:";
|
||||||
@ -46,7 +44,7 @@ public class BluetoothPeer implements Peer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getIcon() {
|
public int getIcon() {
|
||||||
return R.drawable.ic_bluetooth;
|
return R.drawable.ic_bluetooth_white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -1,16 +1,14 @@
|
|||||||
package org.fdroid.fdroid.nearby.peers;
|
package org.fdroid.fdroid.localrepo.peers;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
|
||||||
import javax.jmdns.ServiceInfo;
|
import javax.jmdns.ServiceInfo;
|
||||||
import javax.jmdns.impl.FDroidServiceInfo;
|
import javax.jmdns.impl.FDroidServiceInfo;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public class BonjourPeer extends WifiPeer {
|
public class BonjourPeer extends WifiPeer {
|
||||||
private static final String TAG = "BonjourPeer";
|
private static final String TAG = "BonjourPeer";
|
||||||
|
|
@ -1,8 +1,7 @@
|
|||||||
package org.fdroid.fdroid.nearby.peers;
|
package org.fdroid.fdroid.localrepo.peers;
|
||||||
|
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO This model assumes that "peers" from Bluetooth, Bonjour, and WiFi are
|
* TODO This model assumes that "peers" from Bluetooth, Bonjour, and WiFi are
|
@ -1,9 +1,8 @@
|
|||||||
package org.fdroid.fdroid.nearby.peers;
|
package org.fdroid.fdroid.localrepo.peers;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ public class WifiPeer implements Peer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getIcon() {
|
public int getIcon() {
|
||||||
return R.drawable.ic_wifi;
|
return R.drawable.ic_network_wifi_white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -1,97 +0,0 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.storage.StorageManager;
|
|
||||||
import android.provider.DocumentsContract;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see <a href="https://stackoverflow.com/a/36162691">Android 5.0 DocumentFile from tree URI</a>
|
|
||||||
*/
|
|
||||||
public final class TreeUriUtils {
|
|
||||||
public static final String TAG = "TreeUriUtils";
|
|
||||||
|
|
||||||
private static final String PRIMARY_VOLUME_NAME = "primary";
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static String getFullPathFromTreeUri(Context context, @Nullable final Uri treeUri) {
|
|
||||||
if (treeUri == null) return null;
|
|
||||||
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), context);
|
|
||||||
if (volumePath == null) return File.separator;
|
|
||||||
if (volumePath.endsWith(File.separator))
|
|
||||||
volumePath = volumePath.substring(0, volumePath.length() - 1);
|
|
||||||
|
|
||||||
String documentPath = getDocumentPathFromTreeUri(treeUri);
|
|
||||||
if (documentPath.endsWith(File.separator))
|
|
||||||
documentPath = documentPath.substring(0, documentPath.length() - 1);
|
|
||||||
|
|
||||||
if (documentPath.length() > 0) {
|
|
||||||
if (documentPath.startsWith(File.separator))
|
|
||||||
return volumePath + documentPath;
|
|
||||||
else
|
|
||||||
return volumePath + File.separator + documentPath;
|
|
||||||
} else return volumePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("ObsoleteSdkInt")
|
|
||||||
private static String getVolumePath(final String volumeId, Context context) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
|
|
||||||
try {
|
|
||||||
StorageManager mStorageManager = ContextCompat.getSystemService(context, StorageManager.class);
|
|
||||||
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
|
|
||||||
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
|
|
||||||
Method getUuid = storageVolumeClazz.getMethod("getUuid");
|
|
||||||
Method getPath = storageVolumeClazz.getMethod("getPath");
|
|
||||||
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
|
|
||||||
Object result = getVolumeList.invoke(mStorageManager);
|
|
||||||
|
|
||||||
final int length = Array.getLength(result);
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
Object storageVolumeElement = Array.get(result, i);
|
|
||||||
String uuid = (String) getUuid.invoke(storageVolumeElement);
|
|
||||||
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
|
|
||||||
|
|
||||||
// primary volume?
|
|
||||||
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
|
|
||||||
return (String) getPath.invoke(storageVolumeElement);
|
|
||||||
|
|
||||||
// other volumes?
|
|
||||||
if (uuid != null && uuid.equals(volumeId))
|
|
||||||
return (String) getPath.invoke(storageVolumeElement);
|
|
||||||
}
|
|
||||||
// not found.
|
|
||||||
return null;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
|
|
||||||
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
|
|
||||||
final String[] split = docId.split(":");
|
|
||||||
if (split.length > 0) return split[0];
|
|
||||||
else return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
|
|
||||||
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
|
|
||||||
final String[] split = docId.split(":");
|
|
||||||
if ((split.length >= 2) && (split[1] != null)) return split[1];
|
|
||||||
else return File.separator;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018-2019 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.BroadcastReceiver;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.UriPermission;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.hardware.usb.UsbManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.views.main.NearbyViewBinder;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is just a shim to receive {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED}
|
|
||||||
* events.
|
|
||||||
*/
|
|
||||||
public class UsbDeviceAttachedReceiver extends BroadcastReceiver {
|
|
||||||
public static final String TAG = "UsbDeviceAttachedReceiv";
|
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(api = 19)
|
|
||||||
@Override
|
|
||||||
public void onReceive(final Context context, Intent intent) {
|
|
||||||
if (Build.VERSION.SDK_INT < 19) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent == null || TextUtils.isEmpty(intent.getAction())
|
|
||||||
|| !UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
|
|
||||||
Log.i(TAG, "ignoring irrelevant intent: " + intent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.i(TAG, "handling intent: " + intent);
|
|
||||||
|
|
||||||
final ContentResolver contentResolver = context.getContentResolver();
|
|
||||||
|
|
||||||
for (final UriPermission uriPermission : contentResolver.getPersistedUriPermissions()) {
|
|
||||||
Uri uri = uriPermission.getUri();
|
|
||||||
final ContentObserver contentObserver = new ContentObserver(new Handler()) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange, Uri uri) {
|
|
||||||
NearbyViewBinder.updateUsbOtg(context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
contentResolver.registerContentObserver(uri, true, contentObserver);
|
|
||||||
UsbDeviceDetachedReceiver.contentObservers.put(uri, contentObserver);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018-2019 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.BroadcastReceiver;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.hardware.usb.UsbManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.views.main.NearbyViewBinder;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is just a shim to receive {@link UsbManager#ACTION_USB_DEVICE_DETACHED}
|
|
||||||
* events.
|
|
||||||
*/
|
|
||||||
public class UsbDeviceDetachedReceiver extends BroadcastReceiver {
|
|
||||||
public static final String TAG = "UsbDeviceDetachedReceiv";
|
|
||||||
|
|
||||||
static final HashMap<Uri, ContentObserver> contentObservers = new HashMap<>();
|
|
||||||
|
|
||||||
@RequiresApi(api = 19)
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (Build.VERSION.SDK_INT < 19) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent == null || TextUtils.isEmpty(intent.getAction())
|
|
||||||
|| !UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
|
|
||||||
Log.i(TAG, "ignoring irrelevant intent: " + intent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.i(TAG, "handling intent: " + intent);
|
|
||||||
|
|
||||||
final ContentResolver contentResolver = context.getContentResolver();
|
|
||||||
NearbyViewBinder.updateUsbOtg(context);
|
|
||||||
for (ContentObserver contentObserver : contentObservers.values()) {
|
|
||||||
contentResolver.unregisterContentObserver(contentObserver);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Environment;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.views.main.NearbyViewBinder;
|
|
||||||
|
|
||||||
public class UsbDeviceMediaMountedReceiver extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (intent == null || intent.getAction() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String action = intent.getAction();
|
|
||||||
if (Environment.MEDIA_BAD_REMOVAL.equals(action)
|
|
||||||
|| Environment.MEDIA_MOUNTED.equals(action)
|
|
||||||
|| Environment.MEDIA_REMOVED.equals(action)
|
|
||||||
|| Environment.MEDIA_EJECTING.equals(action)) {
|
|
||||||
NearbyViewBinder.updateUsbOtg(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.net;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
@ -35,9 +35,13 @@ package org.fdroid.fdroid.nearby;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import fi.iki.elonen.NanoHTTPD;
|
||||||
|
import fi.iki.elonen.NanoHTTPD.Response.IStatus;
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
||||||
|
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLServerSocketFactory;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@ -60,11 +64,6 @@ import java.util.Map;
|
|||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import javax.net.ssl.SSLServerSocketFactory;
|
|
||||||
|
|
||||||
import fi.iki.elonen.NanoHTTPD;
|
|
||||||
import fi.iki.elonen.NanoHTTPD.Response.IStatus;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A HTTP server for serving the files that are being swapped via WiFi, etc.
|
* A HTTP server for serving the files that are being swapped via WiFi, etc.
|
||||||
* The only changes were to remove unneeded extras like {@code main()}, the
|
* The only changes were to remove unneeded extras like {@code main()}, the
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.net;
|
||||||
|
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@ -6,25 +6,22 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.DhcpInfo;
|
import android.net.DhcpInfo;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.wifi.WifiConfiguration;
|
|
||||||
import android.net.wifi.WifiInfo;
|
import android.net.wifi.WifiInfo;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
|
|
||||||
import org.apache.commons.net.util.SubnetUtils;
|
import org.apache.commons.net.util.SubnetUtils;
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.UpdateService;
|
import org.fdroid.fdroid.UpdateService;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoManager;
|
||||||
|
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@ -35,14 +32,11 @@ import java.security.cert.Certificate;
|
|||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import cc.mvdan.accesspoint.WifiApControl;
|
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle state changes to the device's wifi, storing the required bits.
|
* Handle state changes to the device's wifi, storing the required bits.
|
||||||
* The {@link Intent} that starts it either has no extras included,
|
* The {@link Intent} that starts it either has no extras included,
|
||||||
* which is how it can be triggered by code, or it came in from the system
|
* which is how it can be triggered by code, or it came in from the system
|
||||||
* via {@link WifiStateChangeReceiver}, in
|
* via {@link org.fdroid.fdroid.receiver.WifiStateChangeReceiver}, in
|
||||||
* which case an instance of {@link NetworkInfo} is included.
|
* which case an instance of {@link NetworkInfo} is included.
|
||||||
* <p>
|
* <p>
|
||||||
* The work is done in a {@link Thread} so that new incoming {@code Intents}
|
* The work is done in a {@link Thread} so that new incoming {@code Intents}
|
||||||
@ -72,8 +66,6 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
private static int previousWifiState = Integer.MIN_VALUE;
|
private static int previousWifiState = Integer.MIN_VALUE;
|
||||||
private static int wifiState;
|
private static int wifiState;
|
||||||
|
|
||||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
public WifiStateChangeService() {
|
public WifiStateChangeService() {
|
||||||
super("WifiStateChangeService");
|
super("WifiStateChangeService");
|
||||||
}
|
}
|
||||||
@ -86,12 +78,6 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
compositeDisposable.dispose();
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onHandleIntent(Intent intent) {
|
protected void onHandleIntent(Intent intent) {
|
||||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
|
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
|
||||||
@ -101,11 +87,10 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
}
|
}
|
||||||
Utils.debugLog(TAG, "WiFi change service started.");
|
Utils.debugLog(TAG, "WiFi change service started.");
|
||||||
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
|
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
|
||||||
wifiManager = ContextCompat.getSystemService(getApplicationContext(), WifiManager.class);
|
wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
|
||||||
wifiState = wifiManager.getWifiState();
|
wifiState = wifiManager.getWifiState();
|
||||||
|
if (ni == null || ni.isConnected()) {
|
||||||
Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState));
|
Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState));
|
||||||
if (ni == null
|
|
||||||
|| ni.getState() == NetworkInfo.State.CONNECTED || ni.getState() == NetworkInfo.State.DISCONNECTED) {
|
|
||||||
if (previousWifiState != wifiState &&
|
if (previousWifiState != wifiState &&
|
||||||
(wifiState == WifiManager.WIFI_STATE_ENABLED
|
(wifiState == WifiManager.WIFI_STATE_ENABLED
|
||||||
|| wifiState == WifiManager.WIFI_STATE_DISABLING // might be switching to hotspot
|
|| wifiState == WifiManager.WIFI_STATE_DISABLING // might be switching to hotspot
|
||||||
@ -119,12 +104,13 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 21 && wifiState == WifiManager.WIFI_STATE_ENABLED) {
|
if (Build.VERSION.SDK_INT < 21 && wifiState == WifiManager.WIFI_STATE_ENABLED) {
|
||||||
compositeDisposable.add(UpdateService.scheduleIfStillOnWifi(this).subscribe());
|
UpdateService.scheduleIfStillOnWifi(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class WifiInfoThread extends Thread {
|
public class WifiInfoThread extends Thread {
|
||||||
|
private static final String TAG = "WifiInfoThread";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -143,7 +129,7 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
|
if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
|
||||||
wifiInfo = wifiManager.getConnectionInfo();
|
wifiInfo = wifiManager.getConnectionInfo();
|
||||||
FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress());
|
FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress());
|
||||||
setSsid(wifiInfo);
|
setSsidFromWifiInfo(wifiInfo);
|
||||||
DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
|
DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
|
||||||
if (dhcpInfo != null) {
|
if (dhcpInfo != null) {
|
||||||
String netmask = formatIpAddress(dhcpInfo.netmask);
|
String netmask = formatIpAddress(dhcpInfo.netmask);
|
||||||
@ -161,13 +147,14 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
setIpInfoFromNetworkInterface();
|
setIpInfoFromNetworkInterface();
|
||||||
}
|
}
|
||||||
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|
||||||
|| wifiState == WifiManager.WIFI_STATE_DISABLING
|
|| wifiState == WifiManager.WIFI_STATE_DISABLING) {
|
||||||
|| wifiState == WifiManager.WIFI_STATE_UNKNOWN) {
|
|
||||||
// try once to see if its a hotspot
|
// try once to see if its a hotspot
|
||||||
setIpInfoFromNetworkInterface();
|
setIpInfoFromNetworkInterface();
|
||||||
if (FDroidApp.ipAddressString == null) {
|
if (FDroidApp.ipAddressString == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else { // a hotspot can be active during WIFI_STATE_UNKNOWN
|
||||||
|
setIpInfoFromNetworkInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retryCount > 120) {
|
if (retryCount > 120) {
|
||||||
@ -184,7 +171,7 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSsid(wifiInfo);
|
setSsidFromWifiInfo(wifiInfo);
|
||||||
|
|
||||||
String scheme;
|
String scheme;
|
||||||
if (Preferences.get().isLocalRepoHttpsEnabled()) {
|
if (Preferences.get().isLocalRepoHttpsEnabled()) {
|
||||||
@ -239,47 +226,16 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSsid(WifiInfo wifiInfo) {
|
private void setSsidFromWifiInfo(WifiInfo wifiInfo) {
|
||||||
if (wifiInfo != null && wifiInfo.getBSSID() != null) {
|
if (wifiInfo != null) {
|
||||||
String ssid = wifiInfo.getSSID();
|
String ssid = wifiInfo.getSSID();
|
||||||
Utils.debugLog(TAG, "Have wifi info, connected to " + ssid);
|
Utils.debugLog(TAG, "Have wifi info, connected to " + ssid);
|
||||||
if (ssid == null) {
|
if (ssid != null) {
|
||||||
FDroidApp.ssid = getString(R.string.swap_blank_wifi_ssid);
|
|
||||||
} else {
|
|
||||||
FDroidApp.ssid = ssid.replaceAll("^\"(.*)\"$", "$1");
|
FDroidApp.ssid = ssid.replaceAll("^\"(.*)\"$", "$1");
|
||||||
}
|
}
|
||||||
FDroidApp.bssid = wifiInfo.getBSSID();
|
String bssid = wifiInfo.getBSSID();
|
||||||
} else {
|
if (bssid != null) {
|
||||||
WifiApControl wifiApControl = null;
|
FDroidApp.bssid = bssid;
|
||||||
try {
|
|
||||||
wifiApControl = WifiApControl.getInstance(this);
|
|
||||||
wifiApControl.isEnabled();
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
wifiApControl = null;
|
|
||||||
}
|
|
||||||
Utils.debugLog(TAG, "WifiApControl: " + wifiApControl);
|
|
||||||
if (wifiApControl == null && FDroidApp.ipAddressString != null) {
|
|
||||||
wifiInfo = wifiManager.getConnectionInfo();
|
|
||||||
if (wifiInfo != null && wifiInfo.getBSSID() != null) {
|
|
||||||
setSsid(wifiInfo);
|
|
||||||
} else {
|
|
||||||
FDroidApp.ssid = getString(R.string.swap_active_hotspot, "");
|
|
||||||
}
|
|
||||||
} else if (wifiApControl != null && wifiApControl.isEnabled()) {
|
|
||||||
WifiConfiguration wifiConfiguration = wifiApControl.getConfiguration();
|
|
||||||
Utils.debugLog(TAG, "WifiConfiguration: " + wifiConfiguration);
|
|
||||||
if (wifiConfiguration == null) {
|
|
||||||
FDroidApp.ssid = getString(R.string.swap_active_hotspot, "");
|
|
||||||
FDroidApp.bssid = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wifiConfiguration.hiddenSSID) {
|
|
||||||
FDroidApp.ssid = getString(R.string.swap_hidden_wifi_ssid);
|
|
||||||
} else {
|
|
||||||
FDroidApp.ssid = wifiConfiguration.SSID;
|
|
||||||
}
|
|
||||||
FDroidApp.bssid = wifiConfiguration.BSSID;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,7 +259,7 @@ public class WifiStateChangeService extends IntentService {
|
|||||||
while (networkInterfaces.hasMoreElements()) {
|
while (networkInterfaces.hasMoreElements()) {
|
||||||
NetworkInterface netIf = networkInterfaces.nextElement();
|
NetworkInterface netIf = networkInterfaces.nextElement();
|
||||||
|
|
||||||
for (Enumeration<InetAddress> inetAddresses = netIf.getInetAddresses(); inetAddresses.hasMoreElements(); ) {
|
for (Enumeration<InetAddress> inetAddresses = netIf.getInetAddresses(); inetAddresses.hasMoreElements();) {
|
||||||
InetAddress inetAddress = inetAddresses.nextElement();
|
InetAddress inetAddress = inetAddresses.nextElement();
|
||||||
if (inetAddress.isLoopbackAddress() || inetAddress instanceof Inet6Address) {
|
if (inetAddress.isLoopbackAddress() || inetAddress instanceof Inet6Address) {
|
||||||
continue;
|
continue;
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.net.bluetooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
@ -1,14 +1,14 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.net.bluetooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothServerSocket;
|
import android.bluetooth.BluetoothServerSocket;
|
||||||
import android.bluetooth.BluetoothSocket;
|
import android.bluetooth.BluetoothSocket;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
import fi.iki.elonen.NanoHTTPD;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.nearby.httpish.Request;
|
import org.fdroid.fdroid.net.bluetooth.httpish.Request;
|
||||||
import org.fdroid.fdroid.nearby.httpish.Response;
|
import org.fdroid.fdroid.net.bluetooth.httpish.Response;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@ -20,8 +20,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import fi.iki.elonen.NanoHTTPD;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Act as a layer on top of LocalHTTPD server, by forwarding requests served
|
* Act as a layer on top of LocalHTTPD server, by forwarding requests served
|
||||||
* over bluetooth to that server.
|
* over bluetooth to that server.
|
@ -1,42 +0,0 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.preference.CheckBoxPreference;
|
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
|
|
||||||
public class DestructiveCheckBoxPreference extends CheckBoxPreference {
|
|
||||||
public DestructiveCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DestructiveCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DestructiveCheckBoxPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DestructiveCheckBoxPreference(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
|
||||||
super.onBindViewHolder(holder);
|
|
||||||
if (!holder.itemView.isEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (FDroidApp.isAppThemeLight()) {
|
|
||||||
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_light));
|
|
||||||
} else {
|
|
||||||
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_dark));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceViewHolder;
|
|
||||||
|
|
||||||
public class DestructivePreference extends Preference {
|
|
||||||
public DestructivePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DestructivePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DestructivePreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DestructivePreference(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
|
||||||
super.onBindViewHolder(holder);
|
|
||||||
if (FDroidApp.isAppThemeLight()) {
|
|
||||||
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_light));
|
|
||||||
} else {
|
|
||||||
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_dark));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
public class PanicPreferencesActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle) {
|
|
||||||
FDroidApp fdroidApp = (FDroidApp) getApplication();
|
|
||||||
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
|
|
||||||
|
|
||||||
super.onCreate(bundle);
|
|
||||||
setContentView(R.layout.activity_panic_settings);
|
|
||||||
|
|
||||||
MaterialToolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
// Handle navigation icon press
|
|
||||||
onBackPressed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.Preferences;
|
|
||||||
import org.fdroid.fdroid.data.Apk;
|
|
||||||
import org.fdroid.fdroid.data.DBHelper;
|
|
||||||
import org.fdroid.fdroid.data.InstalledApp;
|
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
|
||||||
import org.fdroid.fdroid.data.Repo;
|
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
|
||||||
import org.fdroid.fdroid.data.Schema;
|
|
||||||
import org.fdroid.fdroid.installer.Installer;
|
|
||||||
import org.fdroid.fdroid.installer.InstallerService;
|
|
||||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import info.guardianproject.panic.Panic;
|
|
||||||
import info.guardianproject.panic.PanicResponder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This {@link AppCompatActivity} is purely to run events in response to a panic trigger.
|
|
||||||
* It needs to be an {@code AppCompatActivity} rather than a {@link android.app.Service}
|
|
||||||
* so that it can fetch some of the required information about what sent the
|
|
||||||
* {@link Intent}. This is therefore an {@code AppCompatActivity} without any UI, which
|
|
||||||
* is a special case in Android. All the code must be in
|
|
||||||
* {@link #onCreate(Bundle)} and {@link #finish()} must be called at the end of
|
|
||||||
* that method.
|
|
||||||
*
|
|
||||||
* @see PanicResponder#receivedTriggerFromConnectedApp(AppCompatActivity)
|
|
||||||
*/
|
|
||||||
public class PanicResponderActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private static final String TAG = PanicResponderActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (!Panic.isTriggerIntent(intent)) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// received intent from panic app
|
|
||||||
Log.i(TAG, "Received Panic Trigger...");
|
|
||||||
|
|
||||||
final Preferences preferences = Preferences.get();
|
|
||||||
|
|
||||||
boolean receivedTriggerFromConnectedApp = PanicResponder.receivedTriggerFromConnectedApp(this);
|
|
||||||
final boolean runningAppUninstalls = PrivilegedInstaller.isDefault(this);
|
|
||||||
|
|
||||||
ArrayList<String> wipeList = new ArrayList<>(preferences.getPanicWipeSet());
|
|
||||||
preferences.setPanicWipeSet(Collections.<String>emptySet());
|
|
||||||
preferences.setPanicTmpSelectedSet(Collections.<String>emptySet());
|
|
||||||
|
|
||||||
if (receivedTriggerFromConnectedApp && runningAppUninstalls && wipeList.size() > 0) {
|
|
||||||
|
|
||||||
// if this app (e.g. F-Droid) is to be deleted, do it last
|
|
||||||
if (wipeList.contains(getPackageName())) {
|
|
||||||
wipeList.remove(getPackageName());
|
|
||||||
wipeList.add(getPackageName());
|
|
||||||
}
|
|
||||||
|
|
||||||
final Context context = this;
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
|
||||||
final String lastToUninstall = wipeList.get(wipeList.size() - 1);
|
|
||||||
final BroadcastReceiver receiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
switch ((intent.getAction())) {
|
|
||||||
case Installer.ACTION_UNINSTALL_INTERRUPTED:
|
|
||||||
case Installer.ACTION_UNINSTALL_COMPLETE:
|
|
||||||
latch.countDown();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lbm.registerReceiver(receiver, Installer.getUninstallIntentFilter(lastToUninstall));
|
|
||||||
|
|
||||||
for (String packageName : wipeList) {
|
|
||||||
InstalledApp installedApp = InstalledAppProvider.Helper.findByPackageName(context, packageName);
|
|
||||||
InstallerService.uninstall(context, new Apk(installedApp));
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for apps to uninstall before triggering final responses
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
latch.await(10, TimeUnit.MINUTES);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
lbm.unregisterReceiver(receiver);
|
|
||||||
if (preferences.panicResetRepos()) {
|
|
||||||
resetRepos(context);
|
|
||||||
}
|
|
||||||
if (preferences.panicHide()) {
|
|
||||||
HidingManager.hide(context);
|
|
||||||
}
|
|
||||||
if (preferences.panicExit()) {
|
|
||||||
exitAndClear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
} else if (receivedTriggerFromConnectedApp) {
|
|
||||||
if (preferences.panicResetRepos()) {
|
|
||||||
resetRepos(this);
|
|
||||||
}
|
|
||||||
// Performing destructive panic response
|
|
||||||
if (preferences.panicHide()) {
|
|
||||||
Log.i(TAG, "Hiding app...");
|
|
||||||
HidingManager.hide(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// exit and clear, if not deactivated
|
|
||||||
if (!runningAppUninstalls && preferences.panicExit()) {
|
|
||||||
exitAndClear();
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void resetRepos(Context context) {
|
|
||||||
HashSet<String> enabledAddresses = new HashSet<>();
|
|
||||||
HashSet<String> disabledAddresses = new HashSet<>();
|
|
||||||
String[] defaultReposItems = DBHelper.loadInitialRepos(context).toArray(new String[0]);
|
|
||||||
for (int i = 1; i < defaultReposItems.length; i += DBHelper.REPO_XML_ITEM_COUNT) {
|
|
||||||
if ("1".equals(defaultReposItems[i + 3])) {
|
|
||||||
enabledAddresses.add(defaultReposItems[i]);
|
|
||||||
} else {
|
|
||||||
disabledAddresses.add(defaultReposItems[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Repo> repos = RepoProvider.Helper.all(context);
|
|
||||||
for (Repo repo : repos) {
|
|
||||||
ContentValues values = new ContentValues(1);
|
|
||||||
if (enabledAddresses.contains(repo.address)) {
|
|
||||||
values.put(Schema.RepoTable.Cols.IN_USE, true);
|
|
||||||
RepoProvider.Helper.update(context, repo, values);
|
|
||||||
} else if (disabledAddresses.contains(repo.address)) {
|
|
||||||
values.put(Schema.RepoTable.Cols.IN_USE, false);
|
|
||||||
RepoProvider.Helper.update(context, repo, values);
|
|
||||||
} else {
|
|
||||||
RepoProvider.Helper.remove(context, repo.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void exitAndClear() {
|
|
||||||
ExitActivity.exitAndRemoveFromRecentApps(this);
|
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
|
||||||
finishAndRemoveTask();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.Preferences;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.views.installed.InstalledAppListAdapter;
|
|
||||||
import org.fdroid.fdroid.views.installed.InstalledAppListItemController;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
public class SelectInstalledAppListAdapter extends InstalledAppListAdapter {
|
|
||||||
private final Set<String> selectedApps;
|
|
||||||
|
|
||||||
SelectInstalledAppListAdapter(AppCompatActivity activity) {
|
|
||||||
super(activity);
|
|
||||||
Preferences prefs = Preferences.get();
|
|
||||||
selectedApps = prefs.getPanicWipeSet();
|
|
||||||
prefs.setPanicTmpSelectedSet(selectedApps);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public InstalledAppListItemController onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View view = activity.getLayoutInflater().inflate(R.layout.installed_app_list_item, parent, false);
|
|
||||||
return new SelectInstalledAppListItemController(activity, view, selectedApps);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.AppUpdateStatusManager;
|
|
||||||
import org.fdroid.fdroid.data.App;
|
|
||||||
import org.fdroid.fdroid.views.apps.AppListItemState;
|
|
||||||
import org.fdroid.fdroid.views.installed.InstalledAppListItemController;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the currently installed apps as a selectable list.
|
|
||||||
*/
|
|
||||||
public class SelectInstalledAppListItemController extends InstalledAppListItemController {
|
|
||||||
|
|
||||||
private final Set<String> selectedApps;
|
|
||||||
|
|
||||||
public SelectInstalledAppListItemController(AppCompatActivity activity, View itemView, Set<String> selectedApps) {
|
|
||||||
super(activity, itemView);
|
|
||||||
this.selectedApps = selectedApps;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected AppListItemState getCurrentViewState(
|
|
||||||
@NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
|
|
||||||
return new AppListItemState(app).setCheckBoxStatus(selectedApps.contains(app.packageName));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActionButtonPressed(App app) {
|
|
||||||
super.onActionButtonPressed(app);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
|
|
||||||
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
|
|
||||||
*
|
|
||||||
* 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.panic;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.Preferences;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
|
||||||
import org.fdroid.fdroid.views.installed.InstalledAppListAdapter;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.CursorLoader;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
public class SelectInstalledAppsActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
|
|
||||||
|
|
||||||
private InstalledAppListAdapter adapter;
|
|
||||||
private RecyclerView appList;
|
|
||||||
private TextView emptyState;
|
|
||||||
private int checkId;
|
|
||||||
|
|
||||||
private Preferences prefs;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
FDroidApp fdroidApp = (FDroidApp) getApplication();
|
|
||||||
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.installed_apps_layout);
|
|
||||||
|
|
||||||
MaterialToolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
toolbar.setTitle(getString(R.string.panic_add_apps_to_uninstall));
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
adapter = new SelectInstalledAppListAdapter(this);
|
|
||||||
|
|
||||||
appList = findViewById(R.id.app_list);
|
|
||||||
appList.setHasFixedSize(true);
|
|
||||||
appList.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
appList.setAdapter(adapter);
|
|
||||||
|
|
||||||
emptyState = findViewById(R.id.empty_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
prefs = Preferences.get();
|
|
||||||
|
|
||||||
// Starts a new or restarts an existing Loader in this manager
|
|
||||||
getSupportLoaderManager().restartLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new CursorLoader(this, InstalledAppProvider.getAllAppsUri(), null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
|
|
||||||
adapter.setApps(cursor);
|
|
||||||
|
|
||||||
if (adapter.getItemCount() == 0) {
|
|
||||||
appList.setVisibility(View.GONE);
|
|
||||||
emptyState.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
appList.setVisibility(View.VISIBLE);
|
|
||||||
emptyState.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
|
|
||||||
adapter.setApps(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
MenuItem menuItem = menu.add(R.string.menu_select_for_wipe);
|
|
||||||
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
|
||||||
checkId = menuItem.getItemId();
|
|
||||||
menuItem.setIcon(R.drawable.check);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
finish();
|
|
||||||
if (item.getItemId() == checkId) {
|
|
||||||
prefs.setPanicWipeSet(prefs.getPanicTmpSelectedSet());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,11 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.receiver;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
public class WifiStateChangeReceiver extends BroadcastReceiver {
|
public class WifiStateChangeReceiver extends BroadcastReceiver {
|
||||||
private static final String TAG = "WifiStateChangeReceiver";
|
private static final String TAG = "WifiStateChangeReceiver";
|
@ -1,21 +1,18 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
package org.fdroid.fdroid.views.hiding;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A very hacky calculator which is barely functional.
|
* A very hacky calculator which is barely functional.
|
||||||
* It is just meant to pass a very casual inspection.
|
* It is just meant to pass a very casual inspection.
|
||||||
@ -31,21 +28,17 @@ public class CalculatorActivity extends AppCompatActivity {
|
|||||||
// unary operators
|
// unary operators
|
||||||
private static final String PERCENT = "%";
|
private static final String PERCENT = "%";
|
||||||
|
|
||||||
@Nullable
|
private @Nullable String lastOp;
|
||||||
private String lastOp;
|
|
||||||
|
|
||||||
// views
|
// views
|
||||||
private TextView textView;
|
private TextView textView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
FDroidApp fdroidApp = (FDroidApp) getApplication();
|
|
||||||
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_calculator);
|
setContentView(R.layout.activity_calculator);
|
||||||
|
|
||||||
MaterialToolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
textView = (TextView) findViewById(R.id.textView);
|
textView = (TextView) findViewById(R.id.textView);
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
package org.fdroid.fdroid.views.hiding;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -7,14 +7,12 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
|
import android.support.v4.app.NotificationManagerCompat;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.views.main.MainActivity;
|
import org.fdroid.fdroid.views.main.MainActivity;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is encapsulating all methods related to hiding the app from the launcher
|
* This class is encapsulating all methods related to hiding the app from the launcher
|
||||||
* and restoring it.
|
* and restoring it.
|
@ -3,44 +3,40 @@ package org.fdroid.fdroid.views.main;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.UpdateService;
|
import org.fdroid.fdroid.UpdateService;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.data.CategoryProvider;
|
import org.fdroid.fdroid.data.CategoryProvider;
|
||||||
import org.fdroid.fdroid.data.Schema;
|
import org.fdroid.fdroid.data.Schema;
|
||||||
import org.fdroid.fdroid.panic.HidingManager;
|
|
||||||
import org.fdroid.fdroid.views.apps.AppListActivity;
|
import org.fdroid.fdroid.views.apps.AppListActivity;
|
||||||
import org.fdroid.fdroid.views.categories.CategoryAdapter;
|
import org.fdroid.fdroid.views.categories.CategoryAdapter;
|
||||||
import org.fdroid.fdroid.views.categories.CategoryController;
|
import org.fdroid.fdroid.views.categories.CategoryController;
|
||||||
|
import org.fdroid.fdroid.views.hiding.HidingManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.CursorLoader;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for ensuring that the categories view is inflated and then populated correctly.
|
* Responsible for ensuring that the categories view is inflated and then populated correctly.
|
||||||
* Will start a loader to get the list of categories from the database and populate a recycler
|
* Will start a loader to get the list of categories from the database and populate a recycler
|
||||||
* view with relevant info about each.
|
* view with relevant info about each.
|
||||||
*/
|
*/
|
||||||
class CategoriesViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
|
class CategoriesViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
public static final String TAG = "CategoriesViewBinder";
|
|
||||||
|
|
||||||
private static final int LOADER_ID = 429820532;
|
private static final int LOADER_ID = 429820532;
|
||||||
|
|
||||||
@ -96,11 +92,10 @@ class CategoriesViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
activity.getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
activity.getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
if (id != LOADER_ID) {
|
if (id != LOADER_ID) {
|
||||||
throw new IllegalArgumentException("id != LOADER_ID");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CursorLoader(
|
return new CursorLoader(
|
||||||
@ -115,7 +110,7 @@ class CategoriesViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads all categories from the cursor and stores them in memory to provide to the {@link CategoryAdapter}.
|
* Reads all categories from the cursor and stores them in memory to provide to the {@link CategoryAdapter}.
|
||||||
* <p>
|
*
|
||||||
* It does this so it is easier to deal with localized/unlocalized categories without having
|
* It does this so it is easier to deal with localized/unlocalized categories without having
|
||||||
* to store the localized version in the database. It is not expected that the list of categories
|
* to store the localized version in the database. It is not expected that the list of categories
|
||||||
* will grow so large as to make this a performance concern. If it does in the future, the
|
* will grow so large as to make this a performance concern. If it does in the future, the
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
package org.fdroid.fdroid.views.main;
|
package org.fdroid.fdroid.views.main;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.views.PreferencesFragment;
|
import org.fdroid.fdroid.views.PreferencesFragment;
|
||||||
import org.fdroid.fdroid.views.updates.UpdatesViewBinder;
|
import org.fdroid.fdroid.views.updates.UpdatesViewBinder;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decides which view on the main screen to attach to a given {@link FrameLayout}. This class
|
* 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
|
* doesn't know which view it will be rendering at the time it is constructed. Rather, at some
|
||||||
@ -32,10 +30,10 @@ class MainViewController extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see LatestViewBinder
|
* @see WhatsNewViewBinder
|
||||||
*/
|
*/
|
||||||
public void bindLatestView() {
|
public void bindWhatsNewView() {
|
||||||
new LatestViewBinder(activity, frame);
|
new WhatsNewViewBinder(activity, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
package org.fdroid.fdroid.views.main;
|
package org.fdroid.fdroid.views.main;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.Context;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.UriPermission;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.hardware.usb.UsbDevice;
|
|
||||||
import android.hardware.usb.UsbManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.storage.StorageManager;
|
import android.support.annotation.RequiresApi;
|
||||||
import android.os.storage.StorageVolume;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.provider.DocumentsContract;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
@ -21,20 +16,12 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.localrepo.SDCardScannerService;
|
||||||
import org.fdroid.fdroid.nearby.SDCardScannerService;
|
import org.fdroid.fdroid.localrepo.SwapService;
|
||||||
import org.fdroid.fdroid.nearby.SwapService;
|
import org.fdroid.fdroid.localrepo.TreeUriScannerIntentService;
|
||||||
import org.fdroid.fdroid.nearby.TreeUriScannerIntentService;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A splash screen encouraging people to start the swap process. The swap
|
* A splash screen encouraging people to start the swap process. The swap
|
||||||
@ -61,18 +48,15 @@ import androidx.core.content.ContextCompat;
|
|||||||
* write access to the the removable storage.
|
* write access to the the removable storage.
|
||||||
*
|
*
|
||||||
* @see TreeUriScannerIntentService
|
* @see TreeUriScannerIntentService
|
||||||
* @see org.fdroid.fdroid.nearby.SDCardScannerService
|
* @see org.fdroid.fdroid.localrepo.SDCardScannerService
|
||||||
* <p>
|
|
||||||
* TODO use {@link StorageManager#registerStorageVolumeCallback(Executor, StorageManager.StorageVolumeCallback)}
|
|
||||||
*/
|
*/
|
||||||
public class NearbyViewBinder {
|
class NearbyViewBinder {
|
||||||
public static final String TAG = "NearbyViewBinder";
|
public static final String TAG = "NearbyViewBinder";
|
||||||
|
|
||||||
private static File externalStorage = null;
|
static File externalStorage = null;
|
||||||
private static View swapView;
|
|
||||||
|
|
||||||
NearbyViewBinder(final AppCompatActivity activity, FrameLayout parent) {
|
NearbyViewBinder(final Activity activity, FrameLayout parent) {
|
||||||
swapView = activity.getLayoutInflater().inflate(R.layout.main_tab_swap, parent, true);
|
View swapView = activity.getLayoutInflater().inflate(R.layout.main_tab_swap, parent, true);
|
||||||
|
|
||||||
TextView subtext = swapView.findViewById(R.id.both_parties_need_fdroid_text);
|
TextView subtext = swapView.findViewById(R.id.both_parties_need_fdroid_text);
|
||||||
subtext.setText(activity.getString(R.string.nearby_splash__both_parties_need_fdroid,
|
subtext.setText(activity.getString(R.string.nearby_splash__both_parties_need_fdroid,
|
||||||
@ -91,7 +75,7 @@ public class NearbyViewBinder {
|
|||||||
ActivityCompat.requestPermissions(activity, new String[]{coarseLocation},
|
ActivityCompat.requestPermissions(activity, new String[]{coarseLocation},
|
||||||
MainActivity.REQUEST_LOCATION_PERMISSIONS);
|
MainActivity.REQUEST_LOCATION_PERMISSIONS);
|
||||||
} else {
|
} else {
|
||||||
ContextCompat.startForegroundService(activity, new Intent(activity, SwapService.class));
|
SwapService.start(activity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -147,93 +131,5 @@ public class NearbyViewBinder {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUsbOtg(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void updateUsbOtg(final Context context) {
|
|
||||||
if (Build.VERSION.SDK_INT < 24) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (swapView == null) {
|
|
||||||
Utils.debugLog(TAG, "swapView == null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TextView storageVolumeText = swapView.findViewById(R.id.storage_volume_text);
|
|
||||||
Button requestStorageVolume = swapView.findViewById(R.id.request_storage_volume_button);
|
|
||||||
storageVolumeText.setVisibility(View.GONE);
|
|
||||||
requestStorageVolume.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
final StorageManager storageManager = ContextCompat.getSystemService(context, StorageManager.class);
|
|
||||||
for (final StorageVolume storageVolume : storageManager.getStorageVolumes()) {
|
|
||||||
if (storageVolume.isRemovable() && !storageVolume.isPrimary()) {
|
|
||||||
Log.i(TAG, "StorageVolume: " + storageVolume);
|
|
||||||
Intent tmpIntent = null;
|
|
||||||
if (Build.VERSION.SDK_INT < 29) {
|
|
||||||
tmpIntent = storageVolume.createAccessIntent(null);
|
|
||||||
} else {
|
|
||||||
tmpIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
|
||||||
tmpIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
|
|
||||||
Uri.parse("content://"
|
|
||||||
+ TreeUriScannerIntentService.EXTERNAL_STORAGE_PROVIDER_AUTHORITY
|
|
||||||
+ "/tree/"
|
|
||||||
+ storageVolume.getUuid()
|
|
||||||
+ "%3A/document/"
|
|
||||||
+ storageVolume.getUuid()
|
|
||||||
+ "%3A"));
|
|
||||||
}
|
|
||||||
if (tmpIntent == null) {
|
|
||||||
Utils.debugLog(TAG, "Got null Storage Volume access Intent");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Intent intent = tmpIntent;
|
|
||||||
|
|
||||||
storageVolumeText.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
String text = storageVolume.getDescription(context);
|
|
||||||
if (!TextUtils.isEmpty(text)) {
|
|
||||||
requestStorageVolume.setText(text);
|
|
||||||
UsbDevice usb = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
|
||||||
if (usb != null) {
|
|
||||||
text = String.format("%s (%s %s)", text, usb.getManufacturerName(), usb.getProductName());
|
|
||||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestStorageVolume.setVisibility(View.VISIBLE);
|
|
||||||
requestStorageVolume.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
@RequiresApi(api = 24)
|
|
||||||
public void onClick(View v) {
|
|
||||||
List<UriPermission> list = context.getContentResolver().getPersistedUriPermissions();
|
|
||||||
if (list != null) for (UriPermission uriPermission : list) {
|
|
||||||
Uri uri = uriPermission.getUri();
|
|
||||||
if (uri.getPath().equals(String.format("/tree/%s:", storageVolume.getUuid()))) {
|
|
||||||
intent.setData(uri);
|
|
||||||
TreeUriScannerIntentService.onActivityResult(context, intent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppCompatActivity activity = null;
|
|
||||||
if (context instanceof AppCompatActivity) {
|
|
||||||
activity = (AppCompatActivity) context;
|
|
||||||
} else if (swapView != null && swapView.getContext() instanceof AppCompatActivity) {
|
|
||||||
activity = (AppCompatActivity) swapView.getContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity != null) {
|
|
||||||
activity.startActivityForResult(intent, MainActivity.REQUEST_STORAGE_ACCESS);
|
|
||||||
} else {
|
|
||||||
// scan in the background without requesting permissions
|
|
||||||
Toast.makeText(context.getApplicationContext(),
|
|
||||||
context.getString(R.string.scan_removable_storage_toast, externalStorage),
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
SDCardScannerService.scan(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
package org.fdroid.fdroid.views.panic;
|
||||||
|
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
public class ExitActivity extends AppCompatActivity {
|
public class ExitActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -0,0 +1,38 @@
|
|||||||
|
package org.fdroid.fdroid.views.panic;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
|
||||||
|
public class PanicPreferencesActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle) {
|
||||||
|
((FDroidApp) getApplication()).applyTheme(this);
|
||||||
|
super.onCreate(bundle);
|
||||||
|
setContentView(R.layout.activity_panic_settings);
|
||||||
|
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
ActionBar ab = getSupportActionBar();
|
||||||
|
if (ab != null) {
|
||||||
|
ab.setDisplayShowHomeEnabled(true);
|
||||||
|
ab.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == android.R.id.home) {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package org.fdroid.fdroid.panic;
|
package org.fdroid.fdroid.views.panic;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -7,57 +8,49 @@ import android.content.SharedPreferences;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.LightingColorFilter;
|
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.ColorInt;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v14.preference.PreferenceFragment;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.preference.CheckBoxPreference;
|
||||||
|
import android.support.v7.preference.ListPreference;
|
||||||
|
import android.support.v7.preference.Preference;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Preferences;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.preference.CheckBoxPreference;
|
|
||||||
import androidx.preference.ListPreference;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceCategory;
|
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
|
||||||
import info.guardianproject.panic.Panic;
|
import info.guardianproject.panic.Panic;
|
||||||
import info.guardianproject.panic.PanicResponder;
|
import info.guardianproject.panic.PanicResponder;
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.R;
|
||||||
|
import org.fdroid.fdroid.views.hiding.HidingManager;
|
||||||
|
|
||||||
public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class PanicPreferencesFragment extends PreferenceFragment
|
||||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private static final String PREF_EXIT = Preferences.PREF_PANIC_EXIT;
|
||||||
private static final String PREF_APP = "pref_panic_app";
|
private static final String PREF_APP = "pref_panic_app";
|
||||||
|
private static final String PREF_HIDE = Preferences.PREF_PANIC_HIDE;
|
||||||
|
|
||||||
private PackageManager pm;
|
private PackageManager pm;
|
||||||
private ListPreference prefApp;
|
private ListPreference prefApp;
|
||||||
private CheckBoxPreference prefExit;
|
private CheckBoxPreference prefExit;
|
||||||
private CheckBoxPreference prefHide;
|
private CheckBoxPreference prefHide;
|
||||||
private CheckBoxPreference prefResetRepos;
|
|
||||||
private PreferenceCategory categoryAppsToUninstall;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle bundle, String s) {
|
public void onCreatePreferences(Bundle bundle, String s) {
|
||||||
addPreferencesFromResource(R.xml.preferences_panic);
|
addPreferencesFromResource(R.xml.preferences_panic);
|
||||||
|
|
||||||
pm = getActivity().getPackageManager();
|
pm = getActivity().getPackageManager();
|
||||||
prefExit = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_EXIT);
|
prefExit = (CheckBoxPreference) findPreference(PREF_EXIT);
|
||||||
prefApp = (ListPreference) findPreference(PREF_APP);
|
prefApp = (ListPreference) findPreference(PREF_APP);
|
||||||
prefHide = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_HIDE);
|
prefHide = (CheckBoxPreference) findPreference(PREF_HIDE);
|
||||||
prefHide.setTitle(getString(R.string.panic_hide_title, getString(R.string.app_name)));
|
prefHide.setTitle(getString(R.string.panic_hide_title, getString(R.string.app_name)));
|
||||||
prefResetRepos = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_RESET_REPOS);
|
|
||||||
categoryAppsToUninstall = (PreferenceCategory) findPreference("pref_panic_apps_to_uninstall");
|
|
||||||
|
|
||||||
if (PanicResponder.checkForDisconnectIntent(getActivity())) {
|
if (PanicResponder.checkForDisconnectIntent(getActivity())) {
|
||||||
// the necessary action should have been performed by the check already
|
// the necessary action should have been performed by the check already
|
||||||
@ -80,12 +73,9 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
|||||||
if (packageName.equals(Panic.PACKAGE_NAME_NONE)) {
|
if (packageName.equals(Panic.PACKAGE_NAME_NONE)) {
|
||||||
prefHide.setChecked(false);
|
prefHide.setChecked(false);
|
||||||
prefHide.setEnabled(false);
|
prefHide.setEnabled(false);
|
||||||
prefResetRepos.setChecked(false);
|
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||||
prefResetRepos.setEnabled(false);
|
|
||||||
getActivity().setResult(AppCompatActivity.RESULT_CANCELED);
|
|
||||||
} else {
|
} else {
|
||||||
prefHide.setEnabled(true);
|
prefHide.setEnabled(true);
|
||||||
prefResetRepos.setEnabled(true);
|
|
||||||
}
|
}
|
||||||
showPanicApp(packageName);
|
showPanicApp(packageName);
|
||||||
return true;
|
return true;
|
||||||
@ -98,48 +88,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||||
|
|
||||||
if (!PrivilegedInstaller.isDefault(getActivity())) {
|
|
||||||
getPreferenceScreen().removePreference(categoryAppsToUninstall);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showWipeList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showWipeList() {
|
|
||||||
Intent intent = new Intent(getActivity(), SelectInstalledAppsActivity.class);
|
|
||||||
intent.setAction(Intent.ACTION_MAIN);
|
|
||||||
Set<String> wipeSet = Preferences.get().getPanicWipeSet();
|
|
||||||
categoryAppsToUninstall.removeAll();
|
|
||||||
if (Panic.PACKAGE_NAME_NONE.equals(prefApp.getValue())) {
|
|
||||||
categoryAppsToUninstall.setEnabled(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
categoryAppsToUninstall.setEnabled(true);
|
|
||||||
if (wipeSet.size() > 0) {
|
|
||||||
for (String packageName : wipeSet) {
|
|
||||||
Preference preference = new DestructivePreference(getActivity());
|
|
||||||
preference.setSingleLineTitle(true);
|
|
||||||
preference.setIntent(intent);
|
|
||||||
categoryAppsToUninstall.addPreference(preference);
|
|
||||||
try {
|
|
||||||
preference.setTitle(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
|
|
||||||
preference.setIcon(pm.getApplicationIcon(packageName));
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
preference.setTitle(packageName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Preference preference = new Preference(requireActivity());
|
|
||||||
preference.setIntent(intent);
|
|
||||||
Drawable icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_add_circle_outline);
|
|
||||||
icon.setColorFilter(new LightingColorFilter(0, getResources().getColor(R.color.swap_light_grey_icon)));
|
|
||||||
preference.setSingleLineTitle(true);
|
|
||||||
preference.setTitle(R.string.panic_add_apps_to_uninstall);
|
|
||||||
preference.setIcon(icon);
|
|
||||||
categoryAppsToUninstall.addPreference(preference);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -150,13 +98,11 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (key.equals(Preferences.PREF_PANIC_HIDE)
|
if (key.equals(PREF_HIDE) && sharedPreferences.getBoolean(PREF_HIDE, false)) {
|
||||||
&& sharedPreferences.getBoolean(Preferences.PREF_PANIC_HIDE, false)) {
|
|
||||||
showHideConfirmationDialog();
|
showHideConfirmationDialog();
|
||||||
}
|
}
|
||||||
// disable "hiding" if "exit" gets disabled
|
// disable "hiding" if "exit" gets disabled
|
||||||
if (key.equals(Preferences.PREF_PANIC_EXIT)
|
if (key.equals(PREF_EXIT) && !sharedPreferences.getBoolean(PREF_EXIT, true)) {
|
||||||
&& !sharedPreferences.getBoolean(Preferences.PREF_PANIC_EXIT, true)) {
|
|
||||||
prefHide.setChecked(false);
|
prefHide.setChecked(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,7 +156,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
// disable destructive panic actions
|
// disable destructive panic actions
|
||||||
prefHide.setEnabled(false);
|
prefHide.setEnabled(false);
|
||||||
showWipeList();
|
|
||||||
} else {
|
} else {
|
||||||
// try to display connected panic app
|
// try to display connected panic app
|
||||||
try {
|
try {
|
||||||
@ -218,8 +163,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
|||||||
prefApp.setSummary(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
|
prefApp.setSummary(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
|
||||||
prefApp.setIcon(pm.getApplicationIcon(packageName));
|
prefApp.setIcon(pm.getApplicationIcon(packageName));
|
||||||
prefHide.setEnabled(true);
|
prefHide.setEnabled(true);
|
||||||
prefResetRepos.setEnabled(true);
|
|
||||||
showWipeList();
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
// revert back to no app, just to be safe
|
// revert back to no app, just to be safe
|
||||||
PanicResponder.setTriggerPackageName(getActivity(), Panic.PACKAGE_NAME_NONE);
|
PanicResponder.setTriggerPackageName(getActivity(), Panic.PACKAGE_NAME_NONE);
|
||||||
@ -234,13 +177,13 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
|||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
PanicResponder.setTriggerPackageName(getActivity());
|
PanicResponder.setTriggerPackageName(getActivity());
|
||||||
showPanicApp(PanicResponder.getTriggerPackageName(getActivity()));
|
showPanicApp(PanicResponder.getTriggerPackageName(getActivity()));
|
||||||
getActivity().setResult(AppCompatActivity.RESULT_OK);
|
getActivity().setResult(Activity.RESULT_OK);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() {
|
DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
getActivity().setResult(AppCompatActivity.RESULT_CANCELED);
|
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -300,7 +243,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
|
|||||||
@Override
|
@Override
|
||||||
public void onCancel(DialogInterface dialogInterface) {
|
public void onCancel(DialogInterface dialogInterface) {
|
||||||
prefHide.setChecked(false);
|
prefHide.setChecked(false);
|
||||||
prefResetRepos.setChecked(false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.setView(R.layout.dialog_app_hiding);
|
builder.setView(R.layout.dialog_app_hiding);
|
@ -0,0 +1,54 @@
|
|||||||
|
package org.fdroid.fdroid.views.panic;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.views.hiding.HidingManager;
|
||||||
|
|
||||||
|
import info.guardianproject.panic.Panic;
|
||||||
|
import info.guardianproject.panic.PanicResponder;
|
||||||
|
|
||||||
|
public class PanicResponderActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final String TAG = PanicResponderActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent == null || !Panic.isTriggerIntent(intent)) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// received intent from panic app
|
||||||
|
Log.i(TAG, "Received Panic Trigger...");
|
||||||
|
|
||||||
|
Preferences preferences = Preferences.get();
|
||||||
|
|
||||||
|
if (PanicResponder.receivedTriggerFromConnectedApp(this)) {
|
||||||
|
Log.i(TAG, "Panic Trigger came from connected app");
|
||||||
|
|
||||||
|
// Performing destructive panic responses
|
||||||
|
if (preferences.panicHide()) {
|
||||||
|
Log.i(TAG, "Hiding app...");
|
||||||
|
HidingManager.hide(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit and clear, if not deactivated
|
||||||
|
if (preferences.panicExit()) {
|
||||||
|
ExitActivity.exitAndRemoveFromRecentApps(this);
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
finishAndRemoveTask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,12 +1,19 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
@ -19,18 +26,12 @@ import android.widget.CompoundButton;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||||
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
|
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
||||||
import androidx.annotation.NonNull;
|
import org.fdroid.fdroid.localrepo.SwapService;
|
||||||
import androidx.annotation.Nullable;
|
import org.fdroid.fdroid.localrepo.SwapView;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.cursoradapter.widget.CursorAdapter;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.CursorLoader;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
|
|
||||||
public class SelectAppsView extends SwapView implements LoaderManager.LoaderCallbacks<Cursor> {
|
public class SelectAppsView extends SwapView implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
|
|||||||
listView = findViewById(R.id.list);
|
listView = findViewById(R.id.list);
|
||||||
adapter = new AppListAdapter(listView, getContext(),
|
adapter = new AppListAdapter(listView, getContext(),
|
||||||
getContext().getContentResolver().query(InstalledAppProvider.getContentUri(),
|
getContext().getContentResolver().query(InstalledAppProvider.getContentUri(),
|
||||||
null, null, null, null));
|
InstalledAppTable.Cols.ALL, null, null, null));
|
||||||
|
|
||||||
listView.setAdapter(adapter);
|
listView.setAdapter(adapter);
|
||||||
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||||
@ -96,7 +97,13 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
|
|||||||
} else {
|
} else {
|
||||||
uri = InstalledAppProvider.getSearchUri(currentFilterString);
|
uri = InstalledAppProvider.getSearchUri(currentFilterString);
|
||||||
}
|
}
|
||||||
return new CursorLoader(getActivity(), uri, null, null, null, null);
|
return new CursorLoader(
|
||||||
|
getActivity(),
|
||||||
|
uri,
|
||||||
|
InstalledAppTable.Cols.ALL,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
InstalledAppTable.Cols.APPLICATION_LABEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -140,14 +147,14 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
|
|||||||
private LayoutInflater getInflater(Context context) {
|
private LayoutInflater getInflater(Context context) {
|
||||||
if (inflater == null) {
|
if (inflater == null) {
|
||||||
Context themedContext = new ContextThemeWrapper(context, R.style.SwapTheme_AppList_ListItem);
|
Context themedContext = new ContextThemeWrapper(context, R.style.SwapTheme_AppList_ListItem);
|
||||||
inflater = ContextCompat.getSystemService(themedContext, LayoutInflater.class);
|
inflater = (LayoutInflater) themedContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
}
|
}
|
||||||
return inflater;
|
return inflater;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getDefaultAppIcon(Context context) {
|
private Drawable getDefaultAppIcon(Context context) {
|
||||||
if (defaultAppIcon == null) {
|
if (defaultAppIcon == null) {
|
||||||
defaultAppIcon = ContextCompat.getDrawable(context, android.R.drawable.sym_def_app_icon);
|
defaultAppIcon = context.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
|
||||||
}
|
}
|
||||||
return defaultAppIcon;
|
return defaultAppIcon;
|
||||||
}
|
}
|
||||||
@ -199,6 +206,8 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCheckedIndicatorView(view, listView.isItemChecked(listPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateCheckedIndicatorView(int position, boolean checked) {
|
public void updateCheckedIndicatorView(int position, boolean checked) {
|
||||||
@ -207,7 +216,26 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
|
|||||||
|
|
||||||
if (position >= firstListItemPosition && position <= lastListItemPosition) {
|
if (position >= firstListItemPosition && position <= lastListItemPosition) {
|
||||||
final int childIndex = position - firstListItemPosition;
|
final int childIndex = position - firstListItemPosition;
|
||||||
|
updateCheckedIndicatorView(listView.getChildAt(childIndex), checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCheckedIndicatorView(View view, boolean checked) {
|
||||||
|
ImageView imageView = (ImageView) view.findViewById(R.id.checked);
|
||||||
|
if (imageView != null) {
|
||||||
|
int resource;
|
||||||
|
int colour;
|
||||||
|
if (checked) {
|
||||||
|
resource = R.drawable.ic_check_circle_white;
|
||||||
|
colour = getResources().getColor(R.color.swap_bright_blue);
|
||||||
|
} else {
|
||||||
|
resource = R.drawable.ic_add_circle_outline_white;
|
||||||
|
colour = 0xFFD0D0D4;
|
||||||
|
}
|
||||||
|
imageView.setImageDrawable(getResources().getDrawable(resource));
|
||||||
|
imageView.setColorFilter(colour, PorterDuff.Mode.MULTIPLY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
@ -7,6 +7,9 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.wifi.WifiConfiguration;
|
import android.net.wifi.WifiConfiguration;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.support.v7.widget.SwitchCompat;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -19,20 +22,18 @@ import android.widget.ImageView;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import cc.mvdan.accesspoint.WifiApControl;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.nearby.peers.Peer;
|
import org.fdroid.fdroid.localrepo.BluetoothManager;
|
||||||
|
import org.fdroid.fdroid.localrepo.SwapService;
|
||||||
|
import org.fdroid.fdroid.localrepo.SwapView;
|
||||||
|
import org.fdroid.fdroid.localrepo.peers.Peer;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
import cc.mvdan.accesspoint.WifiApControl;
|
|
||||||
|
|
||||||
@SuppressWarnings("LineLength")
|
@SuppressWarnings("LineLength")
|
||||||
public class StartSwapView extends SwapView {
|
public class StartSwapView extends SwapView {
|
||||||
private static final String TAG = "StartSwapView";
|
private static final String TAG = "StartSwapView";
|
||||||
@ -70,7 +71,7 @@ public class StartSwapView extends SwapView {
|
|||||||
Peer peer = getItem(position);
|
Peer peer = getItem(position);
|
||||||
((TextView) convertView.findViewById(R.id.peer_name)).setText(peer.getName());
|
((TextView) convertView.findViewById(R.id.peer_name)).setText(peer.getName());
|
||||||
((ImageView) convertView.findViewById(R.id.icon))
|
((ImageView) convertView.findViewById(R.id.icon))
|
||||||
.setImageDrawable(ContextCompat.getDrawable(getContext(), peer.getIcon()));
|
.setImageDrawable(getResources().getDrawable(peer.getIcon()));
|
||||||
|
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
@ -79,7 +80,7 @@ public class StartSwapView extends SwapView {
|
|||||||
@Nullable /* Emulators typically don't have bluetooth adapters */
|
@Nullable /* Emulators typically don't have bluetooth adapters */
|
||||||
private final BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
|
private final BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
|
||||||
private SwitchMaterial bluetoothSwitch;
|
private SwitchCompat bluetoothSwitch;
|
||||||
private TextView viewBluetoothId;
|
private TextView viewBluetoothId;
|
||||||
private TextView textBluetoothVisible;
|
private TextView textBluetoothVisible;
|
||||||
private TextView viewWifiId;
|
private TextView viewWifiId;
|
||||||
@ -175,7 +176,7 @@ public class StartSwapView extends SwapView {
|
|||||||
|
|
||||||
textBluetoothVisible = findViewById(R.id.bluetooth_visible);
|
textBluetoothVisible = findViewById(R.id.bluetooth_visible);
|
||||||
|
|
||||||
bluetoothSwitch = (SwitchMaterial) findViewById(R.id.switch_bluetooth);
|
bluetoothSwitch = (SwitchCompat) findViewById(R.id.switch_bluetooth);
|
||||||
bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled);
|
bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled);
|
||||||
bluetoothSwitch.setChecked(SwapService.getBluetoothVisibleUserPreference());
|
bluetoothSwitch.setChecked(SwapService.getBluetoothVisibleUserPreference());
|
||||||
bluetoothSwitch.setEnabled(true);
|
bluetoothSwitch.setEnabled(true);
|
||||||
@ -222,17 +223,7 @@ public class StartSwapView extends SwapView {
|
|||||||
WifiApControl wifiAp = WifiApControl.getInstance(getActivity());
|
WifiApControl wifiAp = WifiApControl.getInstance(getActivity());
|
||||||
if (wifiAp != null && wifiAp.isWifiApEnabled()) {
|
if (wifiAp != null && wifiAp.isWifiApEnabled()) {
|
||||||
WifiConfiguration config = wifiAp.getConfiguration();
|
WifiConfiguration config = wifiAp.getConfiguration();
|
||||||
TextView textWifiVisible = findViewById(R.id.wifi_visible);
|
viewWifiNetwork.setText(getContext().getString(R.string.swap_active_hotspot, config.SSID));
|
||||||
if (textWifiVisible != null) {
|
|
||||||
textWifiVisible.setText(R.string.swap_visible_hotspot);
|
|
||||||
}
|
|
||||||
Context context = getContext();
|
|
||||||
if (config == null) {
|
|
||||||
viewWifiNetwork.setText(context.getString(R.string.swap_active_hotspot,
|
|
||||||
context.getString(R.string.swap_blank_wifi_ssid)));
|
|
||||||
} else {
|
|
||||||
viewWifiNetwork.setText(context.getString(R.string.swap_active_hotspot, config.SSID));
|
|
||||||
}
|
|
||||||
} else if (TextUtils.isEmpty(FDroidApp.ssid)) {
|
} else if (TextUtils.isEmpty(FDroidApp.ssid)) {
|
||||||
// not connected to or setup with any wifi network
|
// not connected to or setup with any wifi network
|
||||||
viewWifiNetwork.setText(R.string.swap_no_wifi_network);
|
viewWifiNetwork.setText(R.string.swap_no_wifi_network);
|
@ -1,6 +1,7 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -11,6 +12,13 @@ import android.database.Cursor;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -23,9 +31,7 @@ import android.widget.ListView;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.UpdateService;
|
import org.fdroid.fdroid.UpdateService;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
@ -37,21 +43,12 @@ import org.fdroid.fdroid.data.Repo;
|
|||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
|
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
|
||||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||||
import org.fdroid.fdroid.installer.Installer;
|
import org.fdroid.fdroid.installer.Installer;
|
||||||
|
import org.fdroid.fdroid.localrepo.SwapView;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
import org.fdroid.fdroid.net.DownloaderService;
|
import org.fdroid.fdroid.net.DownloaderService;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.cursoradapter.widget.CursorAdapter;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.CursorLoader;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a view that shows a listing of all apps in the swap repo that this
|
* This is a view that shows a listing of all apps in the swap repo that this
|
||||||
* just connected to. The app listing and search should be replaced by
|
* just connected to. The app listing and search should be replaced by
|
||||||
@ -198,7 +195,7 @@ public class SwapSuccessView extends SwapView implements LoaderManager.LoaderCal
|
|||||||
private final ContentObserver appObserver = new ContentObserver(new Handler()) {
|
private final ContentObserver appObserver = new ContentObserver(new Handler()) {
|
||||||
@Override
|
@Override
|
||||||
public void onChange(boolean selfChange) {
|
public void onChange(boolean selfChange) {
|
||||||
AppCompatActivity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
if (activity != null && app != null) {
|
if (activity != null && app != null) {
|
||||||
app = AppProvider.Helper.findSpecificApp(
|
app = AppProvider.Helper.findSpecificApp(
|
||||||
activity.getContentResolver(),
|
activity.getContentResolver(),
|
||||||
@ -313,7 +310,7 @@ public class SwapSuccessView extends SwapView implements LoaderManager.LoaderCal
|
|||||||
nameView.setText(app.name);
|
nameView.setText(app.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageLoader.getInstance().displayImage(app.getIconUrl(iconView.getContext()), iconView, Utils.getRepoAppDisplayImageOptions());
|
ImageLoader.getInstance().displayImage(app.iconUrl, iconView, Utils.getRepoAppDisplayImageOptions());
|
||||||
|
|
||||||
if (app.hasUpdates()) {
|
if (app.hasUpdates()) {
|
||||||
btnInstall.setText(R.string.menu_upgrade);
|
btnInstall.setText(R.string.menu_upgrade);
|
||||||
@ -365,7 +362,7 @@ public class SwapSuccessView extends SwapView implements LoaderManager.LoaderCal
|
|||||||
@NonNull
|
@NonNull
|
||||||
private LayoutInflater getInflater(Context context) {
|
private LayoutInflater getInflater(Context context) {
|
||||||
if (inflater == null) {
|
if (inflater == null) {
|
||||||
inflater = ContextCompat.getSystemService(context, LayoutInflater.class);
|
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
}
|
}
|
||||||
return inflater;
|
return inflater;
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package org.fdroid.fdroid.nearby;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@ -16,6 +17,17 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.support.annotation.LayoutRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.support.v4.view.MenuItemCompat;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.SearchView;
|
||||||
|
import android.support.v7.widget.SwitchCompat;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -33,22 +45,9 @@ import android.widget.ListView;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import cc.mvdan.accesspoint.WifiApControl;
|
||||||
import androidx.annotation.LayoutRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.SearchView;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
||||||
|
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
|
||||||
import com.google.zxing.integration.android.IntentIntegrator;
|
import com.google.zxing.integration.android.IntentIntegrator;
|
||||||
import com.google.zxing.integration.android.IntentResult;
|
import com.google.zxing.integration.android.IntentResult;
|
||||||
|
|
||||||
import org.fdroid.fdroid.BuildConfig;
|
import org.fdroid.fdroid.BuildConfig;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.NfcHelper;
|
import org.fdroid.fdroid.NfcHelper;
|
||||||
@ -59,12 +58,20 @@ import org.fdroid.fdroid.Utils;
|
|||||||
import org.fdroid.fdroid.data.NewRepoConfig;
|
import org.fdroid.fdroid.data.NewRepoConfig;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
import org.fdroid.fdroid.data.RepoProvider;
|
import org.fdroid.fdroid.data.RepoProvider;
|
||||||
import org.fdroid.fdroid.nearby.peers.BluetoothPeer;
|
import org.fdroid.fdroid.localrepo.BluetoothManager;
|
||||||
import org.fdroid.fdroid.nearby.peers.Peer;
|
import org.fdroid.fdroid.localrepo.BonjourManager;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalHTTPDManager;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
||||||
|
import org.fdroid.fdroid.localrepo.SwapService;
|
||||||
|
import org.fdroid.fdroid.localrepo.SwapView;
|
||||||
|
import org.fdroid.fdroid.localrepo.peers.BluetoothPeer;
|
||||||
|
import org.fdroid.fdroid.localrepo.peers.Peer;
|
||||||
import org.fdroid.fdroid.net.BluetoothDownloader;
|
import org.fdroid.fdroid.net.BluetoothDownloader;
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
import org.fdroid.fdroid.net.HttpDownloader;
|
import org.fdroid.fdroid.net.HttpDownloader;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
import org.fdroid.fdroid.qr.CameraCharacteristicsChecker;
|
import org.fdroid.fdroid.qr.CameraCharacteristicsChecker;
|
||||||
|
import org.fdroid.fdroid.qr.QrGenAsyncTask;
|
||||||
import org.fdroid.fdroid.views.main.MainActivity;
|
import org.fdroid.fdroid.views.main.MainActivity;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -75,9 +82,6 @@ import java.util.Set;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
import cc.mvdan.accesspoint.WifiApControl;
|
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
|
||||||
|
|
||||||
import static org.fdroid.fdroid.views.main.MainActivity.ACTION_REQUEST_SWAP;
|
import static org.fdroid.fdroid.views.main.MainActivity.ACTION_REQUEST_SWAP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,21 +110,18 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
private static final int REQUEST_WRITE_SETTINGS_PERMISSION = 5;
|
private static final int REQUEST_WRITE_SETTINGS_PERMISSION = 5;
|
||||||
private static final int STEP_INTRO = 1; // TODO remove this special case, only use layoutResIds
|
private static final int STEP_INTRO = 1; // TODO remove this special case, only use layoutResIds
|
||||||
|
|
||||||
private MaterialToolbar toolbar;
|
private Toolbar toolbar;
|
||||||
private SwapView currentView;
|
private SwapView currentView;
|
||||||
private boolean hasPreparedLocalRepo;
|
private boolean hasPreparedLocalRepo;
|
||||||
private boolean newIntent;
|
private boolean newIntent;
|
||||||
private NewRepoConfig confirmSwapConfig;
|
private NewRepoConfig confirmSwapConfig;
|
||||||
private LocalBroadcastManager localBroadcastManager;
|
private LocalBroadcastManager localBroadcastManager;
|
||||||
private WifiManager wifiManager;
|
private WifiManager wifiManager;
|
||||||
private WifiApControl wifiApControl;
|
|
||||||
private BluetoothAdapter bluetoothAdapter;
|
private BluetoothAdapter bluetoothAdapter;
|
||||||
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
private int currentSwapViewLayoutRes = STEP_INTRO;
|
private int currentSwapViewLayoutRes = STEP_INTRO;
|
||||||
|
|
||||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
public static void requestSwap(Context context, String repo) {
|
public static void requestSwap(Context context, String repo) {
|
||||||
requestSwap(context, Uri.parse(repo));
|
requestSwap(context, Uri.parse(repo));
|
||||||
}
|
}
|
||||||
@ -202,11 +203,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
FDroidApp fdroidApp = (FDroidApp) getApplication();
|
((FDroidApp) getApplication()).setSecureWindow(this);
|
||||||
fdroidApp.setSecureWindow(this);
|
|
||||||
|
|
||||||
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
currentView = new SwapView(this); // dummy placeholder to avoid NullPointerExceptions;
|
currentView = new SwapView(this); // dummy placeholder to avoid NullPointerExceptions;
|
||||||
@ -219,7 +216,8 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
setContentView(R.layout.swap_activity);
|
setContentView(R.layout.swap_activity);
|
||||||
|
|
||||||
toolbar = findViewById(R.id.toolbar);
|
toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
toolbar.setTitleTextAppearance(getApplicationContext(), R.style.SwapTheme_Wizard_Text_Toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
container = (ViewGroup) findViewById(R.id.container);
|
container = (ViewGroup) findViewById(R.id.container);
|
||||||
@ -228,8 +226,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
localBroadcastManager.registerReceiver(downloaderInterruptedReceiver,
|
localBroadcastManager.registerReceiver(downloaderInterruptedReceiver,
|
||||||
new IntentFilter(Downloader.ACTION_INTERRUPTED));
|
new IntentFilter(Downloader.ACTION_INTERRUPTED));
|
||||||
|
|
||||||
wifiManager = ContextCompat.getSystemService(getApplicationContext(), WifiManager.class);
|
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
wifiApControl = WifiApControl.getInstance(this);
|
|
||||||
|
|
||||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
|
||||||
@ -238,7 +235,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
compositeDisposable.dispose();
|
|
||||||
localBroadcastManager.unregisterReceiver(downloaderInterruptedReceiver);
|
localBroadcastManager.unregisterReceiver(downloaderInterruptedReceiver);
|
||||||
unbindService(serviceConnection);
|
unbindService(serviceConnection);
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
@ -277,7 +273,8 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
CharSequence title = getString(titleResId);
|
CharSequence title = getString(titleResId);
|
||||||
next.setTitle(title);
|
next.setTitle(title);
|
||||||
next.setTitleCondensed(title);
|
next.setTitleCondensed(title);
|
||||||
next.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
MenuItemCompat.setShowAsAction(next,
|
||||||
|
MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||||
next.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
next.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
@ -306,8 +303,8 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
SearchView searchView = new SearchView(this);
|
SearchView searchView = new SearchView(this);
|
||||||
|
|
||||||
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
|
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
|
||||||
searchMenuItem.setActionView(searchView);
|
MenuItemCompat.setActionView(searchMenuItem, searchView);
|
||||||
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
MenuItemCompat.setShowAsAction(searchMenuItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
|
|
||||||
@ -445,19 +442,12 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupWifiAP() {
|
private void setupWifiAP() {
|
||||||
if (wifiApControl == null) {
|
WifiApControl ap = WifiApControl.getInstance(this);
|
||||||
Log.e(TAG, "WiFi AP is null");
|
|
||||||
Toast.makeText(this, R.string.swap_toast_could_not_enable_hotspot, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SwapService.putHotspotEnabledBeforeSwap(wifiApControl.isEnabled());
|
|
||||||
wifiManager.setWifiEnabled(false);
|
wifiManager.setWifiEnabled(false);
|
||||||
if (wifiApControl.enable()) {
|
if (ap.enable()) {
|
||||||
Toast.makeText(this, R.string.swap_toast_hotspot_enabled, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.swap_toast_hotspot_enabled, Toast.LENGTH_SHORT).show();
|
||||||
SwapService.putHotspotActivatedUserPreference(true);
|
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.swap_toast_could_not_enable_hotspot, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.swap_toast_could_not_enable_hotspot, Toast.LENGTH_LONG).show();
|
||||||
SwapService.putHotspotActivatedUserPreference(false);
|
|
||||||
Log.e(TAG, "Could not enable WiFi AP.");
|
Log.e(TAG, "Could not enable WiFi AP.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,13 +483,14 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
getSwapService().initTimer();
|
getSwapService().initTimer();
|
||||||
|
|
||||||
container.removeAllViews();
|
container.removeAllViews();
|
||||||
View view = ContextCompat.getSystemService(this, LayoutInflater.class)
|
View view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(viewRes, container, false);
|
||||||
.inflate(viewRes, container, false);
|
|
||||||
currentView = (SwapView) view;
|
currentView = (SwapView) view;
|
||||||
currentView.setLayoutResId(viewRes);
|
currentView.setLayoutResId(viewRes);
|
||||||
currentSwapViewLayoutRes = viewRes;
|
currentSwapViewLayoutRes = viewRes;
|
||||||
|
|
||||||
|
toolbar.setBackgroundColor(currentView.getToolbarColour());
|
||||||
toolbar.setTitle(currentView.getToolbarTitle());
|
toolbar.setTitle(currentView.getToolbarTitle());
|
||||||
|
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
|
||||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
@ -597,7 +588,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendFDroidApk() {
|
private void sendFDroidApk() {
|
||||||
((FDroidApp) getApplication()).sendViaBluetooth(this, AppCompatActivity.RESULT_OK, BuildConfig.APPLICATION_ID);
|
((FDroidApp) getApplication()).sendViaBluetooth(this, Activity.RESULT_OK, BuildConfig.APPLICATION_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -699,7 +690,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
super.onActivityResult(requestCode, resultCode, intent);
|
|
||||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
||||||
if (scanResult != null) {
|
if (scanResult != null) {
|
||||||
if (scanResult.getContents() != null) {
|
if (scanResult.getContents() != null) {
|
||||||
@ -779,7 +769,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
private final BroadcastReceiver bluetoothScanModeChanged = new BroadcastReceiver() {
|
private final BroadcastReceiver bluetoothScanModeChanged = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
SwitchMaterial bluetoothSwitch = container.findViewById(R.id.switch_bluetooth);
|
SwitchCompat bluetoothSwitch = container.findViewById(R.id.switch_bluetooth);
|
||||||
TextView textBluetoothVisible = container.findViewById(R.id.bluetooth_visible);
|
TextView textBluetoothVisible = container.findViewById(R.id.bluetooth_visible);
|
||||||
if (bluetoothSwitch == null || textBluetoothVisible == null
|
if (bluetoothSwitch == null || textBluetoothVisible == null
|
||||||
|| !BluetoothManager.ACTION_STATUS.equals(intent.getAction())) {
|
|| !BluetoothManager.ACTION_STATUS.equals(intent.getAction())) {
|
||||||
@ -933,14 +923,10 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
ImageView qrImage = container.findViewById(R.id.wifi_qr_code);
|
ImageView qrImage = container.findViewById(R.id.wifi_qr_code);
|
||||||
if (qrUriString != null && qrImage != null) {
|
if (qrUriString != null && qrImage != null) {
|
||||||
Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString);
|
Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString);
|
||||||
|
new QrGenAsyncTask(SwapWorkflowActivity.this, R.id.wifi_qr_code).execute(qrUriString);
|
||||||
compositeDisposable.add(Utils.generateQrBitmap(this, qrUriString)
|
|
||||||
.subscribe(qrBitmap -> {
|
|
||||||
qrImage.setImageBitmap(qrBitmap);
|
|
||||||
|
|
||||||
// Replace all blacks with the background blue.
|
// Replace all blacks with the background blue.
|
||||||
qrImage.setColorFilter(new LightingColorFilter(0xffffffff,
|
qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue)));
|
||||||
ContextCompat.getColor(this, R.color.swap_blue)));
|
|
||||||
|
|
||||||
final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner);
|
final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner);
|
||||||
if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) {
|
if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) {
|
||||||
@ -948,8 +934,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
} else {
|
} else {
|
||||||
qrWarningMessage.setVisibility(View.VISIBLE);
|
qrWarningMessage.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -968,19 +952,19 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
if (TextUtils.isEmpty(FDroidApp.bssid) && !TextUtils.isEmpty(FDroidApp.ipAddressString)) {
|
if (TextUtils.isEmpty(FDroidApp.bssid) && !TextUtils.isEmpty(FDroidApp.ipAddressString)) {
|
||||||
// empty bssid with an ipAddress means hotspot mode
|
// empty bssid with an ipAddress means hotspot mode
|
||||||
descriptionView.setText(R.string.swap_join_this_hotspot);
|
descriptionView.setText(R.string.swap_join_this_hotspot);
|
||||||
wifiIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_wifi_tethering));
|
wifiIcon.setImageDrawable(getResources().getDrawable(R.drawable.hotspot));
|
||||||
ssidView.setText(R.string.swap_active_hotspot);
|
ssidView.setText(R.string.swap_active_hotspot);
|
||||||
tapView.setText(R.string.swap_switch_to_wifi);
|
tapView.setText(R.string.swap_switch_to_wifi);
|
||||||
} else if (TextUtils.isEmpty(FDroidApp.ssid)) {
|
} else if (TextUtils.isEmpty(FDroidApp.ssid)) {
|
||||||
// not connected to or setup with any wifi network
|
// not connected to or setup with any wifi network
|
||||||
descriptionView.setText(R.string.swap_join_same_wifi);
|
descriptionView.setText(R.string.swap_join_same_wifi);
|
||||||
wifiIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_wifi));
|
wifiIcon.setImageDrawable(getResources().getDrawable(R.drawable.wifi));
|
||||||
ssidView.setText(R.string.swap_no_wifi_network);
|
ssidView.setText(R.string.swap_no_wifi_network);
|
||||||
tapView.setText(R.string.swap_view_available_networks);
|
tapView.setText(R.string.swap_view_available_networks);
|
||||||
} else {
|
} else {
|
||||||
// connected to a regular wifi network
|
// connected to a regular wifi network
|
||||||
descriptionView.setText(R.string.swap_join_same_wifi);
|
descriptionView.setText(R.string.swap_join_same_wifi);
|
||||||
wifiIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_wifi));
|
wifiIcon.setImageDrawable(getResources().getDrawable(R.drawable.wifi));
|
||||||
ssidView.setText(FDroidApp.ssid);
|
ssidView.setText(FDroidApp.ssid);
|
||||||
tapView.setText(R.string.swap_view_available_networks);
|
tapView.setText(R.string.swap_view_available_networks);
|
||||||
}
|
}
|
||||||
@ -996,17 +980,13 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
SwitchMaterial wifiSwitch = findViewById(R.id.switch_wifi);
|
SwitchCompat wifiSwitch = findViewById(R.id.switch_wifi);
|
||||||
wifiSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
wifiSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
Context context = getApplicationContext();
|
Context context = getApplicationContext();
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
if (wifiApControl != null && wifiApControl.isEnabled()) {
|
|
||||||
setupWifiAP();
|
|
||||||
} else {
|
|
||||||
wifiManager.setWifiEnabled(true);
|
wifiManager.setWifiEnabled(true);
|
||||||
}
|
|
||||||
BonjourManager.start(context);
|
BonjourManager.start(context);
|
||||||
}
|
}
|
||||||
BonjourManager.setVisible(context, isChecked);
|
BonjourManager.setVisible(context, isChecked);
|
||||||
@ -1060,11 +1040,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
peopleNearbyProgress.setVisibility(View.VISIBLE);
|
peopleNearbyProgress.setVisibility(View.VISIBLE);
|
||||||
break;
|
break;
|
||||||
case BonjourManager.STATUS_VISIBLE:
|
case BonjourManager.STATUS_VISIBLE:
|
||||||
if (wifiApControl != null && wifiApControl.isEnabled()) {
|
|
||||||
textWifiVisible.setText(R.string.swap_visible_hotspot);
|
|
||||||
} else {
|
|
||||||
textWifiVisible.setText(R.string.swap_visible_wifi);
|
textWifiVisible.setText(R.string.swap_visible_wifi);
|
||||||
}
|
|
||||||
peopleNearbyText.setText(R.string.swap_scanning_for_peers);
|
peopleNearbyText.setText(R.string.swap_scanning_for_peers);
|
||||||
peopleNearbyText.setVisibility(View.VISIBLE);
|
peopleNearbyText.setVisibility(View.VISIBLE);
|
||||||
peopleNearbyProgress.setVisibility(View.VISIBLE);
|
peopleNearbyProgress.setVisibility(View.VISIBLE);
|
||||||
@ -1120,7 +1096,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
|||||||
private final BroadcastReceiver bluetoothStatus = new BroadcastReceiver() {
|
private final BroadcastReceiver bluetoothStatus = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
SwitchMaterial bluetoothSwitch = container.findViewById(R.id.switch_bluetooth);
|
SwitchCompat bluetoothSwitch = container.findViewById(R.id.switch_bluetooth);
|
||||||
TextView textBluetoothVisible = container.findViewById(R.id.bluetooth_visible);
|
TextView textBluetoothVisible = container.findViewById(R.id.bluetooth_visible);
|
||||||
TextView textDeviceIdBluetooth = container.findViewById(R.id.device_id_bluetooth);
|
TextView textDeviceIdBluetooth = container.findViewById(R.id.device_id_bluetooth);
|
||||||
TextView peopleNearbyText = container.findViewById(R.id.text_people_nearby);
|
TextView peopleNearbyText = container.findViewById(R.id.text_people_nearby);
|
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 313 B |
BIN
app/src/full/res/drawable-hdpi/hotspot.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
app/src/full/res/drawable-hdpi/ic_add_circle_outline_white.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/full/res/drawable-hdpi/ic_arrow_forward_white.png
Normal file
After Width: | Height: | Size: 248 B |
BIN
app/src/full/res/drawable-hdpi/ic_bluetooth_white.png
Normal file
After Width: | Height: | Size: 427 B |
BIN
app/src/full/res/drawable-hdpi/ic_check_circle_white.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 446 B |
BIN
app/src/full/res/drawable-hdpi/ic_network_wifi_white.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/full/res/drawable-hdpi/ic_qr_grey.png
Normal file
After Width: | Height: | Size: 519 B |