Compare commits

..

1 Commits
master ... 1.10

Author SHA1 Message Date
Hans-Christoph Steiner
9707518c13
removing all translations less than 70% complete
20%  ber  Berber
18%  bn_BD  Bengali (Bangladesh)
37%  es_MX  Spanish (Mexico)
49%  hy  Armenian
61%  kab  Kabyle
65%  kn  Kannada
45%  lv  Latvian
43%  mk  Macedonian
 6%  mn  Mongolian
46%  my  Burmese
20%  pa  Punjabi
55%  sn  Shona
37%  ta  Tamil
35%  th  Thai
29%  tzm  Central Atlas Tamazight
49%  ug  Uyghur
12%  ur  Urdu
68%  zh_Hant_HK  Chinese (Traditional, Hong Kong)
2020-10-22 11:13:38 +02:00
946 changed files with 9690 additions and 21569 deletions

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; echo ERROR !!)
- ./gradlew checkstyle || (export EXITVALUE=1; echo ERROR !!)
- ./tools/check-format-strings.py || (export EXITVALUE=1; echo ERROR !!)
- ./tools/check-fastlane-whitespace.py || (export EXITVALUE=1; echo ERROR !!)
- ./tools/remove-unused-and-blank-translations.py || (export EXITVALUE=1; echo ERROR !!)
- echo "These are unused or blank translations that should be removed:"
- git --no-pager diff --ignore-all-space --name-only --exit-code app/src/*/res/values*/strings.xml || set_error
- git --no-pager diff --ignore-all-space --name-only --exit-code app/src/*/res/values*/strings.xml || (export EXITVALUE=1; echo ERROR !!)
- 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
# 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}'`
@ -95,14 +74,16 @@ errorprone:
- 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;
export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=android.support.test.filters.LargeTest;
fi
- ./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 +91,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 29 default x86_64:
<<: *kvm-template
deploy_nightly:
extends: .base
stage: deploy
only:
- master
@ -140,3 +139,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,76 +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

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,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

