Compare commits

..

No commits in common. "master" and "1.7-alpha0" have entirely different histories.

1510 changed files with 12402 additions and 32847 deletions

1
.gitattributes vendored
View File

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

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ build.xml
# Gradle files
.gradle/
build/
gradle.properties
# Local configuration file (sdk path, etc)
local.properties

View File

@ -1,36 +1,26 @@
stages:
- test
- deploy
.base:
image: registry.gitlab.com/fdroid/ci-images-client:latest
before_script:
- export GRADLE_USER_HOME=$PWD/.gradle
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
- alias sdkmanager="sdkmanager --no_https"
- echo y | sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null
# limit RAM usage for all gradle runs
- export maxmem=$(expr $(sed -n 's,^MemAvailable:[^0-9]*\([0-9][0-9]*\)[^0-9]*$,\1,p' /proc/meminfo) / 1024 / 2 / 1024 \* 1024)
- printf "\norg.gradle.jvmargs=-Xmx${maxmem}m -XX:MaxPermSize=${maxmem}m\norg.gradle.daemon=false\norg.gradle.parallel=false\n" >> gradle.properties
after_script:
# this file changes every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
cache:
paths:
- .gradle/wrapper
- .gradle/caches
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
extends: .base
stage: test
artifacts:
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
paths:
- kernel.log
- logcat.txt
- app/core*
- app/*.log
- app/build/reports
- app/build/outputs/*ml
- app/build/outputs/apk
@ -45,40 +35,29 @@ test_lint_pmd_checkstyle:
<<: *test-template
script:
- export EXITVALUE=0
- function set_error() { export EXITVALUE=1; printf "\x1b[31mERROR `history|tail -2|head -1|cut -b 6-500`\x1b[0m\n"; }
- ./gradlew assemble
# always report on lint errors to the build log
- sed -i -e 's,textReport .*,textReport true,' app/build.gradle
- ./gradlew testFullDebugUnitTest || set_error
- ./gradlew lint || set_error
- ./gradlew pmd || set_error
- ./gradlew checkstyle || set_error
- ./tools/check-format-strings.py || set_error
- ./tools/check-fastlane-whitespace.py || set_error
- ./tools/remove-unused-and-blank-translations.py || set_error
- ./gradlew testFullDebugUnitTest
- ./gradlew lint
- ./gradlew pmd || export EXITVALUE=1
- ./gradlew checkstyle || export EXITVALUE=1
- ./tools/check-format-strings.py || export EXITVALUE=1
- ./tools/check-fastlane-whitespace.py || export EXITVALUE=1
- ./tools/remove-unused-and-blank-translations.py || export EXITVALUE=1
- echo "These are unused or blank translations that should be removed:"
- git --no-pager diff --ignore-all-space --name-only --exit-code app/src/*/res/values*/strings.xml || set_error
- git --no-pager diff --ignore-all-space --name-only --exit-code app/src/*/res/values*/strings.xml || export EXITVALUE=1
- exit $EXITVALUE
errorprone:
extends: .base
stage: test
script:
- apt-get update
- apt-get install -t stretch-backports openjdk-11-jdk-headless
- update-java-alternatives --set java-1.11.0-openjdk-amd64
- export JAVA_HOME=/usr/lib/jvm/java-1.11.0-openjdk-amd64
- cat config/errorprone.gradle >> app/build.gradle
- ./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug
- ./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
# connectedCheck to test all the build flavors
.connected-template: &connected-template
extends: .base
script:
- ./gradlew assembleFullDebug
- 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_PACKAGE="system-images;android-${AVD_SDK};${AVD_TAG};${AVD_ARCH}"
- 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
- 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
- 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
- adb devices
- adb shell input keyevent 82 &
- ./gradlew installFullDebug
- adb shell am start -n org.fdroid.fdroid.debug/org.fdroid.fdroid.views.main.MainActivity
- if [ $AVD_SDK -lt 25 ] || ! emulator -accel-check; then
export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest;
fi
- test $AVD_SDK -ge 25 || export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=android.support.test.filters.LargeTest
- ./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
<<: *connected-template
@ -110,17 +129,35 @@ no-accel 22 default x86:
tags:
- fdroid
- kvm
allow_failure: true
only:
variables:
- $RUN_KVM_JOBS
- branches@eighthave/fdroidclient
<<: *test-template
<<: *connected-template
kvm 29 microg x86_64:
connected 23 default x86:
<<: *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:
extends: .base
stage: deploy
only:
- master
@ -140,3 +177,8 @@ deploy_nightly:
# build the APKs!
- ./gradlew assembleDebug
- 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/

View File

@ -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)
* major refactor of "Nearby" UI code, to prepare for rewriting guts

View File