@ -21,16 +21,16 @@ def basicApplicationId = "org.fdroid.basic"
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
android {
compileSdkVersion 30
compileSdkVersion 28
defaultConfig {
versionCode 1013001
versionCode 1010050
versionName getVersionName()
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
minSdkVersion 24
minSdkVersion 14
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 28
targetSdkVersion 25
/*
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 +38,6 @@ android {
passing the following argument to the test runner: disableAnalytics "true".
*/
testInstrumentationRunnerArguments disableAnalytics: 'true'
vectorDrawables.useSupportLibrary = true
}
buildTypes {
@ -76,8 +75,6 @@ android {
compileOptions {
compileOptions.encoding = "UTF-8"
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
aaptOptions {
@ -102,10 +99,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,29 +135,28 @@ android {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.annotation:annotation:1.1.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.google.android.material:material:1.1.0'
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
implementation 'com.google.zxing:core:3.3.3'
implementation 'info.guardianproject.netcipher:netcipher:2.2.0-alpha'
implementation 'info.guardianproject.netcipher:netcipher:2.0.0-beta1'
implementation 'info.guardianproject.panic:panic:1.0'
implementation 'commons-io:commons-io:2.6'
implementation 'commons-net:commons-net:3.6'
implementation 'ch.acra:acra:4.9.1'
implementation 'io.reactivex:rxjava:1.1.0'
implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.9'
implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.5'
implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
@ -176,22 +168,20 @@ dependencies {
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 'androidx.test:core:1.2.0'
testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation 'org.mockito:mockito-core:3.3.3'
testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'org.bouncycastle:bcprov-jdk15on:1.65'
androidTestImplementation 'androidx.arch.core:core-testing:2.1.0'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:monitor:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test:monitor:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.work:work-testing:2.4.0'
}
checkstyle {

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,13 @@
public *;
}
# 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 +49,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,18 @@
<?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" />
<!-- 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"
<uses-library android:name="android.test.runner"
android:required="false" />
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

View File

@ -0,0 +1,65 @@
package org.fdroid.fdroid;
import android.app.Instrumentation;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.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-existent file
File nonexistent = new File(tempDir, "nonexistent");
CleanCacheService.clearOldFiles(nonexistent, 1);
CleanCacheService.clearOldFiles(null, 1);
}
}

View File

@ -6,7 +6,6 @@ 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;
@ -21,7 +20,7 @@ import androidx.test.uiautomator.UiObjectNotFoundException;
import androidx.test.uiautomator.UiSelector;
import android.util.Log;
import android.view.View;
import org.fdroid.fdroid.views.StatusBanner;
import org.fdroid.fdroid.views.BannerUpdatingRepos;
import org.fdroid.fdroid.views.main.MainActivity;
import org.hamcrest.Matchers;
import org.junit.After;
@ -104,7 +103,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);
@ -177,7 +176,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showSettings() {
ViewInteraction settingsBottonNavButton = onView(
allOf(withText(R.string.menu_settings), isDisplayed()));
@ -212,7 +210,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showUpdates() {
ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed()));
updatesBottonNavButton.perform(click());
@ -220,7 +217,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void startSwap() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
@ -236,7 +232,6 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showCategories() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
@ -262,12 +257,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 +279,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

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

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

View File

@ -19,14 +19,14 @@
package org.fdroid.fdroid.nearby;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Intent;
/**
* Dummy version for basic app flavor.
*/
public class TreeUriScannerIntentService {
public static void onActivityResult(AppCompatActivity activity, Intent intent) {
public static void onActivityResult(Activity activity, Intent intent) {
throw new IllegalStateException("unimplemented");
}
}

View File

@ -19,11 +19,11 @@
package org.fdroid.fdroid.views.main;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import android.widget.FrameLayout;
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);
}
/**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 5.3 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,8 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen android:title="@string/about_title"
android:key="pref_about" />
<PreferenceScreen android:title="@string/about_title">
<intent
android:action="android.intent.action.MAIN"
android:targetPackage="@string/applicationId"
android:targetClass="org.fdroid.fdroid.AboutActivity"/>
</PreferenceScreen>
<PreferenceCategory android:title="@string/preference_category__my_apps">
<PreferenceScreen android:title="@string/preference_manage_installed_apps">
@ -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,7 +56,7 @@
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"/>
@ -72,26 +77,26 @@
<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>
<PreferenceCategory android:title="@string/proxy">
<SwitchPreferenceCompat
<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"
@ -111,12 +116,12 @@
<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"
@ -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"

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,9 @@
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-feature android:name="android.hardware.usb.host" android:required="false"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@ -51,10 +43,8 @@
<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"
<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,33 +52,80 @@
<application>
<activity
android:name=".nearby.SwapWorkflowActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/swap"
android:launchMode="singleTask"
android:name=".nearby.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=".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>
<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"/>
<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>
<activity
android:name=".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"/>
@ -108,71 +145,18 @@
<activity
android:name=".panic.ExitActivity"
android:theme="@android:style/Theme.NoDisplay"/>
<activity
android:name=".panic.CalculatorActivity"
android:enabled="false"
android:icon="@mipmap/ic_calculator_launcher"
android:label="@string/hiding_calculator">
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

@ -10,17 +10,15 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import androidx.localbroadcastmanager.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 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

@ -5,7 +5,7 @@ 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;
@ -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

@ -7,26 +7,22 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import androidx.localbroadcastmanager.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 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

@ -35,9 +35,11 @@ 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 javax.net.ssl.SSLServerSocketFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -60,11 +62,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

@ -6,8 +6,8 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
@ -15,8 +15,6 @@ 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

@ -2,7 +2,7 @@ package org.fdroid.fdroid.nearby;
import android.content.Context;
import android.util.Log;
import kellinwood.security.zipsigner.ZipSigner;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.GeneralName;
@ -19,6 +19,9 @@ 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

@ -10,9 +10,10 @@ import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.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",
@ -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

View File

@ -4,7 +4,7 @@ import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.os.Process;
import androidx.localbroadcastmanager.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.

View File

@ -28,10 +28,11 @@ import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import androidx.core.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,8 +45,6 @@ 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
@ -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.
*
@ -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

@ -4,9 +4,16 @@ 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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.cursoradapter.widget.CursorAdapter;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
@ -19,19 +26,10 @@ 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;
public class SelectAppsView extends SwapView implements LoaderManager.LoaderCallbacks<Cursor> {
public SelectAppsView(Context context) {
@ -140,14 +138,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 +197,8 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
}
});
}
updateCheckedIndicatorView(view, listView.isItemChecked(listPosition));
}
public void updateCheckedIndicatorView(int position, boolean checked) {
@ -207,7 +207,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

@ -7,6 +7,9 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.WifiConfiguration;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.appcompat.widget.SwitchCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@ -19,7 +22,7 @@ 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;
@ -27,12 +30,6 @@ import org.fdroid.fdroid.nearby.peers.Peer;
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 +67,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 +76,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 +172,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);

View File

@ -1,5 +1,6 @@
package org.fdroid.fdroid.nearby;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
@ -12,17 +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.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 android.text.TextUtils;
import android.util.Log;
import cc.mvdan.accesspoint.WifiApControl;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.NotificationHelper;
import org.fdroid.fdroid.Preferences;
@ -35,6 +35,7 @@ import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.nearby.peers.Peer;
import org.fdroid.fdroid.net.Downloader;
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.
@ -78,6 +73,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 +115,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.
@ -304,15 +348,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 +372,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());
}
@ -362,45 +403,6 @@ public class SwapService extends Service {
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()
);
}
/**
* This is for setting things up for when the {@code SwapService} was
* started by the user clicking on the initial start button. The things
@ -424,8 +426,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 +434,7 @@ public class SwapService extends Service {
localBroadcastManager.unregisterReceiver(bonjourPeerFound);
localBroadcastManager.unregisterReceiver(bonjourPeerRemoved);
if (bluetoothAdapter != null) {
unregisterReceiver(bluetoothScanModeChanged);
}
BluetoothManager.stop(this);
@ -460,7 +458,7 @@ public class SwapService extends Service {
if (timer != null) {
timer.cancel();
}
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
stopForeground(true);
deleteAllSwapRepos();

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.nearby;
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.cursoradapter.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;
@ -42,16 +48,6 @@ 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 +194,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(),
@ -365,7 +361,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

@ -3,14 +3,11 @@ package org.fdroid.fdroid.nearby;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
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 android.util.AttributeSet;
import android.widget.RelativeLayout;
import org.fdroid.fdroid.R;
/**
* A {@link android.view.View} that registers to handle the swap events from
@ -45,7 +42,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();
}
@ -56,7 +53,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

@ -1,6 +1,7 @@
package org.fdroid.fdroid.nearby;
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 androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.core.view.MenuItemCompat;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.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;
@ -65,6 +64,7 @@ import org.fdroid.fdroid.net.BluetoothDownloader;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.HttpDownloader;
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 +75,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,7 +103,7 @@ 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;
@ -119,8 +116,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@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 +197,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 +210,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,7 +220,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
localBroadcastManager.registerReceiver(downloaderInterruptedReceiver,
new IntentFilter(Downloader.ACTION_INTERRUPTED));
wifiManager = ContextCompat.getSystemService(getApplicationContext(), WifiManager.class);
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wifiApControl = WifiApControl.getInstance(this);
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@ -238,7 +230,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
compositeDisposable.dispose();
localBroadcastManager.unregisterReceiver(downloaderInterruptedReceiver);
unbindService(serviceConnection);
super.onDestroy();
@ -277,7 +268,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 +298,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() {
@ -493,13 +485,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 +590,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);
}
/**
@ -779,7 +772,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 +926,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 +937,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
} else {
qrWarningMessage.setVisibility(View.VISIBLE);
}
})
);
}
}
@ -968,19 +955,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,7 +983,7 @@ 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) {
@ -1120,7 +1107,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);

View File

@ -20,6 +20,7 @@
package org.fdroid.fdroid.nearby;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.IntentService;
import android.content.ContentResolver;
import android.content.Context;
@ -27,14 +28,15 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
import androidx.documentfile.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.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Repo;
@ -49,8 +51,6 @@ 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
@ -76,22 +76,19 @@ 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";
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

View File

@ -7,14 +7,12 @@ import android.net.Uri;
import android.os.Build;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import androidx.annotation.Nullable;
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>
@ -49,7 +47,8 @@ public final class TreeUriUtils {
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);
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");

View File

@ -19,23 +19,28 @@
package org.fdroid.fdroid.nearby;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.UriPermission;
import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.views.main.MainActivity;
import org.fdroid.fdroid.views.main.NearbyViewBinder;
import androidx.annotation.RequiresApi;
import java.util.HashMap;
/**
* This is just a shim to receive {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED}
@ -44,6 +49,7 @@ import androidx.annotation.RequiresApi;
public class UsbDeviceAttachedReceiver extends BroadcastReceiver {
public static final String TAG = "UsbDeviceAttachedReceiv";
private static final HashMap<Uri, ContentObserver> contentObservers = new HashMap<>();
@RequiresApi(api = 19)
@Override
@ -71,7 +77,6 @@ public class UsbDeviceAttachedReceiver extends BroadcastReceiver {
}
};
contentResolver.registerContentObserver(uri, true, contentObserver);
UsbDeviceDetachedReceiver.contentObservers.put(uri, contentObserver);
}
}
}

View File

@ -19,23 +19,29 @@
package org.fdroid.fdroid.nearby;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.UriPermission;
import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.views.main.MainActivity;
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.
@ -43,7 +49,7 @@ import androidx.annotation.RequiresApi;
public class UsbDeviceDetachedReceiver extends BroadcastReceiver {
public static final String TAG = "UsbDeviceDetachedReceiv";
static final HashMap<Uri, ContentObserver> contentObservers = new HashMap<>();
private static final HashMap<Uri, ContentObserver> contentObservers = new HashMap<>();
@RequiresApi(api = 19)
@Override

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

@ -4,7 +4,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import org.fdroid.fdroid.Utils;
public class WifiStateChangeReceiver extends BroadcastReceiver {

View File

@ -10,13 +10,11 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.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 cc.mvdan.accesspoint.WifiApControl;
import org.apache.commons.net.util.SubnetUtils;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp;
@ -35,9 +33,6 @@ 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,
@ -72,8 +67,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 +79,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,7 +88,7 @@ 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();
Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState));
if (ni == null
@ -119,7 +106,7 @@ 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);
}
}
}

View File

@ -3,11 +3,9 @@ package org.fdroid.fdroid.nearby.peers;
import android.bluetooth.BluetoothClass.Device;
import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.text.TextUtils;
import org.fdroid.fdroid.R;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import org.fdroid.fdroid.R;
public class BluetoothPeer implements Peer {
@ -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

@ -2,15 +2,13 @@ package org.fdroid.fdroid.nearby.peers;
import android.net.Uri;
import android.os.Parcel;
import androidx.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,7 +1,6 @@
package org.fdroid.fdroid.nearby.peers;
import android.os.Parcelable;
import androidx.annotation.DrawableRes;
/**

View File

@ -3,7 +3,6 @@ package org.fdroid.fdroid.nearby.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,21 +1,17 @@
package org.fdroid.fdroid.panic;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.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.
@ -39,13 +35,10 @@ public class CalculatorActivity extends AppCompatActivity {
@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,14 +1,12 @@
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 android.content.res.Resources;
import androidx.preference.CheckBoxPreference;
import androidx.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
public class DestructiveCheckBoxPreference extends CheckBoxPreference {
public DestructiveCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
@ -33,10 +31,11 @@ public class DestructiveCheckBoxPreference extends CheckBoxPreference {
if (!holder.itemView.isEnabled()) {
return;
}
Resources resources = getContext().getResources();
if (FDroidApp.isAppThemeLight()) {
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_light));
holder.itemView.setBackgroundColor(resources.getColor(R.color.panic_destructive_light));
} else {
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_dark));
holder.itemView.setBackgroundColor(resources.getColor(R.color.panic_destructive_dark));
}
}
}

View File

@ -1,14 +1,11 @@
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;
import android.util.AttributeSet;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
public class DestructivePreference extends Preference {
public DestructivePreference(Context context, AttributeSet attrs, int defStyleAttr) {
@ -31,9 +28,9 @@ public class DestructivePreference extends Preference {
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
if (FDroidApp.isAppThemeLight()) {
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_light));
holder.itemView.setBackgroundColor(getContext().getResources().getColor(R.color.panic_destructive_light));
} else {
holder.itemView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.panic_destructive_dark));
holder.itemView.setBackgroundColor(getContext().getResources().getColor(R.color.panic_destructive_dark));
}
}
}

View File

@ -3,7 +3,6 @@ package org.fdroid.fdroid.panic;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class ExitActivity extends AppCompatActivity {

View File

@ -7,14 +7,12 @@ import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import androidx.core.app.NotificationManagerCompat;
import androidx.appcompat.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

@ -1,32 +1,37 @@
package org.fdroid.fdroid.panic;
import android.os.Bundle;
import android.view.View;
import com.google.android.material.appbar.MaterialToolbar;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.MenuItem;
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);
((FDroidApp) getApplication()).applyTheme(this);
super.onCreate(bundle);
setContentView(R.layout.activity_panic_settings);
MaterialToolbar toolbar = findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayShowHomeEnabled(true);
ab.setDisplayHomeAsUpEnabled(true);
}
}
@Override
public void onClick(View view) {
// Handle navigation icon press
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;
import android.app.Activity;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
@ -12,9 +13,20 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.CheckBoxPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import android.text.TextUtils;
import android.util.TypedValue;
import info.guardianproject.panic.Panic;
import info.guardianproject.panic.PanicResponder;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.installer.PrivilegedInstaller;
@ -22,19 +34,6 @@ 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;
public class PanicPreferencesFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener {
@ -82,7 +81,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
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);
@ -131,9 +130,9 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
}
}
} else {
Preference preference = new Preference(requireActivity());
Preference preference = new Preference(getActivity());
preference.setIntent(intent);
Drawable icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_add_circle_outline);
Drawable icon = getResources().getDrawable(R.drawable.ic_add_circle_outline_white);
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);
@ -234,13 +233,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();
}
};

View File

@ -1,13 +1,17 @@
package org.fdroid.fdroid.panic;
import android.app.Activity;
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 androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import info.guardianproject.panic.Panic;
import info.guardianproject.panic.PanicResponder;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.DBHelper;
@ -27,21 +31,16 @@ 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}
* This {@link Activity} is purely to run events in response to a panic trigger.
* It needs to be an {@code Activity} 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
* {@link Intent}. This is therefore an {@code Activity} 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)
* @see PanicResponder#receivedTriggerFromConnectedApp(Activity)
*/
public class PanicResponderActivity extends AppCompatActivity {

View File

@ -1,8 +1,9 @@
package org.fdroid.fdroid.panic;
import android.app.Activity;
import androidx.annotation.NonNull;
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;
@ -10,13 +11,10 @@ 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) {
SelectInstalledAppListAdapter(Activity activity) {
super(activity);
Preferences prefs = Preferences.get();
selectedApps = prefs.getPanicWipeSet();

View File

@ -1,7 +1,9 @@
package org.fdroid.fdroid.panic;
import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.views.apps.AppListItemState;
@ -9,10 +11,6 @@ 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.
*/
@ -20,7 +18,7 @@ public class SelectInstalledAppListItemController extends InstalledAppListItemCo
private final Set<String> selectedApps;
public SelectInstalledAppListItemController(AppCompatActivity activity, View itemView, Set<String> selectedApps) {
public SelectInstalledAppListItemController(Activity activity, View itemView, Set<String> selectedApps) {
super(activity, itemView);
this.selectedApps = selectedApps;
}

View File

@ -20,29 +20,29 @@
package org.fdroid.fdroid.panic;
import android.annotation.SuppressLint;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.LightingColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.Toolbar;
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;
@ -54,14 +54,13 @@ public class SelectInstalledAppsActivity extends AppCompatActivity implements Lo
@Override
protected void onCreate(Bundle savedInstanceState) {
FDroidApp fdroidApp = (FDroidApp) getApplication();
fdroidApp.applyPureBlackBackgroundInDarkTheme(this);
((FDroidApp) getApplication()).applyTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.installed_apps_layout);
MaterialToolbar toolbar = findViewById(R.id.toolbar);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.panic_add_apps_to_uninstall));
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@ -116,7 +115,14 @@ public class SelectInstalledAppsActivity extends AppCompatActivity implements Lo
MenuItem menuItem = menu.add(R.string.menu_select_for_wipe);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
checkId = menuItem.getItemId();
if (FDroidApp.isAppThemeLight()) {
Resources resources = getResources();
Drawable icon = resources.getDrawable(R.drawable.check);
icon.setColorFilter(new LightingColorFilter(0xffffffff, resources.getColor(android.R.color.white)));
menuItem.setIcon(icon);
} else {
menuItem.setIcon(R.drawable.check);
}
return true;
}

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 com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.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.panic.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 androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.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,6 +1,7 @@
package org.fdroid.fdroid.views.main;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.UriPermission;
@ -12,7 +13,9 @@ import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.DocumentsContract;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@ -21,7 +24,6 @@ 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;
@ -31,11 +33,6 @@ import org.fdroid.fdroid.nearby.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
* process is quite heavy duty in that it fires up Bluetooth and/or WiFi
@ -62,8 +59,6 @@ import androidx.core.content.ContextCompat;
*
* @see TreeUriScannerIntentService
* @see org.fdroid.fdroid.nearby.SDCardScannerService
* <p>
* TODO use {@link StorageManager#registerStorageVolumeCallback(Executor, StorageManager.StorageVolumeCallback)}
*/
public class NearbyViewBinder {
public static final String TAG = "NearbyViewBinder";
@ -71,7 +66,7 @@ public class NearbyViewBinder {
private static File externalStorage = null;
private static View swapView;
NearbyViewBinder(final AppCompatActivity activity, FrameLayout parent) {
NearbyViewBinder(final Activity activity, FrameLayout parent) {
swapView = activity.getLayoutInflater().inflate(R.layout.main_tab_swap, parent, true);
TextView subtext = swapView.findViewById(R.id.both_parties_need_fdroid_text);
@ -91,7 +86,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);
}
}
});
@ -164,30 +159,15 @@ public class NearbyViewBinder {
storageVolumeText.setVisibility(View.GONE);
requestStorageVolume.setVisibility(View.GONE);
final StorageManager storageManager = ContextCompat.getSystemService(context, StorageManager.class);
final StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
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) {
final Intent intent = storageVolume.createAccessIntent(null);
if (intent == null) {
Utils.debugLog(TAG, "Got null Storage Volume access Intent");
return;
}
final Intent intent = tmpIntent;
storageVolumeText.setVisibility(View.VISIBLE);
String text = storageVolume.getDescription(context);
@ -214,23 +194,8 @@ public class NearbyViewBinder {
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);
}
((Activity) context).startActivityForResult(intent,
MainActivity.REQUEST_STORAGE_ACCESS);
}
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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