@ -26,16 +26,14 @@ track of modifications and fuzzy translations. Applying translations manually
skips all of the fixes and checks, and overrides the fuzzy state of strings.
Note that you cannot change the English strings on Weblate. If you have any
suggestions on how to improve them, open an issue or merge request like you
would if you were making code changes. This way the changes can be reviewed
before the source strings on Weblate are changed.
suggestions on how to improve them, open a merge request like you would if you
were making code changes. This way the changes can be reviewed before the
source strings on Weblate are changed.
## Code Style
We follow the default Android Studio code formatter (e.g. `Ctrl-Alt-L`). This
should be more or less the same as [Android Java
style](https://source.android.com/source/code-style.html). Some key points:
We follow the [Android Java style](https://source.android.com/source/code-style.html).
Some key points:
* Four space indentation
* UTF-8 source files
@ -47,35 +45,76 @@ style](https://source.android.com/source/code-style.html). Some key points:
* Braces are always used after if, for and while
The current code base doesn't follow it entirely, but new code should follow
it. We enforce some of these, but not all, via `./gradlew checkstyle`.
it. We enforce some of these, but not all, via checkstyle.
## Debugging
To get all the logcat messages by F-Droid, you can run:
adb logcat | grep `adb shell ps | grep org.fdroid.fdroid | cut -c10-15`
## Building tips
* Use gradle with `--daemon` if you are going to build F-Droid multiple times.
* If you get a message like `Could not find com.android.support:support-...`,
make sure that you have the latest Android support maven repository.
* 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
Before pushing commits to a merge request, make sure this passes:
./gradlew checkstyle pmd lint
In order to run the F-Droid test suite, you will need to have either a real device
connected via `adb`, or an emulator running. Then, execute the following from the
command line:
./gradlew check
Many important tests require a device or emulator, but do not work in GitLab CI.
That mean they need to be run locally, and that is usually easiest in Android
Studio rather than the command line.
Note that the CI already runs the tests on an emulator, so you don't
necessarily have to do this yourself if you open a merge request as the tests
will get run.
For a quick way to run a specific JUnit/Robolectric test:
### 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 \
-Pandroid.testInstrumentationRunnerArguments.class=org.fdroid.fdroid.MainActivityExpressoTest
Each stable version follows the `X.Y` pattern. Hotfix releases - i.e. when a
stable has an important bug that needs immediate fixing - will follow the
`X.Y.Z` pattern.
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.

View File

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

View File

@ -1,6 +1,6 @@
# F-Droid Client
[![build status](https://gitlab.com/fdroid/fdroidclient/badges/master/pipeline.svg)](https://gitlab.com/fdroid/fdroidclient/-/jobs)
[![build status](https://gitlab.com/fdroid/fdroidclient/badges/master/build.svg)](https://gitlab.com/fdroid/fdroidclient/builds)
[![Translation status](https://hosted.weblate.org/widgets/f-droid/-/svg-badge.svg)](https://hosted.weblate.org/engage/f-droid/)
Client for [F-Droid](https://f-droid.org), the Free Software repository system

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
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 stdout = new ByteArrayOutputStream()
exec {
@ -12,8 +12,8 @@ def getVersionName = { ->
return stdout.toString().trim()
}
def isCi = "true" == System.getenv("CI")
def preDexEnabled = "true" == System.getProperty("pre-dex", "true")
def isCi = "true".equals(System.getenv("CI"))
def preDexEnabled = "true".equals(System.getProperty("pre-dex", "true"))
def fullApplicationId = "org.fdroid.fdroid"
def basicApplicationId = "org.fdroid.basic"
@ -21,16 +21,14 @@ def basicApplicationId = "org.fdroid.basic"
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
android {
compileSdkVersion 30
compileSdkVersion 27
buildToolsVersion '27.0.3'
defaultConfig {
versionCode 1013001
versionCode 1007000
versionName getVersionName()
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
minSdkVersion 24
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
/*
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
@ -38,7 +36,6 @@ android {
passing the following argument to the test runner: disableAnalytics "true".
*/
testInstrumentationRunnerArguments disableAnalytics: 'true'
vectorDrawables.useSupportLibrary = true
}
buildTypes {
@ -46,9 +43,9 @@ android {
// release builds before.
all {
minifyEnabled true
useProguard true
shrinkResources true
buildConfigField "String", "PRIVILEGED_EXTENSION_PACKAGE_NAME", privilegedExtensionApplicationId
buildConfigField "String", "ACRA_REPORT_EMAIL", '"reports@f-droid.org"' // String needs both quotes
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'src/androidTest/proguard-rules.pro'
}
@ -76,8 +73,6 @@ android {
compileOptions {
compileOptions.encoding = "UTF-8"
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
aaptOptions {
@ -102,10 +97,6 @@ android {
events "skipped", "failed", "standardOut", "standardError"
showStandardStreams = true
}
systemProperty 'robolectric.dependency.repo.url', 'https://repo1.maven.org/maven2'
// hack to avoid memory leak crashes
forkEvery = 1
}
}
}
@ -142,56 +133,50 @@ android {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.work:work-runtime:2.4.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'com.android.support:support-v4:27.1.1'
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:gridlayout-v7:27.1.1'
implementation 'com.android.support:support-annotations:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:support-vector-drawable:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:palette-v7:27.1.1'
implementation 'com.android.support:preference-v14:27.1.1'
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
implementation 'com.google.zxing:core:3.3.3'
implementation 'info.guardianproject.netcipher:netcipher:2.2.0-alpha'
implementation 'info.guardianproject.panic:panic:1.0'
implementation 'info.guardianproject.netcipher:netcipher:2.0.0-beta1'
implementation 'info.guardianproject.panic:panic:0.5'
implementation 'commons-io:commons-io:2.6'
implementation 'commons-net:commons-net:3.6'
implementation 'ch.acra:acra:4.9.1'
implementation 'io.reactivex:rxjava:1.1.0'
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 'io.reactivex.rxjava3:rxjava:3.0.9'
implementation 'com.fasterxml.jackson.core:jackson-core:2.8.11'
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 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1'
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.65'
implementation 'org.bouncycastle:bcprov-jdk15on:1.60'
fullImplementation 'org.bouncycastle:bcpkix-jdk15on:1.60'
fullImplementation 'cc.mvdan.accesspoint:library:0.2.0'
fullImplementation 'org.jmdns:jmdns:3.5.5'
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'junit:junit:4.13.1'
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation 'org.mockito:mockito-core:3.3.3'
testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.65'
testImplementation 'org.robolectric:robolectric:3.8'
testImplementation "com.android.support.test:monitor:1.0.2"
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.60'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.7.22'
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:monitor:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.work:work-testing:2.4.0'
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
}
checkstyle {
@ -207,7 +192,7 @@ task checkstyle(type: Checkstyle) {
}
pmd {
toolVersion = '6.20.0'
toolVersion = '5.5.1'
consoleOutput = true
}
@ -217,6 +202,7 @@ task pmdMain(type: Pmd) {
ruleSets = [] // otherwise defaults clash with the list in rules.xml
source 'src/main/java'
include '**/*.java'
exclude '**/kellinwood/**/*.java'
}
task pmdTest(type: Pmd) {

View File

@ -4,11 +4,13 @@
-keep class org.fdroid.fdroid.** {*;}
-dontskipnonpubliclibraryclassmembers
-dontwarn android.test.**
-dontwarn com.android.support.test.**
-dontwarn javax.naming.**
-dontwarn org.slf4j.**
-dontnote org.apache.http.**
-dontnote android.net.http.**
-dontnote android.support.**
-dontnote **ILicensingService
# Needed for espresso https://stackoverflow.com/a/21706087
@ -31,6 +33,24 @@
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
-keepnames class com.fasterxml.jackson.** { *; }
-dontwarn com.fasterxml.jackson.databind.ext.**
@ -40,8 +60,3 @@ public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
-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>(...);
}

View File

@ -1,24 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.fdroid.fdroid.tests"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk tools:overrideLibrary="android_libs.ub_uiautomator" />
<uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18"/>
<!-- We add an application tag here just so that we can indicate that
this package needs to link against the android.test library,
which is needed when building test cases. -->
<application>
<uses-library
android:name="android.test.runner"
android:required="false" />
<uses-library android:name="android.test.runner"/>
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

View File

@ -1,8 +1,8 @@
package org.fdroid.fdroid;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
@ -16,9 +16,6 @@ public class AssetUtils {
private static final String TAG = "Utils";
/**
* This requires {@link Context} from {@link android.app.Instrumentation#getContext()}
*/
@Nullable
public static File copyAssetToDir(Context context, String assetName, File directory) {
File tempFile = null;
@ -31,7 +28,6 @@ public class AssetUtils {
output = new FileOutputStream(tempFile);
Utils.copy(input, output);
} catch (IOException e) {
Log.e(TAG, "Check the context is from Instrumentation.getContext()");
fail(e.getMessage());
} finally {
Utils.closeQuietly(output);

View File

@ -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);
}
}

View File

@ -5,8 +5,8 @@ import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
@ -179,9 +179,6 @@ public class LocalizationTest {
case "dd":
resources.getString(resId, 1, 2);
break;
case "ds":
resources.getString(resId, 1, "TWO");
break;
case "dds":
resources.getString(resId, 1, 2, "THREE");
break;

View File

@ -5,23 +5,20 @@ import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Build;
import androidx.core.content.ContextCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.espresso.IdlingPolicies;
import androidx.test.espresso.ViewInteraction;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.IdlingPolicies;
import android.support.test.espresso.ViewInteraction;
import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.rule.GrantPermissionRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector;
import android.util.Log;
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.hamcrest.Matchers;
import org.junit.After;
@ -35,19 +32,18 @@ import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.swipeDown;
import static androidx.test.espresso.action.ViewActions.swipeLeft;
import static androidx.test.espresso.action.ViewActions.swipeRight;
import static androidx.test.espresso.action.ViewActions.swipeUp;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.swipeDown;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.action.ViewActions.swipeUp;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertTrue;
@ -91,7 +87,7 @@ public class MainActivityEspressoTest {
} catch (IOException e) {
e.printStackTrace();
}
SystemAnimations.disableAll(ApplicationProvider.getApplicationContext());
SystemAnimations.disableAll(InstrumentationRegistry.getTargetContext());
// dismiss the ANR or any other system dialogs that might be there
UiObject button = new UiObject(new UiSelector().text("Wait").enabled(true));
@ -104,7 +100,7 @@ public class MainActivityEspressoTest {
Context context = instrumentation.getTargetContext();
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
ActivityManager activityManager = ContextCompat.getSystemService(context, ActivityManager.class);
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.getMemoryInfo(mi);
long percentAvail = mi.availMem / mi.totalMem;
Log.i(TAG, "RAM: " + mi.availMem + " / " + mi.totalMem + " = " + percentAvail);
@ -112,7 +108,7 @@ public class MainActivityEspressoTest {
@AfterClass
public static void classTearDown() {
SystemAnimations.enableAll(ApplicationProvider.getApplicationContext());
SystemAnimations.enableAll(InstrumentationRegistry.getTargetContext());
}
public static boolean isEmulator() {
@ -177,7 +173,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showSettings() {
ViewInteraction settingsBottonNavButton = onView(
allOf(withText(R.string.menu_settings), isDisplayed()));
@ -192,27 +187,9 @@ public class MainActivityEspressoTest {
allOf(withText(R.string.preference_manage_installed_apps), isDisplayed()));
manageInstalledAppsButton.perform(click());
onView(withText(R.string.installed_apps__activity_title)).check(matches(isDisplayed()));
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
onView(withText(R.string.menu_manage)).perform(click());
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
manageInstalledAppsButton.perform(click());
onView(withText(R.string.installed_apps__activity_title)).check(matches(isDisplayed()));
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
onView(withText(R.string.menu_manage)).perform(click());
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
onView(withText(R.string.about_title)).perform(click());
onView(withId(R.id.version)).check(matches(isDisplayed()));
onView(withId(R.id.ok_button)).perform(click());
onView(withId(android.R.id.list_container)).perform(swipeUp()).perform(swipeUp()).perform(swipeUp());
}
@LargeTest
@Test
public void showUpdates() {
ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed()));
updatesBottonNavButton.perform(click());
@ -220,7 +197,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void startSwap() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
@ -236,7 +212,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showCategories() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
@ -262,12 +237,11 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showLatest() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
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.main_menu__latest_apps), isDisplayed())).perform(click());
onView(allOf(withId(R.id.swipe_to_refresh), isDisplayed()))
@ -285,7 +259,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showSearch() {
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
onView(withId(R.id.fab_search)).check(doesNotExist());

View File

@ -123,7 +123,7 @@ public class Netstat {
try {
BufferedReader in = new BufferedReader(new FileReader("/proc/net/tcp"));
String line;
while ((line = in.readLine()) != null) { // NOPMD
while ((line = in.readLine()) != null) {
Matcher matcher = NET_PATTERN.matcher(line);
if (matcher.find()) {
final Connection c = new Connection();
@ -141,7 +141,7 @@ public class Netstat {
try {
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
} catch (Exception ex) {
c.setStatus(STATES[11]); // unknown
c.setStatus(STATES[11]); // unknwon
}
c.setPID(-1); // unknown
c.setPName("UNKNOWN");
@ -156,7 +156,7 @@ public class Netstat {
try {
BufferedReader in = new BufferedReader(new FileReader("/proc/net/udp"));
String line;
while ((line = in.readLine()) != null) { // NOPMD
while ((line = in.readLine()) != null) {
Matcher matcher = NET_PATTERN.matcher(line);
if (matcher.find()) {
final Connection c = new Connection();
@ -174,7 +174,7 @@ public class Netstat {
try {
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
} catch (Exception ex) {
c.setStatus(STATES[11]); // unknown
c.setStatus(STATES[11]); // unknwon
}
c.setPID(-1); // unknown
c.setPName("UNKNOWN");
@ -189,7 +189,7 @@ public class Netstat {
try {
BufferedReader in = new BufferedReader(new FileReader("/proc/net/raw"));
String line;
while ((line = in.readLine()) != null) { // NOPMD
while ((line = in.readLine()) != null) {
Matcher matcher = NET_PATTERN.matcher(line);
if (matcher.find()) {
final Connection c = new Connection();
@ -208,7 +208,7 @@ public class Netstat {
try {
c.setStatus(STATES[Integer.parseInt(statusHexa, 16) - 1]);
} catch (Exception ex) {
c.setStatus(STATES[11]); // unknown
c.setStatus(STATES[11]); // unknwon
}
c.setPID(-1); // unknown
c.setPName("UNKNOWN");

View File

@ -16,11 +16,11 @@
package org.fdroid.fdroid;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;
import androidx.test.uiautomator.UiWatcher;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.UiWatcher;
import android.util.Log;
import java.util.ArrayList;
@ -117,7 +117,7 @@ public class UiWatchers {
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) {

View File

@ -4,8 +4,8 @@ import android.app.Instrumentation;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import org.fdroid.fdroid.AssetUtils;

View File

@ -22,10 +22,10 @@ package org.fdroid.fdroid.installer;
import android.app.Instrumentation;
import android.net.Uri;
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 androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.fdroid.fdroid.AssetUtils;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.compat.FileCompatTest;
@ -113,7 +113,7 @@ public class ApkVerifierTest {
Apk apk = new Apk();
apk.packageName = "org.fdroid.permissions.sdk14";
apk.targetSdkVersion = 14;
ArrayList<String> noPrefixPermissionsList = new ArrayList<>(Arrays.asList(
String[] noPrefixPermissions = new String[]{
"AUTHENTICATE_ACCOUNTS",
"MANAGE_ACCOUNTS",
"READ_PROFILE",
@ -129,13 +129,8 @@ public class ApkVerifierTest {
"READ_SYNC_SETTINGS",
"WRITE_SYNC_SETTINGS",
"WRITE_CALL_LOG", // implied-permission!
"READ_CALL_LOG" // implied-permission!
));
if (Build.VERSION.SDK_INT >= 29) {
noPrefixPermissionsList.add("android.permission.ACCESS_MEDIA_LOCATION");
}
String[] noPrefixPermissions = noPrefixPermissionsList.toArray(new String[0]);
"READ_CALL_LOG", // implied-permission!
};
for (int i = 0; i < noPrefixPermissions.length; i++) {
noPrefixPermissions[i] = RepoXMLHandler.fdroidToAndroidPermission(noPrefixPermissions[i]);
}
@ -182,7 +177,7 @@ public class ApkVerifierTest {
Apk apk = new Apk();
apk.packageName = "org.fdroid.permissions.sdk14";
apk.targetSdkVersion = 14;
TreeSet<String> expectedSet = new TreeSet<>(Arrays.asList(
apk.requestedPermissions = new String[]{
"android.permission.AUTHENTICATE_ACCOUNTS",
"android.permission.MANAGE_ACCOUNTS",
"android.permission.READ_PROFILE",
@ -198,12 +193,8 @@ public class ApkVerifierTest {
"android.permission.READ_SYNC_SETTINGS",
"android.permission.WRITE_SYNC_SETTINGS",
"android.permission.WRITE_CALL_LOG", // implied-permission!
"android.permission.READ_CALL_LOG"// implied-permission!
));
if (Build.VERSION.SDK_INT >= 29) {
expectedSet.add("android.permission.ACCESS_MEDIA_LOCATION");
}
apk.requestedPermissions = expectedSet.toArray(new String[0]);
"android.permission.READ_CALL_LOG", // implied-permission!
};
Uri uri = Uri.fromFile(sdk14Apk);
@ -380,9 +371,6 @@ public class ApkVerifierTest {
"android.permission.MANAGE_ACCOUNTS"
));
}
if (Build.VERSION.SDK_INT >= 29) {
expectedSet.add("android.permission.ACCESS_MEDIA_LOCATION");
}
Apk apk = actualDetails.apks.get(1);
Log.i(TAG, "APK: " + apk.apkName);
HashSet<String> actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
@ -419,9 +407,6 @@ public class ApkVerifierTest {
"org.dmfs.permission.READ_TASKS",
"org.dmfs.permission.WRITE_TASKS"
));
if (Build.VERSION.SDK_INT >= 29) {
expectedSet.add("android.permission.ACCESS_MEDIA_LOCATION");
}
expectedPermissions = expectedSet.toArray(new String[expectedSet.size()]);
apk = actualDetails.apks.get(2);
Log.i(TAG, "APK: " + apk.apkName);

View File

@ -1,19 +1,16 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.fdroid.fdroid.FDroidApp;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertTrue;
@ -26,7 +23,7 @@ public class BonjourManagerTest {
@Test
public void testStartStop() throws InterruptedException {
Context context = ApplicationProvider.getApplicationContext();
Context context = InstrumentationRegistry.getTargetContext();
FDroidApp.ipAddressString = LOCALHOST;
FDroidApp.port = PORT;
@ -68,7 +65,7 @@ public class BonjourManagerTest {
@Test
public void testRestart() throws InterruptedException {
Context context = ApplicationProvider.getApplicationContext();
Context context = InstrumentationRegistry.getTargetContext();
FDroidApp.ipAddressString = LOCALHOST;
FDroidApp.port = PORT;

View File

@ -1,21 +1,19 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Netstat;
import org.fdroid.fdroid.Utils;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -42,7 +40,7 @@ public class LocalHTTPDManagerTest {
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
context = InstrumentationRegistry.getTargetContext();
lbm = LocalBroadcastManager.getInstance(context);
FDroidApp.ipAddressString = LOCALHOST;
@ -66,7 +64,6 @@ public class LocalHTTPDManagerTest {
lbm.unregisterReceiver(errorReceiver);
}
@Ignore
@Test
public void testStartStop() throws InterruptedException {
Log.i(TAG, "testStartStop");

View File

@ -6,8 +6,8 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Looper;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.text.TextUtils;
import android.util.Log;
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.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.nearby.LocalHTTPD;
import org.fdroid.fdroid.nearby.LocalRepoKeyStore;
import org.fdroid.fdroid.nearby.LocalRepoManager;
import org.fdroid.fdroid.nearby.LocalRepoService;
import org.junit.Ignore;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.net.LocalHTTPD;
import org.junit.Test;
import java.io.File;
@ -53,9 +52,8 @@ public class 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
public void testSwap()
throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException {

View File

@ -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());
}
*/
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -1,7 +1,3 @@
-dontoptimize
-dontwarn
-dontobfuscate
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontnote junit.framework.**
@ -18,8 +14,3 @@
-keep class junit.** { *; }
-dontwarn junit.**
# This is necessary so that RemoteWorkManager can be initialized (also marked with @Keep)
-keep class androidx.work.multiprocess.RemoteWorkManagerClient {
public <init>(...);
}

View File

@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.content.Context;

View File

@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.nearby.peers;
package org.fdroid.fdroid.localrepo.peers;
import org.fdroid.fdroid.data.NewRepoConfig;

View File

@ -1,5 +0,0 @@
package org.fdroid.fdroid.nearby;
public class LocalRepoManager {
public static final String[] WEB_ROOT_ASSET_FILES = {};
}

View File

@ -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) {
}
}

View File

@ -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");
}
}

View File

@ -17,11 +17,11 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.net;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.Nullable;
import android.support.annotation.Nullable;
/**
* Dummy version for basic app flavor.

View File

@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.net.bluetooth;
/**
* Dummy version for basic app flavor.

View File

@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.panic;
package org.fdroid.fdroid.views.hiding;
import android.content.Context;

View File

@ -19,11 +19,11 @@
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 androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.views.PreferencesFragment;
import org.fdroid.fdroid.views.updates.UpdatesViewBinder;
@ -49,10 +49,10 @@ class MainViewController extends RecyclerView.ViewHolder {
}
/**
* @see LatestViewBinder
* @see WhatsNewViewBinder
*/
public void bindLatestView() {
new LatestViewBinder(activity, frame);
public void bindWhatsNewView() {
new WhatsNewViewBinder(activity, frame);
}
/**

View File

@ -1,9 +1,10 @@
package org.fdroid.fdroid.views.main;
import android.content.Context;
import android.app.Activity;
import android.content.Intent;
class NearbyViewBinder {
public static void updateUsbOtg(Context context) {
static void onActivityResult(Activity activity, Intent data) {
throw new IllegalStateException("unimplemented");
}
}

View File

@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.views.swap;
import android.content.Context;
import android.net.Uri;
@ -26,9 +26,8 @@ import android.net.Uri;
* Dummy version for basic app flavor.
*/
public class SwapWorkflowActivity {
public static final String EXTRA_PREVENT_FURTHER_SWAP_REQUESTS = "preventFurtherSwap";
public static final String EXTRA_CONFIRM = "EXTRA_CONFIRM";
public static void requestSwap(Context context, Uri uri) {
}
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -6,7 +6,7 @@
<!-- android:title and android:icon are set dynamically in MainActivity -->
<item
app:showAsAction="ifRoom|withText"
android:id="@+id/latest"/>
android:id="@+id/whats_new"/>
<item
app:showAsAction="ifRoom|withText"
android:id="@+id/updates"/>

View File

@ -1,25 +1,30 @@
<?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:key="pref_about" />
<android.support.v7.preference.PreferenceScreen android:title="@string/about_title">
<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">
<PreferenceScreen android:title="@string/preference_manage_installed_apps">
<android.support.v7.preference.PreferenceCategory android:title="@string/preference_category__my_apps">
<android.support.v7.preference.PreferenceScreen android:title="@string/preference_manage_installed_apps">
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="@string/applicationId"
android:targetClass="org.fdroid.fdroid.views.installed.InstalledAppsActivity"/>
</PreferenceScreen>
<PreferenceScreen
</android.support.v7.preference.PreferenceScreen>
<android.support.v7.preference.PreferenceScreen
android:title="@string/menu_manage"
android:summary="@string/repositories_summary">
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="@string/applicationId"
android:targetClass="org.fdroid.fdroid.views.ManageReposActivity"/>
</PreferenceScreen>
<PreferenceScreen
</android.support.v7.preference.PreferenceScreen>
<android.support.v7.preference.PreferenceScreen
android:key="installHistory"
android:visible="false"
android:title="@string/install_history"
@ -28,10 +33,10 @@
android:action="android.intent.action.MAIN"
android:targetPackage="@string/applicationId"
android:targetClass="org.fdroid.fdroid.views.InstallHistoryActivity"/>
</PreferenceScreen>
</PreferenceCategory>
</android.support.v7.preference.PreferenceScreen>
</android.support.v7.preference.PreferenceCategory>
<PreferenceCategory android:title="@string/updates">
<android.support.v7.preference.PreferenceCategory android:title="@string/updates">
<org.fdroid.fdroid.views.LiveSeekBarPreference
android:key="overWifi"
android:title="@string/over_wifi"
@ -42,7 +47,7 @@
android:title="@string/over_data"
android:defaultValue="@integer/defaultOverData"
android:layout="@layout/preference_seekbar"/>
<SwitchPreferenceCompat
<SwitchPreference
android:title="@string/update_auto_download"
android:summary="@string/update_auto_download_summary"
android:key="updateAutoDownload"/>
@ -51,13 +56,13 @@
android:title="@string/update_interval"
android:defaultValue="@integer/defaultUpdateInterval"
android:layout="@layout/preference_seekbar"/>
<SwitchPreferenceCompat
<SwitchPreference
android:title="@string/notify"
android:defaultValue="true"
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">
<ListPreference
android:title="@string/pref_language"
@ -68,30 +73,30 @@
android:defaultValue="light"
android:entries="@array/themeNames"
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">
<SwitchPreferenceCompat
<SwitchPreference
android:title="@string/show_incompat_versions"
android:defaultValue="false"
android:key="incompatibleVersions"/>
<SwitchPreferenceCompat
<SwitchPreference
android:title="@string/show_anti_feature_apps"
android:defaultValue="false"
android:key="showAntiFeatureApps"/>
<SwitchPreferenceCompat
<SwitchPreference
android:title="@string/force_touch_apps"
android:defaultValue="false"
android:key="ignoreTouchscreen"/>
</PreferenceCategory>
</android.support.v7.preference.PreferenceCategory>
<PreferenceCategory android:title="@string/proxy">
<SwitchPreferenceCompat
<android.support.v7.preference.PreferenceCategory android:title="@string/proxy">
<SwitchPreference
android:key="useTor"
android:summary="@string/useTorSummary"
android:title="@string/useTor"/>
<SwitchPreferenceCompat
<SwitchPreference
android:defaultValue="false"
android:key="enableProxy"
android:title="@string/enable_proxy_title"
@ -106,24 +111,24 @@
android:title="@string/proxy_port"
android:summary="@string/proxy_port_summary"
android:dependency="enableProxy"/>
</PreferenceCategory>
</android.support.v7.preference.PreferenceCategory>
<PreferenceCategory
<android.support.v7.preference.PreferenceCategory
android:key="pref_category_privacy"
android:title="@string/privacy">
<SwitchPreferenceCompat
<SwitchPreference
android:key="promptToSendCrashReports"
android:title="@string/prompt_to_send_crash_reports"
android:summary="@string/prompt_to_send_crash_reports_summary"
android:defaultValue="true"/>
<SwitchPreferenceCompat
<SwitchPreference
android:defaultValue="false"
android:key="preventScreenshots"
android:summary="@string/preventScreenshots_summary"
android:title="@string/preventScreenshots_title"/>
</PreferenceCategory>
</android.support.v7.preference.PreferenceCategory>
<PreferenceCategory
<android.support.v7.preference.PreferenceCategory
android:title="@string/other"
android:key="pref_category_other">
<ListPreference
@ -132,7 +137,7 @@
android:defaultValue="86400000"
android:entries="@array/keepCacheNames"
android:entryValues="@array/keepCacheValues"/>
<SwitchPreferenceCompat
<SwitchPreference
android:title="@string/expert"
android:defaultValue="false"
android:key="expert"/>
@ -148,12 +153,6 @@
android:summary="@string/keep_install_history_summary"
android:defaultValue="false"
android:dependency="expert"/>
<CheckBoxPreference
android:key="sendToFdroidMetrics"
android:title="@string/send_to_fdroid_metrics"
android:summary="@string/send_to_fdroid_metrics_summary"
android:defaultValue="false"
android:dependency="expert"/>
<CheckBoxPreference
android:key="hideAllNotifications"
android:title="@string/hide_all_notifications"
@ -178,6 +177,6 @@
android:key="privilegedInstaller"
android:persistent="false"
android:dependency="expert"/>
</PreferenceCategory>
</android.support.v7.preference.PreferenceCategory>
</PreferenceScreen>
</android.support.v7.preference.PreferenceScreen>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This file should be outside of release manifest (in this case app/src/mock/Manifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2010-2012 Ciaran Gultnieks
* Copyright (C) 2013-2017 Peter Serwylo
@ -27,16 +26,8 @@
package="org.fdroid.fdroid"
android:installLocation="auto">
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-feature android:name="android.hardware.nfc" android:required="false"/>
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@ -51,10 +42,6 @@
<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"/>
@ -62,39 +49,63 @@
<application>
<activity
android:name=".nearby.SwapWorkflowActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/swap"
android:launchMode="singleTask"
android:name=".views.swap.SwapWorkflowActivity"
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
android:name="android.support.PARENT_ACTIVITY"
android:value=".views.main.MainActivity"/>
</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
android:name=".panic.PanicPreferencesActivity"
android:name=".views.panic.PanicPreferencesActivity"
android:label="@string/panic_settings"
android:parentActivityName=".views.main.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
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>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".views.main.MainActivity" />
</activity>
<activity
android:name=".panic.SelectInstalledAppsActivity"
android:parentActivityName=".panic.PanicPreferencesActivity" />
<activity
android:name=".panic.PanicResponderActivity"
android:name=".views.panic.PanicResponderActivity"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
@ -106,73 +117,20 @@
</intent-filter>
</activity>
<activity
android:name=".panic.ExitActivity"
android:name=".views.panic.ExitActivity"
android:theme="@android:style/Theme.NoDisplay"/>
<activity
android:name=".panic.CalculatorActivity"
android:name=".views.hiding.CalculatorActivity"
android:enabled="false"
android:icon="@mipmap/ic_calculator_launcher"
android:label="@string/hiding_calculator">
android:label="@string/hiding_calculator"
android:theme="@style/AppThemeLight">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name=".nearby.WifiStateChangeReceiver">
<intent-filter>
<action android:name="android.net.wifi.STATE_CHANGE" />
</intent-filter>
</receiver>
<receiver android:name=".receiver.DeviceStorageReceiver">
<intent-filter>
<action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
</intent-filter>
</receiver>
<receiver android:name=".nearby.UsbDeviceAttachedReceiver">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</receiver>
<receiver android:name=".nearby.UsbDeviceDetachedReceiver">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"
android:resource="@xml/device_filter" />
</receiver>
<receiver android:name=".nearby.UsbDeviceMediaMountedReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_EJECT" />
<action android:name="android.intent.action.MEDIA_REMOVED" />
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<action android:name="android.intent.action.MEDIA_BAD_REMOVAL" />
<data android:scheme="content" />
<data android:scheme="file" />
</intent-filter>
</receiver>
<service
android:name=".nearby.WifiStateChangeService"
android:exported="false" />
<service android:name=".nearby.SwapService" />
<service
android:name=".nearby.LocalRepoService"
android:exported="false" />
<service
android:name=".nearby.TreeUriScannerIntentService"
android:exported="false" />
<service
android:name=".nearby.SDCardScannerService"
android:exported="false" />
</application>

View File

@ -4,13 +4,12 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import javax.jmdns.ServiceInfo;
import javax.jmdns.impl.util.ByteWrangler;
import java.net.Inet4Address;
import java.net.Inet6Address;
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.
* In order to make it Parcelable (or Serializable for that matter), there are some package-scope

View File

@ -113,7 +113,7 @@ public class KeyStoreFileManager {
File keystoreFile = new File(keystorePath);
try {
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.
File tmpFile = File.createTempFile(keystoreFile.getName(), null, keystoreFile.getParentFile());
FileOutputStream fos = new FileOutputStream(tmpFile);

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@ -10,17 +10,16 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.R;
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 androidx.localbroadcastmanager.content.LocalBroadcastManager;
/**
* Manage the {@link android.bluetooth.BluetoothAdapter}in a {@link HandlerThread}.
* The start process is in {@link HandlerThread#onLooperPrepared()} so that it is

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.content.Context;
import android.content.Intent;
@ -7,26 +7,22 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.nearby.peers.BonjourPeer;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.HashMap;
import org.fdroid.fdroid.localrepo.peers.BonjourPeer;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.HashMap;
/**
* Manage {@link JmDNS} in a {@link HandlerThread}. The start process is in
@ -123,7 +119,8 @@ public class BonjourManager {
}
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) {
@Override
protected void onLooperPrepared() {

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.content.Context;
import android.content.Intent;
@ -6,17 +6,17 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.net.LocalHTTPD;
import org.fdroid.fdroid.net.WifiStateChangeService;
import java.io.IOException;
import java.net.BindException;
import java.util.Random;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
/**
* Manage {@link LocalHTTPD} in a {@link HandlerThread};
*/

View File

@ -1,8 +1,10 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.content.Context;
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.x500.X500Name;
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.OperatorCreationException;
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.FileInputStream;
import java.io.FileOutputStream;
@ -46,12 +49,6 @@ import java.util.Date;
import java.util.GregorianCalendar;
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
@SuppressWarnings("LineLength")

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@ -10,9 +10,10 @@ import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.IndexUpdater;
@ -49,15 +50,13 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
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
* 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
* and the relevant .apk and icon files available.
*/
@SuppressWarnings("LineLength")
public final class LocalRepoManager {
private static final String TAG = "LocalRepoManager";
@ -66,7 +65,7 @@ public final class LocalRepoManager {
private final AssetManager assetManager;
private final String fdroidPackageName;
public static final String[] WEB_ROOT_ASSET_FILES = {
private static final String[] WEB_ROOT_ASSET_FILES = {
"swap-icon.png",
"swap-tick-done.png",
"swap-tick-not-done.png",
@ -125,7 +124,7 @@ public final class LocalRepoManager {
private String writeFdroidApkToWebroot() {
ApplicationInfo appInfo;
String fdroidClientURL = "https://f-droid.org/F-Droid.apk";
String fdroidClientURL = "https://f-droid.org/FDroid.apk";
try {
appInfo = pm.getApplicationInfo(fdroidPackageName, PackageManager.GET_META_DATA);
@ -349,8 +348,7 @@ public final class LocalRepoManager {
serializer = XmlPullParserFactory.newInstance().newSerializer();
}
public void build(Context context, Map<String, App> apps, OutputStream output)
throws IOException, LocalRepoKeyStore.InitException {
public void build(Context context, Map<String, App> apps, OutputStream output) throws IOException, LocalRepoKeyStore.InitException {
serializer.setOutput(output, "UTF-8");
serializer.startDocument(null, null);
serializer.startTag("", "fdroid");
@ -358,14 +356,12 @@ public final class LocalRepoManager {
// <repo> block
serializer.startTag("", "repo");
serializer.attribute("", "icon", "blah.png");
serializer.attribute("", "name", Preferences.get().getLocalRepoName()
+ " on " + FDroidApp.ipAddressString);
serializer.attribute("", "name", Preferences.get().getLocalRepoName() + " on " + FDroidApp.ipAddressString);
serializer.attribute("", "pubkey", Hasher.hex(LocalRepoKeyStore.get(context).getCertificate()));
long timestamp = System.currentTimeMillis() / 1000L;
serializer.attribute("", "timestamp", String.valueOf(timestamp));
serializer.attribute("", "version", "10");
tag("description", "A local FDroid repo generated from apps installed on "
+ Preferences.get().getLocalRepoName());
tag("description", "A local FDroid repo generated from apps installed on " + Preferences.get().getLocalRepoName());
serializer.endTag("", "repo");
// <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.
*
* @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.
*
* @see IndexXmlBuilder#tag(String, String)
@ -418,7 +414,7 @@ public final class LocalRepoManager {
tag("lastupdated", app.lastUpdated);
tag("name", app.name);
tag("summary", app.summary);
tag("icon", app.iconFromApk);
tag("icon", app.icon);
tag("desc", app.description);
tag("license", "Unknown");
tag("categories", "LocalRepo," + Preferences.get().getLocalRepoName());

View File

@ -1,10 +1,10 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
@ -15,8 +15,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
/**
* Handles setting up and generating the local repo used to swap apps, including
* 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 static final String TAG = "LocalRepoService";
public static final String ACTION_CREATE = "org.fdroid.fdroid.nearby.action.CREATE";
public static final String EXTRA_PACKAGE_NAMES = "org.fdroid.fdroid.nearby.extra.PACKAGE_NAMES";
public static final String ACTION_CREATE = "org.fdroid.fdroid.localrepo.action.CREATE";
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 EXTRA_STATUS = "localRepoStatusExtra";

View File

@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.Manifest;
import android.app.IntentService;
@ -28,10 +28,11 @@ import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.IndexV1Updater;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import java.io.File;
@ -44,11 +45,9 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import androidx.core.content.ContextCompat;
/**
* 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
* 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
@ -56,7 +55,7 @@ import androidx.core.content.ContextCompat;
* "External Storage"
* <p>
* 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}
* permission to be enabled.
*
@ -67,7 +66,7 @@ import androidx.core.content.ContextCompat;
public class SDCardScannerService extends IntentService {
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");
@ -76,10 +75,12 @@ public class SDCardScannerService extends IntentService {
}
public static void scan(Context context) {
if (Preferences.get().isScanRemovableStorageEnabled()) {
Intent intent = new Intent(context, SDCardScannerService.class);
intent.setAction(ACTION_SCAN);
context.startService(intent);
}
}
@Override
protected void onHandleIntent(Intent intent) {

View File

@ -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.PendingIntent;
import android.app.Service;
@ -12,19 +13,16 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Build;
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.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.NotificationHelper;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
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.RepoProvider;
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.WifiStateChangeService;
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
@ -45,12 +46,6 @@ import java.util.Set;
import java.util.Timer;
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
* 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_BLUETOOTH_ENABLED = "bluetoothEnabled";
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_NAME_BEFORE_SWAP = "bluetoothNameBeforeSwap";
private static final String KEY_WIFI_ENABLED_BEFORE_SWAP = "wifiEnabledBeforeSwap";
private static final String KEY_HOTSPOT_ACTIVATED_BEFORE_SWAP = "hotspotEnabledBeforeSwap";
@NonNull
private final Set<String> appsToSwap = new HashSet<>();
@ -78,6 +71,15 @@ public class SwapService extends Service {
private static WifiManager wifiManager;
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) {
Intent intent = new Intent(context, SwapService.class);
context.stopService(intent);
@ -111,6 +113,46 @@ public class SwapService extends Service {
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) {
// 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.
@ -252,14 +294,6 @@ public class SwapService extends Service {
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() {
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();
}
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 final Binder binder = new Binder();
@ -304,15 +330,12 @@ public class SwapService extends Service {
@Nullable
private Timer timer;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public class Binder extends android.os.Binder {
public SwapService getService() {
return SwapService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
startForeground(NOTIFICATION, createNotification());
@ -331,7 +354,7 @@ public class SwapService extends Service {
new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
}
wifiManager = ContextCompat.getSystemService(getApplicationContext(), WifiManager.class);
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager != null) {
SwapService.putWifiEnabledBeforeSwap(wifiManager.isWifiEnabled());
}
@ -347,58 +370,8 @@ public class SwapService extends Service {
localBroadcastManager.registerReceiver(bonjourPeerRemoved, new IntentFilter(BonjourManager.ACTION_REMOVED));
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.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference());
}
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()
);
BonjourManager.setVisible(this, getWifiVisibleUserPreference());
}
/**
@ -409,9 +382,7 @@ public class SwapService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
deleteAllSwapRepos();
Intent startUiIntent = new Intent(this, SwapWorkflowActivity.class);
startUiIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(startUiIntent);
startActivity(new Intent(this, SwapWorkflowActivity.class));
return START_NOT_STICKY;
}
@ -424,8 +395,6 @@ public class SwapService extends Service {
@Override
public void onDestroy() {
compositeDisposable.dispose();
Utils.debugLog(TAG, "Destroying service, will disable swapping if required, and unregister listeners.");
Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
localBroadcastManager.unregisterReceiver(onWifiChange);
@ -434,9 +403,7 @@ public class SwapService extends Service {
localBroadcastManager.unregisterReceiver(bonjourPeerFound);
localBroadcastManager.unregisterReceiver(bonjourPeerRemoved);
if (bluetoothAdapter != null) {
unregisterReceiver(bluetoothScanModeChanged);
}
BluetoothManager.stop(this);
@ -446,21 +413,12 @@ public class SwapService extends Service {
wifiManager.setWifiEnabled(false);
}
WifiApControl ap = WifiApControl.getInstance(this);
if (ap != null) {
if (wasHotspotEnabledBeforeSwap()) {
ap.enable();
} else {
ap.disable();
}
}
stopPollingConnectedSwapRepo();
if (timer != null) {
timer.cancel();
}
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
stopForeground(true);
deleteAllSwapRepos();
@ -471,7 +429,7 @@ public class SwapService extends Service {
Intent intent = new Intent(this, SwapWorkflowActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
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))
.setContentText(getText(R.string.touch_to_configure_local_repo))
.setSmallIcon(R.drawable.ic_nearby)
@ -546,7 +504,7 @@ public class SwapService extends Service {
if (hasIp) {
LocalHTTPDManager.restart(this);
BonjourManager.restart(this);
BonjourManager.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference());
BonjourManager.setVisible(this, getWifiVisibleUserPreference());
} else {
BonjourManager.stop(this);
LocalHTTPDManager.stop(this);

View File

@ -1,16 +1,14 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.ColorInt;
import android.support.annotation.LayoutRes;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import org.fdroid.fdroid.R;
import androidx.annotation.ColorInt;
import androidx.annotation.LayoutRes;
import androidx.core.content.ContextCompat;
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
/**
* 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);
}
/**
* 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) {
super(context, attrs, defStyleAttr);
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();
this(context, attrs, defStyleAttr, 0);
}
@TargetApi(21)
@ -56,7 +43,7 @@ public class SwapView extends RelativeLayout {
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));
getResources().getColor(R.color.swap_blue));
toolbarTitle = a.getString(R.styleable.SwapView_toolbarTitle);
a.recycle();
}

View File

@ -17,25 +17,22 @@
* MA 02110-1301, USA.
*/
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.localrepo;
import android.annotation.TargetApi;
import android.app.IntentService;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.support.v4.provider.DocumentFile;
import android.util.Log;
import android.widget.Toast;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.fdroid.fdroid.AddRepoIntentService;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.IndexV1Updater;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
@ -44,13 +41,10 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import androidx.documentfile.provider.DocumentFile;
/**
* An {@link IntentService} subclass for handling asynchronous scanning of a
* 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
* 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}.
* {@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/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/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 static final String TAG = "TreeUriScannerIntentSer";
private static final String ACTION_SCAN_TREE_URI = "org.fdroid.fdroid.nearby.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";
private static final String ACTION_SCAN_TREE_URI = "org.fdroid.fdroid.localrepo.action.SCAN_TREE_URI";
public TreeUriScannerIntentService() {
super("TreeUriScannerIntentService");
}
public static void scan(Context context, Uri data) {
if (Preferences.get().isScanRemovableStorageEnabled()) {
Intent intent = new Intent(context, TreeUriScannerIntentService.class);
intent.setAction(ACTION_SCAN_TREE_URI);
intent.setData(data);
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
@ -129,34 +95,20 @@ public class TreeUriScannerIntentService extends IntentService {
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) {
DocumentFile[] documentFiles = documentFileDir.listFiles();
if (documentFiles == null) {
return;
}
boolean foundIndex = false;
ArrayList<DocumentFile> dirs = new ArrayList<>();
for (DocumentFile documentFile : documentFiles) {
if (documentFile.isDirectory()) {
dirs.add(documentFile);
} else if (!foundIndex) {
searchDirectory(documentFile);
} else {
if (IndexV1Updater.SIGNED_FILE_NAME.equals(documentFile.getName())) {
registerRepo(documentFile);
foundIndex = true;
}
}
}
for (DocumentFile dir : dirs) {
searchDirectory(dir);
}
}
/**
@ -171,7 +123,9 @@ public class TreeUriScannerIntentService extends IntentService {
private void registerRepo(DocumentFile index) {
InputStream inputStream = null;
try {
Log.i(TAG, "FOUND: " + index.getUri());
inputStream = getContentResolver().openInputStream(index.getUri());
Log.i(TAG, "repo URL: " + index.getParentFile().getUri());
registerRepo(this, inputStream, index.getParentFile().getUri());
} catch (IOException | IndexUpdater.SigningException e) {
e.printStackTrace();
@ -191,6 +145,7 @@ public class TreeUriScannerIntentService extends IntentService {
JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexV1Updater.DATA_FILE_NAME);
IOUtils.readLines(jarFile.getInputStream(indexEntry));
Certificate certificate = IndexUpdater.getSigningCertFromJar(indexEntry);
Log.i(TAG, "Got certificate: " + certificate);
String fingerprint = Utils.calcFingerprint(certificate);
Log.i(TAG, "Got fingerprint: " + fingerprint);
destFile.delete();

View File

@ -1,14 +1,12 @@
package org.fdroid.fdroid.nearby.peers;
package org.fdroid.fdroid.localrepo.peers;
import android.bluetooth.BluetoothClass.Device;
import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.fdroid.fdroid.R;
import androidx.annotation.Nullable;
public class BluetoothPeer implements Peer {
private static final String BLUETOOTH_NAME_TAG = "FDroid:";
@ -46,7 +44,7 @@ public class BluetoothPeer implements Peer {
@Override
public int getIcon() {
return R.drawable.ic_bluetooth;
return R.drawable.ic_bluetooth_white;
}
@Override

View File

@ -1,16 +1,14 @@
package org.fdroid.fdroid.nearby.peers;
package org.fdroid.fdroid.localrepo.peers;
import android.net.Uri;
import android.os.Parcel;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.fdroid.fdroid.FDroidApp;
import javax.jmdns.ServiceInfo;
import javax.jmdns.impl.FDroidServiceInfo;
import androidx.annotation.Nullable;
public class BonjourPeer extends WifiPeer {
private static final String TAG = "BonjourPeer";

View File

@ -1,8 +1,7 @@
package org.fdroid.fdroid.nearby.peers;
package org.fdroid.fdroid.localrepo.peers;
import android.os.Parcelable;
import androidx.annotation.DrawableRes;
import android.support.annotation.DrawableRes;
/**
* TODO This model assumes that "peers" from Bluetooth, Bonjour, and WiFi are

View File

@ -1,9 +1,8 @@
package org.fdroid.fdroid.nearby.peers;
package org.fdroid.fdroid.localrepo.peers;
import android.net.Uri;
import android.os.Parcel;
import android.text.TextUtils;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.NewRepoConfig;
@ -63,7 +62,7 @@ public class WifiPeer implements Peer {
@Override
public int getIcon() {
return R.drawable.ic_wifi;
return R.drawable.ic_network_wifi_white;
}
@Override

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.net;
/*
* #%L
@ -35,9 +35,13 @@ package org.fdroid.fdroid.nearby;
import android.content.Context;
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.localrepo.LocalRepoKeyStore;
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -60,11 +64,6 @@ import java.util.Map;
import java.util.StringTokenizer;
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.
* The only changes were to remove unneeded extras like {@code main()}, the

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.net;
import android.app.IntentService;
import android.content.ComponentName;
@ -6,25 +6,22 @@ import android.content.Context;
import android.content.Intent;
import android.net.DhcpInfo;
import android.net.NetworkInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
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.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.Utils;
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.InetAddress;
@ -35,14 +32,11 @@ import java.security.cert.Certificate;
import java.util.Enumeration;
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.
* 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
* via {@link WifiStateChangeReceiver}, in
* via {@link org.fdroid.fdroid.receiver.WifiStateChangeReceiver}, in
* which case an instance of {@link NetworkInfo} is included.
* <p>
* 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 wifiState;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public WifiStateChangeService() {
super("WifiStateChangeService");
}
@ -86,12 +78,6 @@ public class WifiStateChangeService extends IntentService {
context.startService(intent);
}
@Override
public void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
}
@Override
protected void onHandleIntent(Intent intent) {
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.");
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
wifiManager = ContextCompat.getSystemService(getApplicationContext(), WifiManager.class);
wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
wifiState = wifiManager.getWifiState();
if (ni == null || ni.isConnected()) {
Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState));
if (ni == null
|| ni.getState() == NetworkInfo.State.CONNECTED || ni.getState() == NetworkInfo.State.DISCONNECTED) {
if (previousWifiState != wifiState &&
(wifiState == WifiManager.WIFI_STATE_ENABLED
|| 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) {
compositeDisposable.add(UpdateService.scheduleIfStillOnWifi(this).subscribe());
UpdateService.scheduleIfStillOnWifi(this);
}
}
}
public class WifiInfoThread extends Thread {
private static final String TAG = "WifiInfoThread";
@Override
public void run() {
@ -143,7 +129,7 @@ public class WifiStateChangeService extends IntentService {
if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
wifiInfo = wifiManager.getConnectionInfo();
FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress());
setSsid(wifiInfo);
setSsidFromWifiInfo(wifiInfo);
DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
if (dhcpInfo != null) {
String netmask = formatIpAddress(dhcpInfo.netmask);
@ -161,13 +147,14 @@ public class WifiStateChangeService extends IntentService {
setIpInfoFromNetworkInterface();
}
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|| wifiState == WifiManager.WIFI_STATE_DISABLING
|| wifiState == WifiManager.WIFI_STATE_UNKNOWN) {
|| wifiState == WifiManager.WIFI_STATE_DISABLING) {
// try once to see if its a hotspot
setIpInfoFromNetworkInterface();
if (FDroidApp.ipAddressString == null) {
return;
}
} else { // a hotspot can be active during WIFI_STATE_UNKNOWN
setIpInfoFromNetworkInterface();
}
if (retryCount > 120) {
@ -184,7 +171,7 @@ public class WifiStateChangeService extends IntentService {
return;
}
setSsid(wifiInfo);
setSsidFromWifiInfo(wifiInfo);
String scheme;
if (Preferences.get().isLocalRepoHttpsEnabled()) {
@ -239,47 +226,16 @@ public class WifiStateChangeService extends IntentService {
}
}
private void setSsid(WifiInfo wifiInfo) {
if (wifiInfo != null && wifiInfo.getBSSID() != null) {
private void setSsidFromWifiInfo(WifiInfo wifiInfo) {
if (wifiInfo != null) {
String ssid = wifiInfo.getSSID();
Utils.debugLog(TAG, "Have wifi info, connected to " + ssid);
if (ssid == null) {
FDroidApp.ssid = getString(R.string.swap_blank_wifi_ssid);
} else {
if (ssid != null) {
FDroidApp.ssid = ssid.replaceAll("^\"(.*)\"$", "$1");
}
FDroidApp.bssid = wifiInfo.getBSSID();
} else {
WifiApControl wifiApControl = null;
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;
String bssid = wifiInfo.getBSSID();
if (bssid != null) {
FDroidApp.bssid = bssid;
}
}
}

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.net.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;

View File

@ -1,14 +1,14 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.net.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import android.webkit.MimeTypeMap;
import fi.iki.elonen.NanoHTTPD;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.nearby.httpish.Request;
import org.fdroid.fdroid.nearby.httpish.Response;
import org.fdroid.fdroid.net.bluetooth.httpish.Request;
import org.fdroid.fdroid.net.bluetooth.httpish.Response;
import java.io.File;
import java.io.FileInputStream;
@ -20,8 +20,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import fi.iki.elonen.NanoHTTPD;
/**
* Act as a layer on top of LocalHTTPD server, by forwarding requests served
* over bluetooth to that server.

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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();
}
});
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,11 +1,11 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.net.WifiStateChangeService;
public class WifiStateChangeReceiver extends BroadcastReceiver {
private static final String TAG = "WifiStateChangeReceiver";

View File

@ -1,21 +1,18 @@
package org.fdroid.fdroid.panic;
package org.fdroid.fdroid.views.hiding;
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.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.appbar.MaterialToolbar;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
import java.util.regex.Pattern;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
/**
* A very hacky calculator which is barely functional.
* It is just meant to pass a very casual inspection.
@ -31,21 +28,17 @@ public class CalculatorActivity extends AppCompatActivity {
// unary operators
private static final String PERCENT = "%";
@Nullable
private String lastOp;
private @Nullable String lastOp;
// views
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
FDroidApp fdroidApp = (FDroidApp) getApplication();
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calculator);
MaterialToolbar toolbar = findViewById(R.id.toolbar);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
textView = (TextView) findViewById(R.id.textView);

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.panic;
package org.fdroid.fdroid.views.hiding;
import android.content.ComponentName;
import android.content.Context;
@ -7,14 +7,12 @@ import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
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.R;
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
* and restoring it.

View File

@ -3,44 +3,40 @@ package org.fdroid.fdroid.views.main;
import android.content.Intent;
import android.database.Cursor;
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.widget.FrameLayout;
import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.CategoryProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.panic.HidingManager;
import org.fdroid.fdroid.views.apps.AppListActivity;
import org.fdroid.fdroid.views.categories.CategoryAdapter;
import org.fdroid.fdroid.views.categories.CategoryController;
import org.fdroid.fdroid.views.hiding.HidingManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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.
* Will start a loader to get the list of categories from the database and populate a recycler
* view with relevant info about each.
*/
class CategoriesViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String TAG = "CategoriesViewBinder";
private static final int LOADER_ID = 429820532;
@ -96,11 +92,10 @@ class CategoriesViewBinder implements LoaderManager.LoaderCallbacks<Cursor> {
activity.getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
}
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id != LOADER_ID) {
throw new IllegalArgumentException("id != LOADER_ID");
return null;
}
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}.
* <p>
*
* 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
* will grow so large as to make this a performance concern. If it does in the future, the

View File

@ -1,16 +1,14 @@
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 org.fdroid.fdroid.R;
import org.fdroid.fdroid.views.PreferencesFragment;
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
* 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() {
new LatestViewBinder(activity, frame);
public void bindWhatsNewView() {
new WhatsNewViewBinder(activity, frame);
}
/**

View File

@ -1,19 +1,14 @@
package org.fdroid.fdroid.views.main;
import android.Manifest;
import android.content.Context;
import android.app.Activity;
import android.content.Intent;
import android.content.UriPermission;
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.Environment;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@ -21,20 +16,12 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.nearby.SDCardScannerService;
import org.fdroid.fdroid.nearby.SwapService;
import org.fdroid.fdroid.nearby.TreeUriScannerIntentService;
import org.fdroid.fdroid.localrepo.SDCardScannerService;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.TreeUriScannerIntentService;
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
@ -61,18 +48,15 @@ import androidx.core.content.ContextCompat;
* write access to the the removable storage.
*
* @see TreeUriScannerIntentService
* @see org.fdroid.fdroid.nearby.SDCardScannerService
* <p>
* TODO use {@link StorageManager#registerStorageVolumeCallback(Executor, StorageManager.StorageVolumeCallback)}
* @see org.fdroid.fdroid.localrepo.SDCardScannerService
*/
public class NearbyViewBinder {
class NearbyViewBinder {
public static final String TAG = "NearbyViewBinder";
private static File externalStorage = null;
private static View swapView;
static File externalStorage = null;
NearbyViewBinder(final AppCompatActivity activity, FrameLayout parent) {
swapView = activity.getLayoutInflater().inflate(R.layout.main_tab_swap, parent, true);
NearbyViewBinder(final Activity activity, FrameLayout parent) {
View swapView = activity.getLayoutInflater().inflate(R.layout.main_tab_swap, parent, true);
TextView subtext = swapView.findViewById(R.id.both_parties_need_fdroid_text);
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},
MainActivity.REQUEST_LOCATION_PERMISSIONS);
} 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);
}
}
});
}
}
}
}

View File

@ -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.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class ExitActivity extends AppCompatActivity {
@Override

View File

@ -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);
}
}

View File

@ -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.DialogInterface;
import android.content.Intent;
@ -7,57 +8,49 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.LightingColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
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.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.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 {
private static final String PREF_EXIT = Preferences.PREF_PANIC_EXIT;
private static final String PREF_APP = "pref_panic_app";
private static final String PREF_HIDE = Preferences.PREF_PANIC_HIDE;
private PackageManager pm;
private ListPreference prefApp;
private CheckBoxPreference prefExit;
private CheckBoxPreference prefHide;
private CheckBoxPreference prefResetRepos;
private PreferenceCategory categoryAppsToUninstall;
@Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.preferences_panic);
pm = getActivity().getPackageManager();
prefExit = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_EXIT);
prefExit = (CheckBoxPreference) findPreference(PREF_EXIT);
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)));
prefResetRepos = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_RESET_REPOS);
categoryAppsToUninstall = (PreferenceCategory) findPreference("pref_panic_apps_to_uninstall");
if (PanicResponder.checkForDisconnectIntent(getActivity())) {
// 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)) {
prefHide.setChecked(false);
prefHide.setEnabled(false);
prefResetRepos.setChecked(false);
prefResetRepos.setEnabled(false);
getActivity().setResult(AppCompatActivity.RESULT_CANCELED);
getActivity().setResult(Activity.RESULT_CANCELED);
} else {
prefHide.setEnabled(true);
prefResetRepos.setEnabled(true);
}
showPanicApp(packageName);
return true;
@ -98,48 +88,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
public void onStart() {
super.onStart();
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
@ -150,13 +98,11 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(Preferences.PREF_PANIC_HIDE)
&& sharedPreferences.getBoolean(Preferences.PREF_PANIC_HIDE, false)) {
if (key.equals(PREF_HIDE) && sharedPreferences.getBoolean(PREF_HIDE, false)) {
showHideConfirmationDialog();
}
// disable "hiding" if "exit" gets disabled
if (key.equals(Preferences.PREF_PANIC_EXIT)
&& !sharedPreferences.getBoolean(Preferences.PREF_PANIC_EXIT, true)) {
if (key.equals(PREF_EXIT) && !sharedPreferences.getBoolean(PREF_EXIT, true)) {
prefHide.setChecked(false);
}
}
@ -210,7 +156,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
// disable destructive panic actions
prefHide.setEnabled(false);
showWipeList();
} else {
// try to display connected panic app
try {
@ -218,8 +163,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
prefApp.setSummary(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
prefApp.setIcon(pm.getApplicationIcon(packageName));
prefHide.setEnabled(true);
prefResetRepos.setEnabled(true);
showWipeList();
} catch (PackageManager.NameNotFoundException e) {
// revert back to no app, just to be safe
PanicResponder.setTriggerPackageName(getActivity(), Panic.PACKAGE_NAME_NONE);
@ -234,13 +177,13 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
public void onClick(DialogInterface dialogInterface, int i) {
PanicResponder.setTriggerPackageName(getActivity());
showPanicApp(PanicResponder.getTriggerPackageName(getActivity()));
getActivity().setResult(AppCompatActivity.RESULT_OK);
getActivity().setResult(Activity.RESULT_OK);
}
};
DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
getActivity().setResult(AppCompatActivity.RESULT_CANCELED);
getActivity().setResult(Activity.RESULT_CANCELED);
getActivity().finish();
}
};
@ -300,7 +243,6 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
@Override
public void onCancel(DialogInterface dialogInterface) {
prefHide.setChecked(false);
prefResetRepos.setChecked(false);
}
});
builder.setView(R.layout.dialog_app_hiding);

View File

@ -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();
}
}

View File

@ -1,12 +1,19 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.views.swap;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
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.util.AttributeSet;
import android.view.ContextThemeWrapper;
@ -19,18 +26,12 @@ import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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 org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.SwapView;
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);
adapter = new AppListAdapter(listView, getContext(),
getContext().getContentResolver().query(InstalledAppProvider.getContentUri(),
null, null, null, null));
InstalledAppTable.Cols.ALL, null, null, null));
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
@ -96,7 +97,13 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
} else {
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
@ -140,14 +147,14 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
private LayoutInflater getInflater(Context context) {
if (inflater == null) {
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;
}
private Drawable getDefaultAppIcon(Context context) {
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;
}
@ -199,6 +206,8 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
}
});
}
updateCheckedIndicatorView(view, listView.isItemChecked(listPosition));
}
public void updateCheckedIndicatorView(int position, boolean checked) {
@ -207,7 +216,26 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
if (position >= firstListItemPosition && position <= lastListItemPosition) {
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);
}
}
}
}

View File

@ -1,4 +1,4 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.views.swap;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
@ -7,6 +7,9 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.util.AttributeSet;
import android.view.LayoutInflater;
@ -19,20 +22,18 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import cc.mvdan.accesspoint.WifiApControl;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
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 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")
public class StartSwapView extends SwapView {
private static final String TAG = "StartSwapView";
@ -70,7 +71,7 @@ public class StartSwapView extends SwapView {
Peer peer = getItem(position);
((TextView) convertView.findViewById(R.id.peer_name)).setText(peer.getName());
((ImageView) convertView.findViewById(R.id.icon))
.setImageDrawable(ContextCompat.getDrawable(getContext(), peer.getIcon()));
.setImageDrawable(getResources().getDrawable(peer.getIcon()));
return convertView;
}
@ -79,7 +80,7 @@ public class StartSwapView extends SwapView {
@Nullable /* Emulators typically don't have bluetooth adapters */
private final BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
private SwitchMaterial bluetoothSwitch;
private SwitchCompat bluetoothSwitch;
private TextView viewBluetoothId;
private TextView textBluetoothVisible;
private TextView viewWifiId;
@ -175,7 +176,7 @@ public class StartSwapView extends SwapView {
textBluetoothVisible = findViewById(R.id.bluetooth_visible);
bluetoothSwitch = (SwitchMaterial) findViewById(R.id.switch_bluetooth);
bluetoothSwitch = (SwitchCompat) findViewById(R.id.switch_bluetooth);
bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled);
bluetoothSwitch.setChecked(SwapService.getBluetoothVisibleUserPreference());
bluetoothSwitch.setEnabled(true);
@ -222,17 +223,7 @@ public class StartSwapView extends SwapView {
WifiApControl wifiAp = WifiApControl.getInstance(getActivity());
if (wifiAp != null && wifiAp.isWifiApEnabled()) {
WifiConfiguration config = wifiAp.getConfiguration();
TextView textWifiVisible = findViewById(R.id.wifi_visible);
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));
}
viewWifiNetwork.setText(getContext().getString(R.string.swap_active_hotspot, config.SSID));
} else if (TextUtils.isEmpty(FDroidApp.ssid)) {
// not connected to or setup with any wifi network
viewWifiNetwork.setText(R.string.swap_no_wifi_network);

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.views.swap;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -11,6 +12,13 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
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.util.AttributeSet;
import android.util.Log;
@ -23,9 +31,7 @@ import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
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.installer.InstallManagerService;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.localrepo.SwapView;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.DownloaderService;
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
* 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()) {
@Override
public void onChange(boolean selfChange) {
AppCompatActivity activity = getActivity();
Activity activity = getActivity();
if (activity != null && app != null) {
app = AppProvider.Helper.findSpecificApp(
activity.getContentResolver(),
@ -313,7 +310,7 @@ public class SwapSuccessView extends SwapView implements LoaderManager.LoaderCal
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()) {
btnInstall.setText(R.string.menu_upgrade);
@ -365,7 +362,7 @@ public class SwapSuccessView extends SwapView implements LoaderManager.LoaderCal
@NonNull
private LayoutInflater getInflater(Context context) {
if (inflater == null) {
inflater = ContextCompat.getSystemService(context, LayoutInflater.class);
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
return inflater;
}

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.nearby;
package org.fdroid.fdroid.views.swap;
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@ -16,6 +17,17 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
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.util.Log;
import android.view.LayoutInflater;
@ -33,22 +45,9 @@ import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
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 cc.mvdan.accesspoint.WifiApControl;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp;
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.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.nearby.peers.BluetoothPeer;
import org.fdroid.fdroid.nearby.peers.Peer;
import org.fdroid.fdroid.localrepo.BluetoothManager;
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.Downloader;
import org.fdroid.fdroid.net.HttpDownloader;
import org.fdroid.fdroid.net.WifiStateChangeService;
import org.fdroid.fdroid.qr.CameraCharacteristicsChecker;
import org.fdroid.fdroid.qr.QrGenAsyncTask;
import org.fdroid.fdroid.views.main.MainActivity;
import java.util.Date;
@ -75,9 +82,6 @@ import java.util.Set;
import java.util.Timer;
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;
/**
@ -106,21 +110,18 @@ public class SwapWorkflowActivity extends AppCompatActivity {
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 MaterialToolbar toolbar;
private Toolbar toolbar;
private SwapView currentView;
private boolean hasPreparedLocalRepo;
private boolean newIntent;
private NewRepoConfig confirmSwapConfig;
private LocalBroadcastManager localBroadcastManager;
private WifiManager wifiManager;
private WifiApControl wifiApControl;
private BluetoothAdapter bluetoothAdapter;
@LayoutRes
private int currentSwapViewLayoutRes = STEP_INTRO;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public static void requestSwap(Context context, String repo) {
requestSwap(context, Uri.parse(repo));
}
@ -202,11 +203,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
FDroidApp fdroidApp = (FDroidApp) getApplication();
fdroidApp.setSecureWindow(this);
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
((FDroidApp) getApplication()).setSecureWindow(this);
super.onCreate(savedInstanceState);
currentView = new SwapView(this); // dummy placeholder to avoid NullPointerExceptions;
@ -219,7 +216,8 @@ public class SwapWorkflowActivity extends AppCompatActivity {
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);
container = (ViewGroup) findViewById(R.id.container);
@ -228,8 +226,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
localBroadcastManager.registerReceiver(downloaderInterruptedReceiver,
new IntentFilter(Downloader.ACTION_INTERRUPTED));
wifiManager = ContextCompat.getSystemService(getApplicationContext(), WifiManager.class);
wifiApControl = WifiApControl.getInstance(this);
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@ -238,7 +235,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
compositeDisposable.dispose();
localBroadcastManager.unregisterReceiver(downloaderInterruptedReceiver);
unbindService(serviceConnection);
super.onDestroy();
@ -277,7 +273,8 @@ public class SwapWorkflowActivity extends AppCompatActivity {
CharSequence title = getString(titleResId);
next.setTitle(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() {
@Override
public boolean onMenuItemClick(MenuItem item) {
@ -306,8 +303,8 @@ public class SwapWorkflowActivity extends AppCompatActivity {
SearchView searchView = new SearchView(this);
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
searchMenuItem.setActionView(searchView);
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
MenuItemCompat.setActionView(searchMenuItem, searchView);
MenuItemCompat.setShowAsAction(searchMenuItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@ -445,19 +442,12 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
private void setupWifiAP() {
if (wifiApControl == null) {
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());
WifiApControl ap = WifiApControl.getInstance(this);
wifiManager.setWifiEnabled(false);
if (wifiApControl.enable()) {
if (ap.enable()) {
Toast.makeText(this, R.string.swap_toast_hotspot_enabled, Toast.LENGTH_SHORT).show();
SwapService.putHotspotActivatedUserPreference(true);
} else {
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.");
}
}
@ -493,13 +483,14 @@ public class SwapWorkflowActivity extends AppCompatActivity {
getSwapService().initTimer();
container.removeAllViews();
View view = ContextCompat.getSystemService(this, LayoutInflater.class)
.inflate(viewRes, container, false);
View view = ((LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(viewRes, container, false);
currentView = (SwapView) view;
currentView.setLayoutResId(viewRes);
currentSwapViewLayoutRes = viewRes;
toolbar.setBackgroundColor(currentView.getToolbarColour());
toolbar.setTitle(currentView.getToolbarTitle());
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -597,7 +588,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
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
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
if (scanResult != null) {
if (scanResult.getContents() != null) {
@ -779,7 +769,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
private final BroadcastReceiver bluetoothScanModeChanged = new BroadcastReceiver() {
@Override
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);
if (bluetoothSwitch == null || textBluetoothVisible == null
|| !BluetoothManager.ACTION_STATUS.equals(intent.getAction())) {
@ -933,14 +923,10 @@ public class SwapWorkflowActivity extends AppCompatActivity {
ImageView qrImage = container.findViewById(R.id.wifi_qr_code);
if (qrUriString != null && qrImage != null) {
Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString);
compositeDisposable.add(Utils.generateQrBitmap(this, qrUriString)
.subscribe(qrBitmap -> {
qrImage.setImageBitmap(qrBitmap);
new QrGenAsyncTask(SwapWorkflowActivity.this, R.id.wifi_qr_code).execute(qrUriString);
// Replace all blacks with the background blue.
qrImage.setColorFilter(new LightingColorFilter(0xffffffff,
ContextCompat.getColor(this, R.color.swap_blue)));
qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue)));
final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner);
if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) {
@ -948,8 +934,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
} else {
qrWarningMessage.setVisibility(View.VISIBLE);
}
})
);
}
}
@ -968,19 +952,19 @@ public class SwapWorkflowActivity extends AppCompatActivity {
if (TextUtils.isEmpty(FDroidApp.bssid) && !TextUtils.isEmpty(FDroidApp.ipAddressString)) {
// empty bssid with an ipAddress means hotspot mode
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);
tapView.setText(R.string.swap_switch_to_wifi);
} else if (TextUtils.isEmpty(FDroidApp.ssid)) {
// not connected to or setup with any wifi network
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);
tapView.setText(R.string.swap_view_available_networks);
} else {
// connected to a regular wifi network
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);
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() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Context context = getApplicationContext();
if (isChecked) {
if (wifiApControl != null && wifiApControl.isEnabled()) {
setupWifiAP();
} else {
wifiManager.setWifiEnabled(true);
}
BonjourManager.start(context);
}
BonjourManager.setVisible(context, isChecked);
@ -1060,11 +1040,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
peopleNearbyProgress.setVisibility(View.VISIBLE);
break;
case BonjourManager.STATUS_VISIBLE:
if (wifiApControl != null && wifiApControl.isEnabled()) {
textWifiVisible.setText(R.string.swap_visible_hotspot);
} else {
textWifiVisible.setText(R.string.swap_visible_wifi);
}
peopleNearbyText.setText(R.string.swap_scanning_for_peers);
peopleNearbyText.setVisibility(View.VISIBLE);
peopleNearbyProgress.setVisibility(View.VISIBLE);
@ -1120,7 +1096,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
private final BroadcastReceiver bluetoothStatus = new BroadcastReceiver() {
@Override
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 textDeviceIdBluetooth = container.findViewById(R.id.device_id_bluetooth);
TextView peopleNearbyText = container.findViewById(R.id.text_people_nearby);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

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