mirror of
https://github.com/bilde2910/Hauk.git
synced 2026-01-23 18:36:09 +00:00
Compare commits
No commits in common. "master" and "v1.5.1" have entirely different histories.
193 changed files with 1061 additions and 6507 deletions
53
.github/workflows/docker-image.yml
vendored
53
.github/workflows/docker-image.yml
vendored
|
|
@ -1,53 +0,0 @@
|
|||
name: Build Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
# branches:
|
||||
# - 'master'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- # Check out repository
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- # Set up QEMU for multiarch support
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- # Set up Docker BuildX for Docker image building
|
||||
name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- # Set proper tags
|
||||
name: Docker metadata
|
||||
id: docker_meta # you'll use this in the next step
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: bilde2910/hauk
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern=v{{version}}
|
||||
type=semver,pattern=stable-{{major}}.x
|
||||
- # Log in to Docker Hub
|
||||
name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- # Build and push
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
|
|
@ -4,11 +4,9 @@ COPY frontend/ /var/www/html/
|
|||
COPY docker/start.sh .
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y memcached libmemcached-dev zlib1g-dev libldap2-dev libssl-dev && \
|
||||
apt-get install -y memcached libmemcached-dev zlib1g-dev && \
|
||||
pecl install memcached && \
|
||||
docker-php-ext-enable memcached && \
|
||||
docker-php-ext-configure ldap --with-libdir=lib/*-linux-gnu*/ && \
|
||||
docker-php-ext-install ldap
|
||||
docker-php-ext-enable memcached
|
||||
|
||||
EXPOSE 80/tcp
|
||||
VOLUME /etc/hauk
|
||||
|
|
|
|||
91
README.md
91
README.md
|
|
@ -24,9 +24,8 @@ phone, and you're good to go!
|
|||
|
||||
## System Requirements
|
||||
|
||||
- Web server running PHP and Memcached or Redis.
|
||||
- PHP `memcached`, `memcache` or `redis` extension installed on the web server.
|
||||
- PHP `ldap` extension if using LDAP authentication.
|
||||
- Web server running LEMP or LAMP
|
||||
- PHP Memcached or Memcache extension installed on websever.
|
||||
- Android 6 or above to run the [companion Android app](https://f-droid.org/packages/info.varden.hauk/).
|
||||
|
||||
## Installation instructions
|
||||
|
|
@ -36,11 +35,13 @@ phone, and you're good to go!
|
|||
to install Hauk in, for example `/var/www/html`. Follow the instructions
|
||||
given by the install script. Make sure to set a secure hashed password and
|
||||
edit your site's domain in the configuration file after installation.
|
||||
3. Start the web server and make sure Memcached or Redis is running and
|
||||
[properly configured and firewalled](https://github.com/bilde2910/Hauk/wiki/FAQ#how-do-i-securely-configure-memcachedredis).
|
||||
4. Install the companion Android app (from your favourite store linked above)
|
||||
3. Start the webserver and make sure Memcached is running.
|
||||
4. Install the [companion Android app](https://f-droid.org/packages/info.varden.hauk/)
|
||||
on your phone and enter your server's settings.
|
||||
|
||||
When you visit the webroot you may see an experation notice. Hauk uses randomly
|
||||
generated URL which will be provided by the app.
|
||||
|
||||
## Manual installation
|
||||
|
||||
If you prefer not to use the install script, you can instead choose to copy the
|
||||
|
|
@ -51,32 +52,12 @@ files manually.
|
|||
in your web root, for example `/var/www/html`.
|
||||
3. Modify `include/config.php` to your liking. Make sure to set a secure hashed
|
||||
password and edit your site's domain in this file.
|
||||
4. Start the web server and make sure Memcached or Redis is running and
|
||||
[properly configured and firewalled](https://github.com/bilde2910/Hauk/wiki/FAQ#how-do-i-securely-configure-memcachedredis).
|
||||
5. Install the companion Android app (from your favourite store linked above)
|
||||
4. Start the webserver and make sure Memcached is running.
|
||||
5. Install the [companion Android app](https://f-droid.org/packages/info.varden.hauk/)
|
||||
on your phone and enter your server's settings.
|
||||
|
||||
## Distribution-specific packages
|
||||
|
||||
The Hauk backend is available as packages for the following distributions:
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Install [`hauk-server`](https://aur.archlinux.org/packages/hauk-server/) from
|
||||
AUR. The backend will be installed to `/usr/share/webapps/hauk-server`.
|
||||
|
||||
## Via Docker Compose
|
||||
|
||||
The official Docker image on Docker Hub is `bilde2910/hauk`. It comes with several different tags:
|
||||
|
||||
| Tag | Description |
|
||||
| --- | ----------- |
|
||||
| `latest` | Updated with each commit to this repository and always has the latest changes. |
|
||||
| `stable-1.x` | The latest tagged [release](https://github.com/bilde2910/Hauk/releases) of version 1.x. |
|
||||
| `X.Y.Z` | A specific release of the Hauk backend. Note that old versions are not supported and are provided for your convenience only. |
|
||||
|
||||
`latest`, `stable-1.x` and all releases from `1.5.2` and up are multi-arch and compiled for x86_64, armv7l and aarch64. `1.5.1` and older are x86_64 only. You can use any of these tags for all architectures, and Docker will automatically pick the correct one. If you need the image for a specific architecture, however, you can fetch them using `*-amd64` (x86_64), `*-arm32v7` (armv7l) or `*-arm64v8` (aarch64) versions of any of the tags (e.g. `latest-arm32v7`).
|
||||
|
||||
**docker-compose.yml**
|
||||
|
||||
```yaml
|
||||
|
|
@ -128,22 +109,6 @@ server {
|
|||
}
|
||||
```
|
||||
|
||||
## Upgrading to newer versions
|
||||
|
||||
Hauk is versioned according to [Semantic Versioning 2.0.0](https://semver.org/). Any update that is **not a major update** is guaranteed to be without breaking changes, and you can keep the same configuration file for the updated release.
|
||||
|
||||
- Major updates add breaking changes that either require manual intervention, or breaks backward compatibility. Update instructions for major versions will be listed in the release notes, as well as either this README or in the wiki. To date there have been no major updates.
|
||||
- Minor updates add functionality, but does not break backward compatibility. You can still use an older client on a newer server, or a newer client on an older server, though some functionality may be missing. This will be dynamically detected by the client and server, which could e.g. lead to some UI elements being disabled in the app, or a notification made if a user tries to use new functionality that the other endpoint does not support.
|
||||
- Patch updates are primarily bugfixes.
|
||||
|
||||
Aside from certain major changes, you can keep your configuration file. New options may have been added to the config, but these will have sane defaults applied automatically. If you wish to change any new options, you can either reconfigure Hauk from the new config.php template, or copy and paste the relevant options from the new template to your existing file and change the appropriate values.
|
||||
|
||||
Installations done using either the installer (`install.sh`) or via manual file copy can be upgraded simply by pulling the latest version of this repository and running the installer again, or overwriting the installation with the new files.
|
||||
|
||||
Installations done via distribution-specific packages will be updated to the latest version by your package manager.
|
||||
|
||||
Docker installations will be updated whenever you pull the image. If you're using Docker, you can reserve yourself from receiving major updates (which may contain breaking changes) by using the `stable-*` tag instead of `latest`. If you use a specific versioned tag, your installation will be locked at that specific version and you will not receive feature updates or bugfixes unless you manually change the tag and pull.
|
||||
|
||||
## Demo server
|
||||
|
||||
If you'd like to see what Hauk can do, download the app and insert connection details for the demo server:
|
||||
|
|
@ -153,41 +118,21 @@ Password: `demo`
|
|||
|
||||
Location shares on the demo server is limited to 2 minutes and is only meant for demonstration purposes. Set up your own server to use Hauk to its full extent.
|
||||
|
||||
<details>
|
||||
<summary>Demo server privacy policy - Last updated December 26, 2019</summary>
|
||||
|
||||
**Last updated: December 26, 2019**
|
||||
|
||||
The demo server is limited by configuration to shares no longer than 2 minutes. This means that no matter what happens, the location data you send to the demo server will be deleted automatically after at most 2 minutes from session initiation. Location data is never logged to disk in any way and only stays in RAM for this time. After the session ends, the data is no longer available. It is a vanilla installation of Hauk from GitHub and the code has not been altered in any way.
|
||||
|
||||
The server currently uses CloudFlare for DDoS protection, hence CloudFlare can see the data in transit. You may refer to their privacy policy as well.
|
||||
|
||||
The HTTP daemon keeps a standard access log for 7 days. This log contains the link ID (which is useless after the 2 minute session expiration), full URLs, user agents, timestamps, and referring URL (if any). It also logs the IP addresses of the CloudFlare proxy server you connect through. It does *not* contain *your* IP address, only that of a CloudFlare data center somewhere. It's thus not possible to track individuals using it, and not possible to get any meaningful data from it. This log file is used for abuse prevention only.
|
||||
|
||||
The server itself is located in Norway and is thus covered under Norwegian privacy regulations.
|
||||
</details>
|
||||
|
||||
## Translators
|
||||
|
||||
Hauk depends on volunteers to translate the project. Want to help out? Head over to the [translation portal](https://traduki.varden.info/engage/hauk/) to get started.
|
||||
|
||||
[](https://traduki.varden.info/engage/hauk/)
|
||||
|
||||
- **Basque** - osoitz
|
||||
- **Catalan** - xordiet
|
||||
- **Dutch** - Jdekoning141
|
||||
- **French** - thifranc and LukeMarlin
|
||||
- **German** - natrius, hurradiegams, lemmerk, code-surfer and Marmo
|
||||
- **Italian** - Vieler
|
||||
- **Norwegian Bokmål** - bilde2910
|
||||
- **Norwegian Nynorsk** - bilde2910
|
||||
- **Polish** - krystiancha and RuralYak
|
||||
- **Portugese (Brazil)** - arajooj
|
||||
- **Romanian** - Licaon_Kter
|
||||
- **Russian** - RuralYak, Brujerizmo90
|
||||
- **Spanish** - sdstolworthy
|
||||
- **Turkish** - kylethedeveloper, ayyilmaz
|
||||
- **Ukrainian** - RuralYak
|
||||
**Basque** - osoitz
|
||||
**Dutch** - Jdekoning141
|
||||
**French** - thifranc
|
||||
**German** - natrius, hurradiegams and lemmerk
|
||||
**Norwegian Bokmål** - bilde2910
|
||||
**Norwegian Nynorsk** - bilde2910
|
||||
**Polish** - krystiancha and RuralYak
|
||||
**Russian** - RuralYak
|
||||
**Ukrainian** - RuralYak
|
||||
|
||||
### Translation status
|
||||
|
||||
|
|
|
|||
3
android/.idea/codeStyles/Project.xml
generated
3
android/.idea/codeStyles/Project.xml
generated
|
|
@ -1,6 +1,9 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="JD_KEEP_INVALID_TAGS" value="false" />
|
||||
</JavaCodeStyleSettings>
|
||||
|
|
|
|||
9
android/.idea/gradle.xml
generated
9
android/.idea/gradle.xml
generated
|
|
@ -1,20 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<compositeConfiguration>
|
||||
<compositeBuild compositeDefinitionSource="SCRIPT" />
|
||||
</compositeConfiguration>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="/usr/share/java/gradle" />
|
||||
<option name="gradleJvm" value="jbr-17" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
<inspection_tool class="AndroidLintGoogleAppIndexingWarning" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="AndroidLintLogConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintMangledCRLF" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintMissingTranslation" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintSelectableText" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AndroidLintUnusedIds" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
|
|
|
|||
25
android/.idea/jarRepositories.xml
generated
25
android/.idea/jarRepositories.xml
generated
|
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google" />
|
||||
<option name="name" value="Google" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
10
android/.idea/misc.xml
generated
10
android/.idea/misc.xml
generated
|
|
@ -5,7 +5,7 @@
|
|||
<option name="myDefaultNotNull" value="android.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="14">
|
||||
<list size="12">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
|
|
@ -18,14 +18,12 @@
|
|||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||
<item index="12" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
||||
<item index="13" class="java.lang.String" itemvalue="org.jspecify.nullness.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="13">
|
||||
<list size="11">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
|
|
@ -37,13 +35,11 @@
|
|||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||
<item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
|
||||
<item index="12" class="java.lang.String" itemvalue="org.jspecify.nullness.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
|
|
|||
12
android/.idea/runConfigurations.xml
generated
Normal file
12
android/.idea/runConfigurations.xml
generated
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
defaultConfig {
|
||||
applicationId "info.varden.hauk"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 33
|
||||
versionCode 14
|
||||
versionName "1.6.2"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 10
|
||||
versionName "1.5.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
|
@ -16,15 +17,13 @@ android {
|
|||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
namespace 'info.varden.hauk'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
package info.varden.hauk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
|
@ -9,8 +9,6 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import info.varden.hauk.system.preferences.Preference;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -4,36 +4,22 @@
|
|||
package="info.varden.hauk">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:icon="@drawable/ic_icon"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security"
|
||||
android:roundIcon="@drawable/ic_icon"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:name=".system.preferences.ui.SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
android:parentActivityName=".ui.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="info.varden.hauk.ui.MainActivity" />
|
||||
</activity>
|
||||
|
||||
android:theme="@style/AppTheme"
|
||||
android:fullBackupContent="@xml/backup_descriptor">
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:theme="@style/HomeTheme"
|
||||
android:exported="true">
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
|
@ -43,9 +29,7 @@
|
|||
<activity
|
||||
android:name=".global.ui.AuthorizationActivity"
|
||||
android:screenOrientation="portrait">
|
||||
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".global.Receiver"
|
||||
android:exported="true"
|
||||
|
|
@ -55,30 +39,23 @@
|
|||
<action android:name="info.varden.hauk.START_ALONE_THEN_MAKE_TOAST" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".notify.CopyLinkReceiver"
|
||||
android:exported="false">
|
||||
|
||||
<receiver android:name=".notify.CopyLinkReceiver" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="info.varden.hauk.COPY_LINK" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".notify.StopSharingReceiver"
|
||||
android:exported="false">
|
||||
<receiver android:name=".notify.StopSharingReceiver" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="info.varden.hauk.STOP_SHARING" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".service.LocationPushService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="location">
|
||||
<service android:name=".service.LocationPushService" android:exported="false" android:enabled="true" android:foregroundServiceType="location">
|
||||
<intent-filter>
|
||||
<action android:name="info.varden.hauk.LOCATION_SERVICE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
package info.varden.hauk;
|
||||
|
||||
import info.varden.hauk.http.security.CertificateValidationPolicy;
|
||||
import info.varden.hauk.struct.Version;
|
||||
import info.varden.hauk.system.preferences.Preference;
|
||||
import info.varden.hauk.system.preferences.indexresolver.NightModeStyle;
|
||||
import info.varden.hauk.system.preferences.indexresolver.ProxyTypeResolver;
|
||||
import info.varden.hauk.utils.Preference;
|
||||
|
||||
/**
|
||||
* Constants used in the Hauk app.
|
||||
|
|
@ -28,26 +25,16 @@ public enum Constants {
|
|||
|
||||
// Keys for use in stored server preferences.
|
||||
public static final Preference<String> PREF_SERVER_ENCRYPTED = new Preference.EncryptedString("cryptServer", "");
|
||||
public static final Preference<ProxyTypeResolver> PREF_PROXY_TYPE = new Preference.Enum<>("proxyType", ProxyTypeResolver.SYSTEM_DEFAULT);
|
||||
public static final Preference<String> PREF_PROXY_HOST = new Preference.String("proxyHost", "localhost");
|
||||
public static final Preference<Integer> PREF_PROXY_PORT = new Preference.Integer("proxyPort", 9050);
|
||||
public static final Preference<Integer> PREF_CONNECTION_TIMEOUT = new Preference.Integer("connectTimeout", 10);
|
||||
public static final Preference<CertificateValidationPolicy> PREF_CERTIFICATE_VALIDATION = new Preference.Enum<>("tlsCertValidation", CertificateValidationPolicy.VALIDATE_ALL);
|
||||
public static final Preference<String> PREF_USERNAME_ENCRYPTED = new Preference.EncryptedString("cryptUsername", "");
|
||||
public static final Preference<String> PREF_PASSWORD_ENCRYPTED = new Preference.EncryptedString("cryptPassword", "");
|
||||
public static final Preference<Integer> PREF_DURATION = new Preference.Integer("duration", 30);
|
||||
public static final Preference<Integer> PREF_INTERVAL = new Preference.Integer("interval", 1);
|
||||
public static final Preference<Integer> PREF_NO_GNSS_FALLBACK = new Preference.Integer("noGnssFallback", 45);
|
||||
public static final Preference<Float> PREF_UPDATE_DISTANCE = new Preference.Float("minUpdateDistance", 0.0F);
|
||||
public static final Preference<String> PREF_CUSTOM_ID = new Preference.String("requestLink", "");
|
||||
public static final Preference<Boolean> PREF_ENABLE_E2E = new Preference.Boolean("enableE2E", false);
|
||||
public static final Preference<String> PREF_E2E_PASSWORD = new Preference.EncryptedString("e2ePassword", "");
|
||||
public static final Preference<String> PREF_NICKNAME = new Preference.String("nickname", "");
|
||||
public static final Preference<Integer> PREF_DURATION_UNIT = new Preference.Integer("durUnit", Constants.DURATION_UNIT_MINUTES);
|
||||
public static final Preference<Boolean> PREF_REMEMBER_PASSWORD = new Preference.Boolean("rememberPassword", false);
|
||||
public static final Preference<Boolean> PREF_ALLOW_ADOPTION = new Preference.Boolean("allowAdoption", true);
|
||||
public static final Preference<NightModeStyle> PREF_NIGHT_MODE = new Preference.Enum<>("nightMode", NightModeStyle.FOLLOW_SYSTEM);
|
||||
public static final Preference<Boolean> PREF_CONFIRM_STOP = new Preference.Boolean("confirmStop", true);
|
||||
public static final Preference<Boolean> PREF_HIDE_LOGO = new Preference.Boolean("hideLogo", false);
|
||||
|
||||
@Deprecated // Use PREF_SERVER_ENCRYPTED instead
|
||||
public static final Preference<String> PREF_SERVER = new Preference.String("server", "");
|
||||
|
|
@ -68,16 +55,12 @@ public enum Constants {
|
|||
// Regular expression for extracting a share ID from a URL when adopting a share.
|
||||
public static final String REGEX_ADOPT_ID_FROM_LINK = "\\?([A-Za-z0-9-]+)";
|
||||
|
||||
// Formatting and input validation.
|
||||
public static final String DATE_FORMAT_UI = "yyyy-MM-dd HH:mm:ss z";
|
||||
public static final String DATE_FORMAT_LOG = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||
public static final int PORT_MIN = 0;
|
||||
public static final int PORT_MAX = 65536;
|
||||
// Default date format.
|
||||
public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss z";
|
||||
|
||||
// Keys for intent extras.
|
||||
public static final String EXTRA_SHARE = "share";
|
||||
public static final String EXTRA_STOP_TASK = "stopTask";
|
||||
public static final String EXTRA_HANDLER = "handler";
|
||||
public static final String EXTRA_GNSS_ACTIVE_TASK = "gnssActiveTask";
|
||||
public static final String EXTRA_BROADCAST_RECEIVER_REGISTRY_INDEX = "dataRegistryIndex";
|
||||
public static final String EXTRA_BROADCAST_AUTHORIZATION_IDENTIFIER = "source";
|
||||
|
|
@ -88,7 +71,6 @@ public enum Constants {
|
|||
public static final String EXTRA_SESSION_CUSTOM_ID = "requestLink";
|
||||
public static final String EXTRA_SESSION_E2E_PASSWORD = "e2ePassword";
|
||||
public static final String EXTRA_SESSION_INTERVAL = "interval";
|
||||
public static final String EXTRA_SESSION_MIN_DISTANCE = "minDistance";
|
||||
public static final String EXTRA_SESSION_ALLOW_ADOPT = "adoptable";
|
||||
|
||||
// Content types for intents.
|
||||
|
|
@ -114,7 +96,6 @@ public enum Constants {
|
|||
public static final String PACKET_PARAM_LONGITUDE = "lon";
|
||||
public static final String PACKET_PARAM_NICKNAME = "nic";
|
||||
public static final String PACKET_PARAM_PASSWORD = "pwd";
|
||||
public static final String PACKET_PARAM_PROVIDER_ACCURACY = "prv";
|
||||
public static final String PACKET_PARAM_SALT = "salt";
|
||||
public static final String PACKET_PARAM_SESSION_ID = "sid";
|
||||
public static final String PACKET_PARAM_SHARE_ID = "lid";
|
||||
|
|
|
|||
|
|
@ -7,76 +7,34 @@ import info.varden.hauk.R;
|
|||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class Buttons {
|
||||
private Buttons() {
|
||||
public enum Buttons {
|
||||
|
||||
OK_CANCEL (R.string.btn_ok, R.string.btn_cancel),
|
||||
YES_NO (R.string.btn_yes, R.string.btn_no),
|
||||
CREATE_CANCEL (R.string.btn_create, R.string.btn_cancel),
|
||||
SETTINGS_DISMISS (R.string.btn_dismiss, R.string.btn_show_settings),
|
||||
OK_SHARE (R.string.btn_ok, R.string.btn_share_short);
|
||||
|
||||
// The dialog has one positive and one negative button.
|
||||
private final int positive;
|
||||
private final int negative;
|
||||
|
||||
Buttons(int positive, int negative) {
|
||||
this.positive = positive;
|
||||
this.negative = negative;
|
||||
}
|
||||
|
||||
public enum Two {
|
||||
|
||||
OK_CANCEL(R.string.btn_ok, R.string.btn_cancel),
|
||||
YES_NO(R.string.btn_yes, R.string.btn_no),
|
||||
CREATE_CANCEL(R.string.btn_create, R.string.btn_cancel),
|
||||
SETTINGS_DISMISS(R.string.btn_dismiss, R.string.btn_show_settings),
|
||||
SETTINGS_OK(R.string.btn_ok, R.string.btn_show_settings),
|
||||
OK_SHARE(R.string.btn_ok, R.string.btn_share_short);
|
||||
|
||||
// The dialog has one positive and one negative button.
|
||||
private final int positive;
|
||||
private final int negative;
|
||||
|
||||
Two(int positive, int negative) {
|
||||
this.positive = positive;
|
||||
this.negative = negative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a strings resource ID that corresponds to this button set's positive button.
|
||||
*/
|
||||
public int getPositiveButton() {
|
||||
return this.positive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a strings resource ID that corresponds to this button set's negative button.
|
||||
*/
|
||||
public int getNegativeButton() {
|
||||
return this.negative;
|
||||
}
|
||||
/**
|
||||
* Returns a strings resource ID that corresponds to this button set's positive button.
|
||||
*/
|
||||
public int getPositiveButton() {
|
||||
return this.positive;
|
||||
}
|
||||
|
||||
public enum Three {
|
||||
YES_NO_REMEMBER(R.string.btn_yes, R.string.btn_remember, R.string.btn_no);
|
||||
|
||||
// The dialog has one positive, one neutral and one negative button.
|
||||
private final int positive;
|
||||
private final int neutral;
|
||||
private final int negative;
|
||||
|
||||
Three(int positive, int neutral, int negative) {
|
||||
this.positive = positive;
|
||||
this.neutral = neutral;
|
||||
this.negative = negative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a strings resource ID that corresponds to this button set's positive button.
|
||||
*/
|
||||
public int getPositiveButton() {
|
||||
return this.positive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a strings resource ID that corresponds to this button set's neutral button.
|
||||
*/
|
||||
public int getNeutralButton() {
|
||||
return this.neutral;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a strings resource ID that corresponds to this button set's negative button.
|
||||
*/
|
||||
public int getNegativeButton() {
|
||||
return this.negative;
|
||||
}
|
||||
/**
|
||||
* Returns a strings resource ID that corresponds to this button set's negative button.
|
||||
*/
|
||||
public int getNegativeButton() {
|
||||
return this.negative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,4 @@ public interface CustomDialogBuilder {
|
|||
*/
|
||||
@Nullable
|
||||
View createView(Context ctx);
|
||||
|
||||
interface Three extends CustomDialogBuilder {
|
||||
/**
|
||||
* Fires when the neutral button is clicked in the dialog.
|
||||
*/
|
||||
void onNeutral();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ public final class DialogService {
|
|||
* @param buttons The buttons to display on the dialog.
|
||||
* @param builder A dialog builder that builds a View and handles the dialog buttons.
|
||||
*/
|
||||
public void showDialog(int title, int message, Buttons.Two buttons, CustomDialogBuilder builder) {
|
||||
public void showDialog(int title, int message, Buttons buttons, CustomDialogBuilder builder) {
|
||||
showDialog(title, this.ctx.getString(message), buttons, builder);
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ public final class DialogService {
|
|||
* @param buttons The buttons to display on the dialog.
|
||||
* @param builder A dialog builder that builds a View and handles the dialog buttons.
|
||||
*/
|
||||
public void showDialog(int title, String message, Buttons.Two buttons, CustomDialogBuilder builder) {
|
||||
public void showDialog(int title, String message, Buttons buttons, CustomDialogBuilder builder) {
|
||||
showDialog(this.ctx.getString(title), message, buttons, builder);
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ public final class DialogService {
|
|||
* @param buttons The buttons to display on the dialog.
|
||||
* @param builder A dialog builder that builds a View and handles the dialog buttons.
|
||||
*/
|
||||
private void showDialog(String title, String message, Buttons.Two buttons, CustomDialogBuilder builder) {
|
||||
private void showDialog(String title, String message, Buttons buttons, CustomDialogBuilder builder) {
|
||||
View view = builder.createView(this.ctx);
|
||||
if (view != null) {
|
||||
TypedValue tv = new TypedValue();
|
||||
|
|
@ -184,64 +184,6 @@ public final class DialogService {
|
|||
dlgAlert.create().show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog box with a custom rendered View.
|
||||
*
|
||||
* @param title A string resource representing the title of the dialog box.
|
||||
* @param message A string resource representing the body of the dialog box.
|
||||
* @param buttons The buttons to display on the dialog.
|
||||
* @param builder A dialog builder that builds a View and handles the dialog buttons.
|
||||
*/
|
||||
public void showDialog(int title, int message, Buttons.Three buttons, CustomDialogBuilder.Three builder) {
|
||||
showDialog(title, this.ctx.getString(message), buttons, builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog box with a custom rendered View.
|
||||
*
|
||||
* @param title A string resource representing the title of the dialog box.
|
||||
* @param message A string representing the body of the dialog box.
|
||||
* @param buttons The buttons to display on the dialog.
|
||||
* @param builder A dialog builder that builds a View and handles the dialog buttons.
|
||||
*/
|
||||
public void showDialog(int title, String message, Buttons.Three buttons, CustomDialogBuilder.Three builder) {
|
||||
showDialog(this.ctx.getString(title), message, buttons, builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog box with a custom rendered View.
|
||||
*
|
||||
* @param title A string representing the title of the dialog box.
|
||||
* @param message A string representing the body of the dialog box.
|
||||
* @param buttons The buttons to display on the dialog.
|
||||
* @param builder A dialog builder that builds a View and handles the dialog buttons.
|
||||
*/
|
||||
private void showDialog(String title, String message, Buttons.Three buttons, CustomDialogBuilder.Three builder) {
|
||||
View view = builder.createView(this.ctx);
|
||||
if (view != null) {
|
||||
TypedValue tv = new TypedValue();
|
||||
int padding = 0;
|
||||
if (this.ctx.getTheme().resolveAttribute(R.attr.dialogPreferredPadding, tv, true)) {
|
||||
padding = TypedValue.complexToDimensionPixelSize(tv.data, this.ctx.getResources().getDisplayMetrics());
|
||||
}
|
||||
view.setPadding(padding, padding, padding, 0);
|
||||
}
|
||||
|
||||
Log.d("Showing dialog with title=%s, message=%s, builder=%s, view=%s", title, message, builder, view); //NON-NLS
|
||||
|
||||
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this.ctx);
|
||||
dlgAlert.setMessage(message);
|
||||
dlgAlert.setTitle(title);
|
||||
if (view != null) dlgAlert.setView(view);
|
||||
|
||||
dlgAlert.setPositiveButton(this.ctx.getString(buttons.getPositiveButton()), new PositiveClickListener(builder));
|
||||
dlgAlert.setNegativeButton(this.ctx.getString(buttons.getNegativeButton()), new NegativeClickListener(builder));
|
||||
dlgAlert.setNeutralButton(this.ctx.getString(buttons.getNeutralButton()), new NeutralClickListener(builder));
|
||||
|
||||
dlgAlert.setCancelable(false);
|
||||
dlgAlert.create().show();
|
||||
}
|
||||
|
||||
/**
|
||||
* A click listener for a dialog button that calls a given {@link Runnable} when the button is
|
||||
* clicked.
|
||||
|
|
@ -276,24 +218,6 @@ public final class DialogService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A click listener for a dialog button that calls the neutral action handler of the given
|
||||
* {@link CustomDialogBuilder} when the button is clicked.
|
||||
*/
|
||||
private static final class NeutralClickListener implements DialogInterface.OnClickListener {
|
||||
private final CustomDialogBuilder.Three builder;
|
||||
|
||||
private NeutralClickListener(CustomDialogBuilder.Three builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int which) {
|
||||
Log.d("Closing dialog, which=%s (neutral)", which); //NON-NLS
|
||||
this.builder.onNeutral();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A click listener for a dialog button that calls the positive action handler of the given
|
||||
* {@link CustomDialogBuilder} when the button is clicked.
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
package info.varden.hauk.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.manager.SessionManager;
|
||||
import info.varden.hauk.struct.Share;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Prompt that confirms with the user if they really intended to stop sharing their location.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class StopSharingConfirmationPrompt implements CustomDialogBuilder.Three {
|
||||
private final PreferenceManager prefs;
|
||||
private final SessionManager manager;
|
||||
private final Share share;
|
||||
|
||||
public StopSharingConfirmationPrompt(PreferenceManager prefs, SessionManager manager) {
|
||||
this(prefs, manager, null);
|
||||
}
|
||||
|
||||
public StopSharingConfirmationPrompt(PreferenceManager prefs, SessionManager manager, Share share) {
|
||||
this.prefs = prefs;
|
||||
this.manager = manager;
|
||||
this.share = share;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNeutral() {
|
||||
Log.i("Disabling future confirmation prompts when stopping shares"); //NON-NLS
|
||||
this.prefs.set(Constants.PREF_CONFIRM_STOP, false);
|
||||
if (this.share == null) {
|
||||
this.manager.stopSharing();
|
||||
} else {
|
||||
this.manager.stopSharing(this.share);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPositive() {
|
||||
if (this.share == null) {
|
||||
this.manager.stopSharing();
|
||||
} else {
|
||||
this.manager.stopSharing(this.share);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegative() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View createView(Context ctx) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,6 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.R;
|
||||
import info.varden.hauk.global.ui.AuthorizationActivity;
|
||||
|
|
@ -17,15 +13,13 @@ import info.varden.hauk.global.ui.DisplayShareDialogListener;
|
|||
import info.varden.hauk.global.ui.toast.GNSSStatusUpdateListenerImpl;
|
||||
import info.varden.hauk.global.ui.toast.SessionInitiationResponseHandlerImpl;
|
||||
import info.varden.hauk.global.ui.toast.ShareListenerImpl;
|
||||
import info.varden.hauk.http.ConnectionParameters;
|
||||
import info.varden.hauk.http.SessionInitiationPacket;
|
||||
import info.varden.hauk.http.security.CertificateValidationPolicy;
|
||||
import info.varden.hauk.manager.SessionManager;
|
||||
import info.varden.hauk.struct.AdoptabilityPreference;
|
||||
import info.varden.hauk.system.LocationPermissionsNotGrantedException;
|
||||
import info.varden.hauk.system.LocationServicesDisabledException;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
import info.varden.hauk.utils.DeprecationMigrator;
|
||||
import info.varden.hauk.utils.PreferenceManager;
|
||||
import info.varden.hauk.utils.TimeUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -175,34 +169,12 @@ public final class Receiver extends BroadcastReceiver {
|
|||
String password = intent.hasExtra(Constants.EXTRA_SESSION_PASSWORD) ? intent.getStringExtra(Constants.EXTRA_SESSION_PASSWORD) : fallback.get(Constants.PREF_PASSWORD_ENCRYPTED);
|
||||
int duration = intent.hasExtra(Constants.EXTRA_SESSION_DURATION) ? intent.getIntExtra(Constants.EXTRA_SESSION_DURATION, 0) : TimeUtils.timeUnitsToSeconds(fallback.get(Constants.PREF_DURATION), fallback.get(Constants.PREF_DURATION_UNIT));
|
||||
int interval = intent.hasExtra(Constants.EXTRA_SESSION_INTERVAL) ? intent.getIntExtra(Constants.EXTRA_SESSION_INTERVAL, 0) : fallback.get(Constants.PREF_INTERVAL);
|
||||
float minDistance = intent.hasExtra(Constants.EXTRA_SESSION_MIN_DISTANCE) ? intent.getIntExtra(Constants.EXTRA_SESSION_MIN_DISTANCE, 0) : fallback.get(Constants.PREF_UPDATE_DISTANCE);
|
||||
String customID = intent.hasExtra(Constants.EXTRA_SESSION_CUSTOM_ID) ? intent.getStringExtra(Constants.EXTRA_SESSION_CUSTOM_ID) : fallback.get(Constants.PREF_CUSTOM_ID);
|
||||
|
||||
String e2ePass = "";
|
||||
if (intent.hasExtra(Constants.EXTRA_SESSION_E2E_PASSWORD)) {
|
||||
e2ePass = intent.getStringExtra(Constants.EXTRA_SESSION_E2E_PASSWORD);
|
||||
} else if (fallback.get(Constants.PREF_ENABLE_E2E)) {
|
||||
e2ePass = fallback.get(Constants.PREF_E2E_PASSWORD);
|
||||
}
|
||||
String e2ePass = intent.hasExtra(Constants.EXTRA_SESSION_E2E_PASSWORD) ? intent.getStringExtra(Constants.EXTRA_SESSION_E2E_PASSWORD) : fallback.get(Constants.PREF_E2E_PASSWORD);
|
||||
|
||||
assert server != null;
|
||||
server = server.endsWith("/") ? server : server + "/";
|
||||
|
||||
int timeout = fallback.get(Constants.PREF_CONNECTION_TIMEOUT) * (int) TimeUtils.MILLIS_PER_SECOND;
|
||||
CertificateValidationPolicy tlsPolicy = fallback.get(Constants.PREF_CERTIFICATE_VALIDATION);
|
||||
ConnectionParameters connParams;
|
||||
Proxy.Type proxyType = fallback.get(Constants.PREF_PROXY_TYPE).resolve();
|
||||
if (proxyType == Proxy.Type.DIRECT) {
|
||||
connParams = new ConnectionParameters(Proxy.NO_PROXY.type(), Proxy.NO_PROXY.address(), timeout, tlsPolicy);
|
||||
} else if (proxyType != null) {
|
||||
SocketAddress proxyAddr = new InetSocketAddress(fallback.get(Constants.PREF_PROXY_HOST).trim(), fallback.get(Constants.PREF_PROXY_PORT));
|
||||
connParams = new ConnectionParameters(proxyType, proxyAddr, timeout, tlsPolicy);
|
||||
} else {
|
||||
connParams = new ConnectionParameters(null, null, timeout, tlsPolicy);
|
||||
}
|
||||
|
||||
SessionInitiationPacket.InitParameters initParams = new SessionInitiationPacket.InitParameters(server, username, password, duration, interval, minDistance, customID, e2ePass);
|
||||
initParams.setConnectionParameters(connParams);
|
||||
return initParams;
|
||||
return new SessionInitiationPacket.InitParameters(server, username, password, duration, interval, customID, e2ePass);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,6 @@ public final class GNSSStatusUpdateListenerImpl implements GNSSStatusUpdateListe
|
|||
Toast.makeText(this.ctx, R.string.label_status_wait, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGNSSConnectionLost() {
|
||||
Toast.makeText(this.ctx, R.string.label_status_lost_gnss, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoarseLocationReceived() {
|
||||
Toast.makeText(this.ctx, R.string.label_status_coarse, Toast.LENGTH_LONG).show();
|
||||
|
|
@ -46,14 +41,4 @@ public final class GNSSStatusUpdateListenerImpl implements GNSSStatusUpdateListe
|
|||
public void onAccurateLocationReceived() {
|
||||
Toast.makeText(this.ctx, R.string.label_status_ok, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionLost() {
|
||||
// Silently ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionRestored() {
|
||||
// Silently ignore
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import info.varden.hauk.struct.Share;
|
|||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class ShareListenerImpl implements ShareListener {
|
||||
public class ShareListenerImpl implements ShareListener {
|
||||
/**
|
||||
* Android application context.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ public abstract class AdoptSharePacket extends Packet {
|
|||
* @param origin The share ID of the share to adopt.
|
||||
* @param nickname The nickname that should be assigned to the user when adopted.
|
||||
*/
|
||||
protected AdoptSharePacket(Context ctx, Share target, String origin, String nickname) {
|
||||
super(ctx, target.getSession().getServerURL(), target.getSession().getConnectionParameters(), Constants.URL_PATH_ADOPT_SHARE);
|
||||
public AdoptSharePacket(Context ctx, Share target, String origin, String nickname) {
|
||||
super(ctx, target.getSession().getServerURL(), Constants.URL_PATH_ADOPT_SHARE);
|
||||
this.nickname = nickname;
|
||||
setParameter(Constants.PACKET_PARAM_SESSION_ID, target.getSession().getID());
|
||||
setParameter(Constants.PACKET_PARAM_NICKNAME, nickname);
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
package info.varden.hauk.http;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.Proxy;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import info.varden.hauk.http.security.CertificateValidationPolicy;
|
||||
|
||||
/**
|
||||
* Structure used to store connection parameters for backend connections, e.g. proxy details.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class ConnectionParameters implements Serializable {
|
||||
private static final long serialVersionUID = -6275381322711990147L;
|
||||
|
||||
/**
|
||||
* The type of proxy to use for the connection.
|
||||
*/
|
||||
private final Proxy.Type proxyType;
|
||||
|
||||
/**
|
||||
* The proxy endpoint address.
|
||||
*/
|
||||
private final SocketAddress proxyAddress;
|
||||
|
||||
/**
|
||||
* The maximum connection timeout, in milliseconds.
|
||||
*/
|
||||
private final int connectTimeout;
|
||||
|
||||
/**
|
||||
* TLS certificate validation policy for the connection.
|
||||
*/
|
||||
private final CertificateValidationPolicy tlsPolicy;
|
||||
|
||||
public ConnectionParameters(Proxy.Type proxyType, SocketAddress proxyAddress, int connectTimeout, CertificateValidationPolicy tlsPolicy) {
|
||||
this.proxyType = proxyType;
|
||||
this.proxyAddress = proxyAddress;
|
||||
this.connectTimeout = connectTimeout;
|
||||
this.tlsPolicy = tlsPolicy;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Proxy getProxy() {
|
||||
return this.proxyType == null || this.proxyAddress == null ? null : new Proxy(this.proxyType, this.proxyAddress);
|
||||
}
|
||||
|
||||
int getTimeout() {
|
||||
return this.connectTimeout;
|
||||
}
|
||||
|
||||
CertificateValidationPolicy getTLSPolicy() {
|
||||
return this.tlsPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConnectionParameters{"
|
||||
+ "proxyType=" + this.proxyType
|
||||
+ ",proxyAddress=" + this.proxyAddress
|
||||
+ ",connectTimeout=" + this.connectTimeout
|
||||
+ ",tlsPolicy=" + this.tlsPolicy
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -10,27 +10,18 @@ import java.io.OutputStream;
|
|||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import info.varden.hauk.BuildConfig;
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.R;
|
||||
import info.varden.hauk.http.security.CertificateValidationPolicy;
|
||||
import info.varden.hauk.http.security.InsecureHostnameVerifier;
|
||||
import info.varden.hauk.http.security.InsecureTrustManager;
|
||||
import info.varden.hauk.struct.Version;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* An asynchronous task that HTTP-POSTs data to a given URL with the given POST fields.
|
||||
|
|
@ -38,6 +29,11 @@ import info.varden.hauk.utils.Log;
|
|||
* @author Marius Lindvall
|
||||
*/
|
||||
public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String, ConnectionThread.Response> {
|
||||
/**
|
||||
* The maximum time to wait for the request to complete before the request times out.
|
||||
*/
|
||||
private static final int TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* A callback that is called after the request is completed. Contains received data, or errors,
|
||||
* if applicable.
|
||||
|
|
@ -66,30 +62,11 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
@Override
|
||||
@SuppressWarnings("HardCodedStringLiteral")
|
||||
protected final Response doInBackground(Request... params) {
|
||||
int seq = new Random().nextInt();
|
||||
try {
|
||||
Request req = params[0];
|
||||
Log.v("Assigning seq=%s for request %s", seq, req);
|
||||
|
||||
// Configure and open the connection.
|
||||
Proxy proxy = req.getParameters().getProxy();
|
||||
URL url = new URL(req.getURL());
|
||||
HttpURLConnection client = (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy));
|
||||
if (url.getHost().endsWith(".onion") && url.getProtocol().equals("https")) {
|
||||
// Check if TLS validation should be disabled for .onion addresses over HTTPS.
|
||||
if (req.getParameters().getTLSPolicy().equals(CertificateValidationPolicy.DISABLE_TRUST_ANCHOR_ONION)) {
|
||||
Log.v("[seq:%s] Setting insecure SSL socket factory for connection to comply with TLS policy", seq);
|
||||
((HttpsURLConnection) client).setSSLSocketFactory(InsecureTrustManager.getSocketFactory());
|
||||
} else if (req.getParameters().getTLSPolicy().equals(CertificateValidationPolicy.DISABLE_ALL_ONION)) {
|
||||
Log.v("[seq:%s] Setting insecure SSL socket factory and disabling hostname validation for connection to comply with TLS policy", seq);
|
||||
((HttpsURLConnection) client).setSSLSocketFactory(InsecureTrustManager.getSocketFactory());
|
||||
((HttpsURLConnection) client).setHostnameVerifier(new InsecureHostnameVerifier());
|
||||
}
|
||||
}
|
||||
|
||||
// Post the data.
|
||||
Log.v("[seq:%s] Setting connection parameters", seq);
|
||||
client.setConnectTimeout(req.getParameters().getTimeout());
|
||||
// Open a connection to the Hauk server and post the data.
|
||||
URL url = new URL(params[0].getURL());
|
||||
HttpURLConnection client = (HttpURLConnection) url.openConnection();
|
||||
client.setConnectTimeout(TIMEOUT);
|
||||
client.setRequestMethod("POST");
|
||||
client.setRequestProperty("Accept-Language", Locale.getDefault().getLanguage());
|
||||
client.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
|
@ -97,7 +74,6 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
client.setDoInput(true);
|
||||
client.setDoOutput(true);
|
||||
|
||||
Log.v("[seq:%s] Writing data to socket", seq);
|
||||
OutputStream os = client.getOutputStream();
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
|
||||
writer.write(params[0].getURLEncodedData());
|
||||
|
|
@ -105,7 +81,6 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
os.close();
|
||||
|
||||
int response = client.getResponseCode();
|
||||
Log.v("[seq:%s] Response code for request is %s", seq, response);
|
||||
if (response == HttpURLConnection.HTTP_OK) {
|
||||
// The response should be returned as an array of strings where each element of the
|
||||
// array is one line of output. Hauk uses this array as an argument array when
|
||||
|
|
@ -114,20 +89,16 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
ArrayList<String> lines = new ArrayList<>();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
|
||||
while ((line = br.readLine()) != null) {
|
||||
Log.v("[seq:%s] resp += \"%s\"", seq, line);
|
||||
lines.add(line);
|
||||
}
|
||||
br.close();
|
||||
Log.v("[seq:%s] Returning success response", seq);
|
||||
return new Response(null, lines.toArray(new String[0]), new Version(client.getHeaderField(Constants.HTTP_HEADER_HAUK_VERSION)));
|
||||
} else {
|
||||
// Hauk only returns HTTP 200; any other response should be considered an error.
|
||||
Log.v("[seq:%s] Returning HTTP code failure response", seq);
|
||||
return new Response(new ServerException(String.format(params[0].getContext().getString(R.string.err_response_code), String.valueOf(response))), null, null);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// If an exception occurred, return no data.
|
||||
Log.v("[seq:%s] Returning exception failure response", ex, seq);
|
||||
return new Response(ex, null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -151,21 +122,18 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
private final Context ctx;
|
||||
private final String url;
|
||||
private final Map<String, String> data;
|
||||
private final ConnectionParameters params;
|
||||
|
||||
/**
|
||||
* Constructs an HTTP request that should be passed through a proxy.
|
||||
* Constructs an HTTP request.
|
||||
*
|
||||
* @param ctx Android application context.
|
||||
* @param url The URL to POST data to.
|
||||
* @param data A set of key-value pairs consisting of data to be sent in the POST request.
|
||||
* @param params The parameters that should be used when establishing the connection.
|
||||
* @param ctx Android application context.
|
||||
* @param url The URL to POST data to.
|
||||
* @param data A set of key-value pairs consisting of data to be sent in the POST request.
|
||||
*/
|
||||
Request(Context ctx, String url, Map<String, String> data, ConnectionParameters params) {
|
||||
Request(Context ctx, String url, Map<String, String> data) {
|
||||
this.ctx = ctx;
|
||||
this.url = url;
|
||||
this.data = Collections.unmodifiableMap(data);
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
private Context getContext() {
|
||||
|
|
@ -176,10 +144,6 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
return this.url;
|
||||
}
|
||||
|
||||
private ConnectionParameters getParameters() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
private String getURLEncodedData() throws UnsupportedEncodingException {
|
||||
// Create a URL-encoded data body for the HTTP request.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
|
@ -193,22 +157,6 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
String body;
|
||||
try {
|
||||
body = getURLEncodedData();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e("Unsupported encoding used in Request#toString()", e);
|
||||
body = "<exception>";
|
||||
}
|
||||
return "Request{"
|
||||
+ "url=" + this.url
|
||||
+ ",body=" + body
|
||||
+ ",params=" + this.params
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -248,15 +196,6 @@ public class ConnectionThread extends AsyncTask<ConnectionThread.Request, String
|
|||
Version getServerVersion() {
|
||||
return this.ver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Response{"
|
||||
+ "ex=" + this.ex
|
||||
+ ",data=" + Arrays.toString(this.data)
|
||||
+ ",ver=" + this.ver
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import javax.crypto.Cipher;
|
|||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.R;
|
||||
import info.varden.hauk.http.parameter.LocationProvider;
|
||||
import info.varden.hauk.struct.Session;
|
||||
import info.varden.hauk.struct.Version;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
|
@ -44,15 +43,14 @@ public abstract class LocationUpdatePacket extends Packet {
|
|||
* @param session The session for which location is being updated.
|
||||
* @param location The updated location data obtained from GNSS/network sensors.
|
||||
*/
|
||||
protected LocationUpdatePacket(Context ctx, Session session, Location location, LocationProvider accuracy) {
|
||||
super(ctx, session.getServerURL(), session.getConnectionParameters(), Constants.URL_PATH_POST_LOCATION);
|
||||
protected LocationUpdatePacket(Context ctx, Session session, Location location) {
|
||||
super(ctx, session.getServerURL(), Constants.URL_PATH_POST_LOCATION);
|
||||
setParameter(Constants.PACKET_PARAM_SESSION_ID, session.getID());
|
||||
|
||||
if (session.getDerivableE2EKey() == null) {
|
||||
// If not using end-to-end encryption, send parameters in plain text.
|
||||
setParameter(Constants.PACKET_PARAM_LATITUDE, String.valueOf(location.getLatitude()));
|
||||
setParameter(Constants.PACKET_PARAM_LONGITUDE, String.valueOf(location.getLongitude()));
|
||||
setParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, String.valueOf(accuracy.getMode()));
|
||||
setParameter(Constants.PACKET_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND));
|
||||
|
||||
// Not all devices provide these parameters:
|
||||
|
|
@ -68,7 +66,6 @@ public abstract class LocationUpdatePacket extends Packet {
|
|||
|
||||
setParameter(Constants.PACKET_PARAM_LATITUDE, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getLatitude()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
|
||||
setParameter(Constants.PACKET_PARAM_LONGITUDE, Base64.encodeToString(cipher.doFinal(String.valueOf(location.getLongitude()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
|
||||
setParameter(Constants.PACKET_PARAM_PROVIDER_ACCURACY, Base64.encodeToString(cipher.doFinal(String.valueOf(accuracy.getMode()).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
|
||||
setParameter(Constants.PACKET_PARAM_TIMESTAMP, Base64.encodeToString(cipher.doFinal(String.valueOf(System.currentTimeMillis() / (double) TimeUtils.MILLIS_PER_SECOND).getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT));
|
||||
|
||||
// Not all devices provide these parameters:
|
||||
|
|
@ -80,9 +77,8 @@ public abstract class LocationUpdatePacket extends Packet {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("DesignForExtension")
|
||||
@Override
|
||||
protected void onSuccess(String[] data, Version backendVersion) throws ServerException {
|
||||
protected final void onSuccess(String[] data, Version backendVersion) throws ServerException {
|
||||
// Somehow the data array can be empty? Check for this.
|
||||
if (data.length < 1) {
|
||||
throw new ServerException(getContext(), R.string.err_empty);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public abstract class NewLinkPacket extends Packet {
|
|||
* @param allowAdoption Whether or not this share should be adoptable.
|
||||
*/
|
||||
protected NewLinkPacket(Context ctx, Session session, boolean allowAdoption) {
|
||||
super(ctx, session.getServerURL(), session.getConnectionParameters(), Constants.URL_PATH_CREATE_NEW_LINK);
|
||||
super(ctx, session.getServerURL(), Constants.URL_PATH_CREATE_NEW_LINK);
|
||||
this.session = session;
|
||||
setParameter(Constants.PACKET_PARAM_SESSION_ID, session.getID());
|
||||
setParameter(Constants.PACKET_PARAM_ADOPTABLE, allowAdoption ? "1" : "0");
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ public abstract class Packet {
|
|||
private final Context ctx;
|
||||
private final String server;
|
||||
private final String path;
|
||||
private final ConnectionParameters connParams;
|
||||
|
||||
/**
|
||||
* Called if the request is successful.
|
||||
|
|
@ -48,12 +47,11 @@ public abstract class Packet {
|
|||
* @param server The full Hauk server base URL, including trailing slash.
|
||||
* @param path The path underneath the base URL that should be called.
|
||||
*/
|
||||
Packet(Context ctx, String server, ConnectionParameters connParams, String path) {
|
||||
Packet(Context ctx, String server, String path) {
|
||||
this.params = new HashMap<>();
|
||||
this.ctx = ctx;
|
||||
this.server = server;
|
||||
this.path = path;
|
||||
this.connParams = connParams;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,6 +94,6 @@ public abstract class Packet {
|
|||
onFailure(e);
|
||||
}
|
||||
}
|
||||
}).execute(new ConnectionThread.Request(this.ctx, this.server + this.path, this.params, this.connParams));
|
||||
}).execute(new ConnectionThread.Request(this.ctx, this.server + this.path, this.params));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import android.content.Context;
|
|||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public class ServerException extends Exception {
|
||||
class ServerException extends Exception {
|
||||
private static final long serialVersionUID = 2879124634145201633L;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class SessionInitiationPacket extends Packet {
|
|||
private final byte[] salt;
|
||||
|
||||
private SessionInitiationPacket(Context ctx, InitParameters params, ResponseHandler handler) {
|
||||
super(ctx, params.getServerURL(), params.getConnectionParameters(), Constants.URL_PATH_CREATE_SHARE);
|
||||
super(ctx, params.getServerURL(), Constants.URL_PATH_CREATE_SHARE);
|
||||
this.params = params;
|
||||
this.handler = handler;
|
||||
if (params.getUsername() != null) {
|
||||
|
|
@ -155,16 +155,7 @@ public class SessionInitiationPacket extends Packet {
|
|||
}
|
||||
|
||||
// Create a share and pass it upstream.
|
||||
Session session = new Session(
|
||||
this.params.getServerURL(),
|
||||
this.params.getConnectionParameters(),
|
||||
backendVersion,
|
||||
sessionID,
|
||||
this.params.getDuration() * TimeUtils.MILLIS_PER_SECOND + System.currentTimeMillis(),
|
||||
this.params.getInterval(),
|
||||
this.params.getMinimumDistance(),
|
||||
e2eParams
|
||||
);
|
||||
Session session = new Session(this.params.getServerURL(), backendVersion, sessionID, this.params.getDuration() * TimeUtils.MILLIS_PER_SECOND + System.currentTimeMillis(), this.params.getInterval(), e2eParams);
|
||||
Share share = new Share(session, viewURL, viewID, joinCode, this.mode);
|
||||
|
||||
this.handler.onSessionInitiated(share);
|
||||
|
|
@ -228,12 +219,9 @@ public class SessionInitiationPacket extends Packet {
|
|||
private final String password;
|
||||
private final int duration;
|
||||
private final int interval;
|
||||
private final float minDistance;
|
||||
private final String customID;
|
||||
private final String e2ePass;
|
||||
|
||||
private ConnectionParameters connParams;
|
||||
|
||||
/**
|
||||
* Declares initialization parameters for a session initiation request.
|
||||
*
|
||||
|
|
@ -243,14 +231,12 @@ public class SessionInitiationPacket extends Packet {
|
|||
* @param duration The duration, in seconds, to run the share for.
|
||||
* @param interval The interval, in seconds, between each sent location update.
|
||||
*/
|
||||
public InitParameters(String server, String username, String password, int duration, int interval, float minDistance, String customID, String e2ePass) {
|
||||
public InitParameters(String server, String username, String password, int duration, int interval, String customID, String e2ePass) {
|
||||
this.server = server;
|
||||
this.connParams = null;
|
||||
this.username = username == null || username.isEmpty() ? null : username;
|
||||
this.password = password;
|
||||
this.duration = duration;
|
||||
this.interval = interval;
|
||||
this.minDistance = minDistance;
|
||||
this.customID = customID == null || customID.isEmpty() ? null : customID;
|
||||
this.e2ePass = e2ePass == null || e2ePass.isEmpty() ? null : e2ePass;
|
||||
}
|
||||
|
|
@ -259,14 +245,6 @@ public class SessionInitiationPacket extends Packet {
|
|||
return this.server;
|
||||
}
|
||||
|
||||
public void setConnectionParameters(ConnectionParameters connParams) {
|
||||
this.connParams = connParams;
|
||||
}
|
||||
|
||||
ConnectionParameters getConnectionParameters() {
|
||||
return this.connParams;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getUsername() {
|
||||
return this.username;
|
||||
|
|
@ -284,10 +262,6 @@ public class SessionInitiationPacket extends Packet {
|
|||
return this.interval;
|
||||
}
|
||||
|
||||
float getMinimumDistance() {
|
||||
return this.minDistance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getCustomID() {
|
||||
return this.customID;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public abstract class StopSharingPacket extends Packet {
|
|||
* @param session The session to delete.
|
||||
*/
|
||||
protected StopSharingPacket(Context ctx, Session session) {
|
||||
super(ctx, session.getServerURL(), session.getConnectionParameters(), Constants.URL_PATH_STOP_SHARING);
|
||||
super(ctx, session.getServerURL(), Constants.URL_PATH_STOP_SHARING);
|
||||
setParameter(Constants.PACKET_PARAM_SESSION_ID, session.getID());
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ public abstract class StopSharingPacket extends Packet {
|
|||
* @param share The share to stop.
|
||||
*/
|
||||
protected StopSharingPacket(Context ctx, Share share) {
|
||||
super(ctx, share.getSession().getServerURL(), share.getSession().getConnectionParameters(), Constants.URL_PATH_STOP_SHARING);
|
||||
super(ctx, share.getSession().getServerURL(), Constants.URL_PATH_STOP_SHARING);
|
||||
setParameter(Constants.PACKET_PARAM_SESSION_ID, share.getSession().getID());
|
||||
setParameter(Constants.PACKET_PARAM_SHARE_ID, share.getID());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package info.varden.hauk.http.parameter;
|
||||
|
||||
/**
|
||||
* An enum that identifies the currently active location provider on the device.
|
||||
*/
|
||||
public enum LocationProvider {
|
||||
FINE(0),
|
||||
COARSE(1);
|
||||
|
||||
private final int mode;
|
||||
|
||||
LocationProvider(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LocationProvider<mode=" + this.mode + ">";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package info.varden.hauk.http.proxy;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.http.FailureHandler;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Async task that checks whether or not a proxy address must be resolved, resolves it if necessary,
|
||||
* and passes execution back to callbacks. This is necessary because proxy hostname resolution is
|
||||
* network-dependent and therefore not permitted on the UI thread.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public abstract class NameResolverTask extends AsyncTask<Void, Void, Proxy> implements FailureHandler {
|
||||
/**
|
||||
* Called if hostname resolution is required.
|
||||
*
|
||||
* @param hostname The hostname of the proxy.
|
||||
*/
|
||||
protected abstract void onResolutionStarted(String hostname);
|
||||
|
||||
/**
|
||||
* Called if the hostname could not be resolved. This is a failure state.
|
||||
*
|
||||
* @param hostname The hostname that could not be resolved.
|
||||
*/
|
||||
protected abstract void onHostUnresolved(String hostname);
|
||||
|
||||
/**
|
||||
* Called if proxy hostname resolution was required and was successful, or if resolution was
|
||||
* skipped because no proxy is in use.
|
||||
*
|
||||
* @param proxy A proxy that can be used when connecting to the backend. May be null if no proxy
|
||||
* should be used.
|
||||
*/
|
||||
protected abstract void onSuccess(@Nullable Proxy proxy);
|
||||
|
||||
private final Proxy.Type proxyType;
|
||||
private final String proxyHost;
|
||||
private final int proxyPort;
|
||||
|
||||
private boolean wasSuccessful = true;
|
||||
|
||||
protected NameResolverTask(PreferenceManager prefs) {
|
||||
this.proxyType = prefs.get(Constants.PREF_PROXY_TYPE).resolve();
|
||||
this.proxyHost = prefs.get(Constants.PREF_PROXY_HOST).trim();
|
||||
this.proxyPort = prefs.get(Constants.PREF_PROXY_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the proxy resolution process.
|
||||
*/
|
||||
public final void resolve() {
|
||||
if (this.proxyType == Proxy.Type.DIRECT) {
|
||||
// If explicitly using no proxy, forward the NO_PROXY upstream.
|
||||
Log.i("Using direct connection to backend server"); //NON-NLS
|
||||
onSuccess(Proxy.NO_PROXY);
|
||||
|
||||
} else if (this.proxyType == null) {
|
||||
// If system default is set, forward null proxy upstream.
|
||||
Log.i("Using system default proxy to backend server"); //NON-NLS
|
||||
onSuccess(null);
|
||||
|
||||
} else {
|
||||
// Otherwise, a proxy is in use and a hostname may need to be resolved.
|
||||
Log.i("Using a proxy; resolving the hostname for %s", this.proxyHost); //NON-NLS
|
||||
onResolutionStarted(this.proxyHost);
|
||||
this.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected final Proxy doInBackground(Void... params) {
|
||||
try {
|
||||
Log.i("Resolving proxy hostname %s...", this.proxyHost); //NON-NLS
|
||||
InetSocketAddress proxyAddr = new InetSocketAddress(this.proxyHost, this.proxyPort);
|
||||
|
||||
// Check if the hostname could be resolved.
|
||||
if (proxyAddr.isUnresolved()) {
|
||||
Log.e("Proxy hostname %s is unresolved", this.proxyHost); //NON-NLS
|
||||
this.wasSuccessful = false;
|
||||
onHostUnresolved(this.proxyHost);
|
||||
return null;
|
||||
} else {
|
||||
Log.v("Proxy hostname resolution was successful!"); //NON-NLS
|
||||
return new Proxy(this.proxyType, proxyAddr);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
Log.e("Proxy setup failed for proxy %s:%s", ex, this.proxyHost, this.proxyPort); //NON-NLS
|
||||
this.wasSuccessful = false;
|
||||
onFailure(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onPostExecute(Proxy result) {
|
||||
if (this.wasSuccessful) onSuccess(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package info.varden.hauk.http.security;
|
||||
|
||||
import info.varden.hauk.system.preferences.IndexedEnum;
|
||||
|
||||
/**
|
||||
* An enum representing the various types of proxies available on the system, and their ID when
|
||||
* stored in preferences.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class CertificateValidationPolicy extends IndexedEnum<CertificateValidationPolicy> {
|
||||
private static final long serialVersionUID = -1906017485528370776L;
|
||||
|
||||
public static final CertificateValidationPolicy VALIDATE_ALL = new CertificateValidationPolicy(0);
|
||||
public static final CertificateValidationPolicy DISABLE_TRUST_ANCHOR_ONION = new CertificateValidationPolicy(1);
|
||||
public static final CertificateValidationPolicy DISABLE_ALL_ONION = new CertificateValidationPolicy(2);
|
||||
|
||||
private CertificateValidationPolicy(int index) {
|
||||
super(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CertificateValidationPolicy{" + super.toString() + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package info.varden.hauk.http.security;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
/**
|
||||
* Intentionally insecure hostname verifier used to ignore invalid hostnames if hostname validation
|
||||
* is disabled for a domain in preferences. Should be used with caution.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class InsecureHostnameVerifier implements HostnameVerifier {
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
package info.varden.hauk.http.security;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Intentionally insecure trust manager that accepts all trust anchors. Should be used with caution.
|
||||
*/
|
||||
public final class InsecureTrustManager implements X509TrustManager {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
|
||||
Log.v("Client certificate presented for %s", s); //NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
|
||||
Log.v("Server certificate presented for %s", x509Certificates[0]); //NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
Log.v("Got request for accepted issuers"); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
|
||||
public static SSLSocketFactory getSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
SSLContext context = SSLContext.getInstance("TLS"); //NON-NLS
|
||||
context.init(null, new TrustManager[] {new InsecureTrustManager()}, new SecureRandom());
|
||||
return context.getSocketFactory();
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ public final class AutoResumptionPrompter implements ResumeHandler {
|
|||
Log.i("Resuming shares..."); //NON-NLS
|
||||
AutoResumptionPrompter.this.resumptionHandler.clearResumableSession();
|
||||
for (Share share : this.shares) {
|
||||
AutoResumptionPrompter.this.manager.shareLocation(share, SessionInitiationReason.USER_RESUMED);
|
||||
AutoResumptionPrompter.this.manager.shareLocation(share);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,6 @@ public interface GNSSStatusUpdateListener {
|
|||
*/
|
||||
void onStarted();
|
||||
|
||||
/**
|
||||
* Called if the accurate GNSS location listener stops working. This implies that the coarse
|
||||
* location listener is now back in use and {@link #onCoarseLocationReceived()} may be called
|
||||
* again.
|
||||
*/
|
||||
void onGNSSConnectionLost();
|
||||
|
||||
/**
|
||||
* <p>Called on first reception of low-accuracy location data from the network. Should be
|
||||
* available almost instantly if the user device has network-based or other non-GNSS location
|
||||
|
|
@ -39,14 +32,4 @@ public interface GNSSStatusUpdateListener {
|
|||
* adequate GNSS signal reception.
|
||||
*/
|
||||
void onAccurateLocationReceived();
|
||||
|
||||
/**
|
||||
* Called if the backend server is unreachable.
|
||||
*/
|
||||
void onServerConnectionLost();
|
||||
|
||||
/**
|
||||
* Called if the backend server was unreachable, but is now reachable again.
|
||||
*/
|
||||
void onServerConnectionRestored();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
package info.varden.hauk.manager;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import info.varden.hauk.caching.ResumableSessions;
|
||||
import info.varden.hauk.caching.ResumeHandler;
|
||||
import info.varden.hauk.struct.Session;
|
||||
import info.varden.hauk.struct.Share;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* {@link ResumeHandler} implementation used by {@link SessionManager} to automatically resume
|
||||
* sessions following a service relaunch. This can happen if the main activity is terminated, but
|
||||
* the share itself keeps running in the background.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class ServiceRelauncher implements ResumeHandler {
|
||||
/**
|
||||
* The session manager to call to resume the shares.
|
||||
*/
|
||||
private final SessionManager manager;
|
||||
|
||||
/**
|
||||
* The manager's resumption handler. This is used to clear the resumption data before the shares
|
||||
* are resumed by the session manager, as the session manager will re-flag the shares as
|
||||
* resumable when it adds them to its internal share list.
|
||||
*/
|
||||
private final ResumableSessions resumptionHandler;
|
||||
|
||||
ServiceRelauncher(SessionManager manager, ResumableSessions resumptionHandler) {
|
||||
this.manager = manager;
|
||||
this.resumptionHandler = resumptionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharesFetched(Context ctx, Session session, Share[] shares) {
|
||||
Log.i("Resuming %s share(s) automatically found for session %s", shares.length, session); //NON-NLS
|
||||
// The shares provided by ResumableSessions do not have a session attached to them. Attach
|
||||
// it to the shares so that they can be shown properly by the prompt and so that the updates
|
||||
// have a backend to be broadcast to when the shares are resumed.
|
||||
this.resumptionHandler.clearResumableSession();
|
||||
for (Share share : shares) {
|
||||
share.setSession(session);
|
||||
this.manager.shareLocation(share, SessionInitiationReason.SERVICE_RELAUNCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package info.varden.hauk.manager;
|
||||
|
||||
import info.varden.hauk.struct.Session;
|
||||
import info.varden.hauk.struct.Share;
|
||||
|
||||
/**
|
||||
* Describes the reason a session was initiated.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public enum SessionInitiationReason {
|
||||
/**
|
||||
* The user requested to start a new sharing session.
|
||||
*/
|
||||
USER_STARTED,
|
||||
|
||||
/**
|
||||
* The user requested to resume a previous sharing session.
|
||||
*/
|
||||
USER_RESUMED,
|
||||
|
||||
/**
|
||||
* The sharing session is automatically resumed as a result of a relaunch of the location
|
||||
* sharing service.
|
||||
*/
|
||||
SERVICE_RELAUNCH,
|
||||
|
||||
/**
|
||||
* The session was created because a share was added to it. This should never be received by
|
||||
* {@link SessionListener#onSessionCreated(Session, Share, SessionInitiationReason)} under any
|
||||
* normal circumstances.
|
||||
*/
|
||||
SHARE_ADDED
|
||||
}
|
||||
|
|
@ -15,9 +15,8 @@ public interface SessionListener {
|
|||
*
|
||||
* @param session The session that was created.
|
||||
* @param share The share that the session was created for.
|
||||
* @param reason The reason the session was created.
|
||||
*/
|
||||
void onSessionCreated(Session session, Share share, SessionInitiationReason reason);
|
||||
void onSessionCreated(Session session, Share share);
|
||||
|
||||
/**
|
||||
* Called if the session could not be initiated due to missing location permissions.
|
||||
|
|
|
|||
|
|
@ -63,12 +63,6 @@ public abstract class SessionManager {
|
|||
*/
|
||||
private final StopSharingCallback stopCallback;
|
||||
|
||||
/**
|
||||
* Intent for the location pusher, so that it can be stopped if already running when launching
|
||||
* the app.
|
||||
*/
|
||||
private static Intent pusher = null;
|
||||
|
||||
/**
|
||||
* Android application context.
|
||||
*/
|
||||
|
|
@ -127,7 +121,6 @@ public abstract class SessionManager {
|
|||
// Called when sharing ends. Clear the active session, and all collections of active
|
||||
// shares present in this class, then propagate this stop message upstream to all
|
||||
// session listeners.
|
||||
Log.d("Performing stop task cleanup for task %s and stopping timed callback on handler %s", this, SessionManager.this.handler); //NON-NLS
|
||||
SessionManager.this.activeSession = null;
|
||||
SessionManager.this.handler.removeCallbacksAndMessages(null);
|
||||
SessionManager.this.resumable.clearResumableSession();
|
||||
|
|
@ -189,21 +182,7 @@ public abstract class SessionManager {
|
|||
* any are found in storage.
|
||||
*/
|
||||
public final void resumeShares(ResumePrompt prompt) {
|
||||
// Check if the location push service is already running. This will happen if the main UI
|
||||
// activity is killed/stopped, but the app itself and the pushing service keeps running in
|
||||
// the background. If this happens, the push service should be silently restarted to ensure
|
||||
// it behaves properly with new instances of GNSSActiveHandler and StopSharingTask that will
|
||||
// be created and attached when creating a new SessionManager in MainActivity. There is
|
||||
// probably a cleaner way to do this.
|
||||
if (pusher != null) {
|
||||
Log.d("Pusher is non-null (%s), stopping and nulling it and calling service relauncher", pusher); //NON-NLS
|
||||
this.ctx.stopService(pusher);
|
||||
pusher = null;
|
||||
this.resumable.tryResumeShare(new ServiceRelauncher(this, this.resumable));
|
||||
} else {
|
||||
Log.d("Pusher is null, calling resumption prompter"); //NON-NLS
|
||||
this.resumable.tryResumeShare(new AutoResumptionPrompter(this, this.resumable, prompt));
|
||||
}
|
||||
this.resumable.tryResumeShare(new AutoResumptionPrompter(this, this.resumable, prompt));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -215,7 +194,7 @@ public abstract class SessionManager {
|
|||
* @throws LocationServicesDisabledException if location services are disabled.
|
||||
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
|
||||
*/
|
||||
private SessionInitiationPacket.ResponseHandler preSessionInitiation(final SessionInitiationResponseHandler upstreamCallback, final SessionInitiationReason reason) throws LocationServicesDisabledException, LocationPermissionsNotGrantedException {
|
||||
private SessionInitiationPacket.ResponseHandler preSessionInitiation(final SessionInitiationResponseHandler upstreamCallback) throws LocationServicesDisabledException, LocationPermissionsNotGrantedException {
|
||||
// Check for location permission and prompt the user if missing. This returns because the
|
||||
// checking function creates async dialogs here - the user is prompted to press the button
|
||||
// again instead.
|
||||
|
|
@ -236,7 +215,7 @@ public abstract class SessionManager {
|
|||
Log.i("Session was initiated for share %s; setting session resumable", share); //NON-NLS
|
||||
|
||||
// Proceed with the location share.
|
||||
shareLocation(share, reason);
|
||||
shareLocation(share);
|
||||
|
||||
upstreamCallback.onSuccess();
|
||||
}
|
||||
|
|
@ -271,7 +250,7 @@ public abstract class SessionManager {
|
|||
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
|
||||
*/
|
||||
public final void shareLocation(SessionInitiationPacket.InitParameters initParams, SessionInitiationResponseHandler upstreamCallback, AdoptabilityPreference allowAdoption) throws LocationPermissionsNotGrantedException, LocationServicesDisabledException {
|
||||
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback, SessionInitiationReason.USER_STARTED);
|
||||
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback);
|
||||
|
||||
// Create a handshake request and handle the response. The handshake transmits the duration
|
||||
// and interval to the server and waits for the server to return a session ID to confirm
|
||||
|
|
@ -290,7 +269,7 @@ public abstract class SessionManager {
|
|||
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
|
||||
*/
|
||||
public final void shareLocation(SessionInitiationPacket.InitParameters initParams, SessionInitiationResponseHandler upstreamCallback, String nickname) throws LocationPermissionsNotGrantedException, LocationServicesDisabledException {
|
||||
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback, SessionInitiationReason.USER_STARTED);
|
||||
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback);
|
||||
|
||||
// Create a handshake request and handle the response. The handshake transmits the duration
|
||||
// and interval to the server and waits for the server to return a session ID to confirm
|
||||
|
|
@ -310,7 +289,7 @@ public abstract class SessionManager {
|
|||
* @throws LocationPermissionsNotGrantedException if location permissions have not been granted.
|
||||
*/
|
||||
public final void shareLocation(SessionInitiationPacket.InitParameters initParams, SessionInitiationResponseHandler upstreamCallback, String nickname, String groupPin) throws LocationPermissionsNotGrantedException, LocationServicesDisabledException {
|
||||
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback, SessionInitiationReason.USER_STARTED);
|
||||
SessionInitiationPacket.ResponseHandler handler = preSessionInitiation(upstreamCallback);
|
||||
|
||||
// Create a handshake request and handle the response. The handshake transmits the duration
|
||||
// and interval to the server and waits for the server to return a session ID to confirm
|
||||
|
|
@ -325,10 +304,10 @@ public abstract class SessionManager {
|
|||
*
|
||||
* @param share The share to run against the server.
|
||||
*/
|
||||
public final void shareLocation(Share share, SessionInitiationReason reason) {
|
||||
public final void shareLocation(Share share) {
|
||||
// If we are not already sharing our location, initiate a new session.
|
||||
if (this.activeSession == null) {
|
||||
initiateSessionForExistingShare(share, reason);
|
||||
initiateSessionForExistingShare(share);
|
||||
}
|
||||
|
||||
Log.i("Attaching to share, share=%s", share); //NON-NLS
|
||||
|
|
@ -370,7 +349,7 @@ public abstract class SessionManager {
|
|||
*
|
||||
* @param share The share whose session should be pushed to.
|
||||
*/
|
||||
private void initiateSessionForExistingShare(Share share, SessionInitiationReason reason) {
|
||||
private void initiateSessionForExistingShare(Share share) {
|
||||
this.activeSession = share.getSession();
|
||||
this.resumable.setSessionResumable(this.activeSession);
|
||||
|
||||
|
|
@ -387,7 +366,6 @@ public abstract class SessionManager {
|
|||
pusher.setAction(LocationPushService.ACTION_ID);
|
||||
pusher.putExtra(Constants.EXTRA_SHARE, ReceiverDataRegistry.register(share));
|
||||
pusher.putExtra(Constants.EXTRA_STOP_TASK, ReceiverDataRegistry.register(this.stopTask));
|
||||
pusher.putExtra(Constants.EXTRA_HANDLER, ReceiverDataRegistry.register(this.handler));
|
||||
pusher.putExtra(Constants.EXTRA_GNSS_ACTIVE_TASK, ReceiverDataRegistry.register(statusUpdateHandler));
|
||||
|
||||
// Android O and higher require the service to be started as a foreground service for it
|
||||
|
|
@ -404,15 +382,10 @@ public abstract class SessionManager {
|
|||
// these so that they can be canceled when the location share ends.
|
||||
this.stopTask.updateTask(pusher);
|
||||
|
||||
// Required for session relaunches
|
||||
Log.d("Setting static pusher %s (was %s)", pusher, SessionManager.pusher); //NON-NLS
|
||||
//noinspection AssignmentToStaticFieldFromInstanceMethod
|
||||
SessionManager.pusher = pusher;
|
||||
|
||||
// stopTask is scheduled for expiration, but it could also be called if the user
|
||||
// manually stops the share, or if the app is destroyed.
|
||||
long expireIn = share.getSession().getRemainingMillis();
|
||||
Log.i("Scheduling session task %s for expiration in %s milliseconds on handler %s", this.stopTask, expireIn, this.handler); //NON-NLS
|
||||
Log.i("Scheduling session expiration in %s milliseconds", expireIn); //NON-NLS
|
||||
this.handler.postDelayed(this.stopTask, expireIn);
|
||||
|
||||
// Push the start event to upstream listeners.
|
||||
|
|
@ -420,7 +393,7 @@ public abstract class SessionManager {
|
|||
listener.onStarted();
|
||||
}
|
||||
for (SessionListener listener : this.upstreamSessionListeners) {
|
||||
listener.onSessionCreated(share.getSession(), share, reason);
|
||||
listener.onSessionCreated(share.getSession(), share);
|
||||
}
|
||||
} else {
|
||||
Log.w("Location permission has not been granted; sharing will not commence"); //NON-NLS
|
||||
|
|
@ -462,13 +435,6 @@ public abstract class SessionManager {
|
|||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoarseRebound() {
|
||||
for (GNSSStatusUpdateListener listener : SessionManager.this.upstreamUpdateHandlers) {
|
||||
listener.onGNSSConnectionLost();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoarseLocationReceived() {
|
||||
for (GNSSStatusUpdateListener listener : SessionManager.this.upstreamUpdateHandlers) {
|
||||
|
|
@ -483,20 +449,6 @@ public abstract class SessionManager {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionLost() {
|
||||
for (GNSSStatusUpdateListener listener : SessionManager.this.upstreamUpdateHandlers) {
|
||||
listener.onServerConnectionLost();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionRestored() {
|
||||
for (GNSSStatusUpdateListener listener : SessionManager.this.upstreamUpdateHandlers) {
|
||||
listener.onServerConnectionRestored();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShareListReceived(String linkFormat, String[] shareIDs) {
|
||||
List<String> currentShares = Arrays.asList(shareIDs);
|
||||
|
|
@ -508,7 +460,7 @@ public abstract class SessionManager {
|
|||
// that can be initiated by a remote user (through adoption).
|
||||
Share newShare = new Share(this.session, String.format(linkFormat, shareID), shareID, ShareMode.JOIN_GROUP);
|
||||
Log.i("Received unknown share %s from server", newShare); //NON-NLS
|
||||
shareLocation(newShare, SessionInitiationReason.SHARE_ADDED);
|
||||
shareLocation(newShare);
|
||||
}
|
||||
}
|
||||
for (Iterator<Map.Entry<String, Share>> it = SessionManager.this.knownShares.entrySet().iterator(); it.hasNext();) {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public abstract class StopSharingTask implements Runnable {
|
|||
* @param pusher A location handler that should be unregistered when sharing is stopped.
|
||||
*/
|
||||
final void updateTask(Intent pusher) {
|
||||
Log.i("Setting new update task %s in task %s", pusher, this); //NON-NLS
|
||||
Log.i("Setting new update task"); //NON-NLS
|
||||
this.pusher = pusher;
|
||||
this.canExecute = true;
|
||||
}
|
||||
|
|
@ -93,7 +93,6 @@ public abstract class StopSharingTask implements Runnable {
|
|||
*/
|
||||
@Override
|
||||
public final void run() {
|
||||
Log.d("Stop sharing task %s was called", this); //NON-NLS
|
||||
if (!this.canExecute) return;
|
||||
Log.i("Executing share stop task"); //NON-NLS
|
||||
this.canExecute = false;
|
||||
|
|
|
|||
|
|
@ -60,22 +60,6 @@ public abstract class HaukNotification {
|
|||
*/
|
||||
protected abstract void build(NotificationCompat.Builder builder) throws Exception;
|
||||
|
||||
/**
|
||||
* Displays the notification, or updates if it is already displayed.
|
||||
*/
|
||||
final void push() {
|
||||
NotificationManager manager = (NotificationManager) this.ctx.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (manager != null) {
|
||||
try {
|
||||
manager.notify(this.id, create());
|
||||
} catch (Exception e) {
|
||||
Log.e("Error while pushing notification", e); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
Log.e("Notification manager is null"); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a notification instance that can be displayed using NotificationManager.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -56,6 +56,6 @@ final class Receiver<T> {
|
|||
// the intent.
|
||||
intent.putExtra(Constants.EXTRA_BROADCAST_RECEIVER_REGISTRY_INDEX, ReceiverDataRegistry.register(this.data));
|
||||
|
||||
return PendingIntent.getBroadcast(this.ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
return PendingIntent.getBroadcast(this.ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,6 @@ final class ReopenIntent {
|
|||
PendingIntent toPending() {
|
||||
Intent intent = new Intent(this.ctx, this.activity);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
return PendingIntent.getActivity(this.ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
return PendingIntent.getActivity(this.ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import androidx.core.app.NotificationCompat;
|
|||
|
||||
import info.varden.hauk.R;
|
||||
import info.varden.hauk.manager.StopSharingTask;
|
||||
import info.varden.hauk.service.GNSSActiveHandler;
|
||||
import info.varden.hauk.struct.Share;
|
||||
import info.varden.hauk.ui.MainActivity;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
|
@ -16,7 +15,7 @@ import info.varden.hauk.utils.Log;
|
|||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class SharingNotification extends HaukNotification implements GNSSActiveHandler {
|
||||
public final class SharingNotification extends HaukNotification {
|
||||
/**
|
||||
* The share that this notification represents.
|
||||
*/
|
||||
|
|
@ -28,16 +27,6 @@ public final class SharingNotification extends HaukNotification implements GNSSA
|
|||
*/
|
||||
private final StopSharingTask stopSharingTask;
|
||||
|
||||
/**
|
||||
* A string resource representing the title currently displayed in the notification.
|
||||
*/
|
||||
private int notifyTitle;
|
||||
|
||||
/**
|
||||
* The old notification title, if switching to or from the "backend connection lost" title.
|
||||
*/
|
||||
private int lastTitle;
|
||||
|
||||
/**
|
||||
* Creates a persistent notification.
|
||||
*
|
||||
|
|
@ -49,14 +38,12 @@ public final class SharingNotification extends HaukNotification implements GNSSA
|
|||
super(ctx);
|
||||
this.share = share;
|
||||
this.stopSharingTask = stopSharingTask;
|
||||
this.notifyTitle = R.string.label_status_wait;
|
||||
this.lastTitle = R.string.label_status_wait;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void build(NotificationCompat.Builder builder) throws Exception {
|
||||
Log.v("Building sharing notification"); //NON-NLS
|
||||
builder.setContentTitle(getContext().getString(this.notifyTitle));
|
||||
builder.setContentTitle(getContext().getString(R.string.notify_title));
|
||||
builder.setContentText(String.format(getContext().getString(R.string.notify_body), this.share.getSession().getServerURL()));
|
||||
builder.setSmallIcon(R.drawable.ic_notify);
|
||||
builder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
||||
|
|
@ -68,41 +55,4 @@ public final class SharingNotification extends HaukNotification implements GNSSA
|
|||
|
||||
builder.setOngoing(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoarseRebound() {
|
||||
this.notifyTitle = R.string.label_status_lost_gnss;
|
||||
this.lastTitle = this.notifyTitle;
|
||||
push();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoarseLocationReceived() {
|
||||
this.notifyTitle = R.string.label_status_coarse;
|
||||
this.lastTitle = this.notifyTitle;
|
||||
push();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccurateLocationReceived() {
|
||||
this.notifyTitle = R.string.label_status_ok;
|
||||
this.lastTitle = this.notifyTitle;
|
||||
push();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionLost() {
|
||||
this.notifyTitle = R.string.label_status_disconnected;
|
||||
push();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionRestored() {
|
||||
this.notifyTitle = this.lastTitle;
|
||||
push();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShareListReceived(String linkFormat, String[] shareIDs) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,6 @@ package info.varden.hauk.service;
|
|||
* @author Marius Lindvall
|
||||
*/
|
||||
public interface GNSSActiveHandler {
|
||||
/**
|
||||
* Called when the fine location provider times out and the coarse location provider is rebound.
|
||||
*/
|
||||
void onCoarseRebound();
|
||||
|
||||
/**
|
||||
* Called when the initial low-accuracy GNSS fix has been obtained.
|
||||
*/
|
||||
|
|
@ -21,16 +16,6 @@ public interface GNSSActiveHandler {
|
|||
*/
|
||||
void onAccurateLocationReceived();
|
||||
|
||||
/**
|
||||
* Called if the backend server is unreachable.
|
||||
*/
|
||||
void onServerConnectionLost();
|
||||
|
||||
/**
|
||||
* Called if the backend server was unreachable, but is now reachable again.
|
||||
*/
|
||||
void onServerConnectionRestored();
|
||||
|
||||
/**
|
||||
* Called when a list of shares the client is contributing to has been received from the server.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package info.varden.hauk.service;
|
||||
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
|
@ -14,7 +13,7 @@ import info.varden.hauk.utils.Log;
|
|||
*/
|
||||
abstract class LocationListenerBase implements LocationListener {
|
||||
@Override
|
||||
public final void onStatusChanged(String provider, int status, Bundle bundle) {
|
||||
public final void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
Log.v("Location status changed for provider %s, status=%s", provider, status); //NON-NLS
|
||||
}
|
||||
|
||||
|
|
@ -27,13 +26,4 @@ abstract class LocationListenerBase implements LocationListener {
|
|||
public final void onProviderDisabled(String provider) {
|
||||
Log.w("Location provider %s was disabled", provider); //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
* Request location updates from the given location manager.
|
||||
*
|
||||
* @param manager The location manager to request location updates from.
|
||||
* @return true if successful, false otherwise.
|
||||
* @throws SecurityException if location permission has not been granted.
|
||||
*/
|
||||
abstract boolean request(LocationManager manager) throws SecurityException;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,24 +6,20 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.http.LocationUpdatePacket;
|
||||
import info.varden.hauk.http.ServerException;
|
||||
import info.varden.hauk.http.parameter.LocationProvider;
|
||||
import info.varden.hauk.manager.StopSharingTask;
|
||||
import info.varden.hauk.notify.HaukNotification;
|
||||
import info.varden.hauk.notify.SharingNotification;
|
||||
import info.varden.hauk.struct.Share;
|
||||
import info.varden.hauk.struct.Version;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
import info.varden.hauk.utils.Log;
|
||||
import info.varden.hauk.utils.ReceiverDataRegistry;
|
||||
import info.varden.hauk.utils.TimeUtils;
|
||||
|
||||
/**
|
||||
* This class is a location listener that POSTs all location updates to Hauk as it receives them. It
|
||||
|
|
@ -72,25 +68,12 @@ public final class LocationPushService extends Service {
|
|||
/**
|
||||
* The service's location listener for fine (GNSS, high-accuracy) location updates.
|
||||
*/
|
||||
private FineLocationListener listenFine;
|
||||
private LocationListener listenFine;
|
||||
|
||||
/**
|
||||
* The service's location listener for coarse (network, low-accuracy) location updates.
|
||||
*/
|
||||
private CoarseLocationListener listenCoarse;
|
||||
|
||||
/**
|
||||
* The handler that has scheduled the stop task. This is needed so that the callback can be
|
||||
* cancelled if the service is relaunched because of a {@link info.varden.hauk.ui.MainActivity}
|
||||
* reset/recreation.
|
||||
*/
|
||||
private Handler handler;
|
||||
|
||||
/**
|
||||
* Whether or not the last update packet was sent successfully, i.e. whether there is a
|
||||
* connection to the backend server.
|
||||
*/
|
||||
private boolean connected = true;
|
||||
private LocationListener listenCoarse;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
|
@ -100,15 +83,12 @@ public final class LocationPushService extends Service {
|
|||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.i("Location push service %s was started, flags=%s, startId=%s", this, flags, startId); //NON-NLS
|
||||
Log.i("Location push service was started, flags=%s, startId=%s", flags, startId); //NON-NLS
|
||||
|
||||
// A task that should be run when sharing ends, either automatically or by user request.
|
||||
StopSharingTask stopTask = (StopSharingTask) ReceiverDataRegistry.retrieve(intent.getIntExtra(Constants.EXTRA_STOP_TASK, -1));
|
||||
this.share = (Share) ReceiverDataRegistry.retrieve(intent.getIntExtra(Constants.EXTRA_SHARE, -1));
|
||||
GNSSActiveHandler parentHandler = (GNSSActiveHandler) ReceiverDataRegistry.retrieve(intent.getIntExtra(Constants.EXTRA_GNSS_ACTIVE_TASK, -1));
|
||||
this.handler = (Handler) ReceiverDataRegistry.retrieve(intent.getIntExtra(Constants.EXTRA_HANDLER, -1));
|
||||
|
||||
Log.d("Pusher %s was given extras stopTask=%s, share=%s, parentHandler=%s, handler=%s", this, stopTask, this.share, parentHandler, this.handler); //NON-NLS
|
||||
this.gnssActiveTask = (GNSSActiveHandler) ReceiverDataRegistry.retrieve(intent.getIntExtra(Constants.EXTRA_GNSS_ACTIVE_TASK, -1));
|
||||
|
||||
try {
|
||||
// Even though we previously requested location permission, we still have to check for
|
||||
|
|
@ -121,18 +101,45 @@ public final class LocationPushService extends Service {
|
|||
// buttons that let the user interact with Hauk while in the background, but the
|
||||
// real reason we need a notification is so that Android does not kill our app while
|
||||
// it is in the background. Having an active notification stops this from happening.
|
||||
SharingNotification notify = new SharingNotification(this, this.share, stopTask);
|
||||
HaukNotification notify = new SharingNotification(this, this.share, stopTask);
|
||||
startForeground(notify.getID(), notify.create());
|
||||
|
||||
// Send status changes both to the parent handler and the notification.
|
||||
this.gnssActiveTask = new MultiTargetGNSSHandlerProxy(parentHandler, notify);
|
||||
this.listenCoarse = new LocationListenerBase() {
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if (!LocationPushService.this.hasRunCoarseTask) {
|
||||
// Notify the main activity that coarse GPS data is now being received,
|
||||
// such that the UI can be updated.
|
||||
LocationPushService.this.hasRunCoarseTask = true;
|
||||
LocationPushService.this.gnssActiveTask.onCoarseLocationReceived();
|
||||
}
|
||||
Log.v("Location was received on coarse location provider"); //NON-NLS
|
||||
LocationPushService.this.onLocationChanged(location);
|
||||
}
|
||||
};
|
||||
|
||||
// Create and bind location listeners.
|
||||
this.listenCoarse = new CoarseLocationListener();
|
||||
this.listenFine = new FineLocationListener();
|
||||
if (!this.listenCoarse.request(this.locMan)) this.listenCoarse = null;
|
||||
if (!this.listenFine.request(this.locMan)) this.listenFine = null;
|
||||
this.listenFine = new LocationListenerBase() {
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if (LocationPushService.this.listenCoarse != null) {
|
||||
// Unregister the coarse location listener, since we are now receiving
|
||||
// accurate location data.
|
||||
Log.i("Accurate location found; removing updates from coarse location provider"); //NON-NLS
|
||||
LocationPushService.this.locMan.removeUpdates(LocationPushService.this.listenCoarse);
|
||||
LocationPushService.this.listenCoarse = null;
|
||||
}
|
||||
if (!LocationPushService.this.hasRunAccurateTask) {
|
||||
// Notify the main activity that accurate GPS data is now being
|
||||
// received, such that the UI can be updated.
|
||||
LocationPushService.this.hasRunAccurateTask = true;
|
||||
LocationPushService.this.gnssActiveTask.onAccurateLocationReceived();
|
||||
}
|
||||
Log.v("Location was received on fine location provider"); //NON-NLS
|
||||
LocationPushService.this.onLocationChanged(location);
|
||||
}
|
||||
};
|
||||
|
||||
attachToLocationServices();
|
||||
} else {
|
||||
Log.e("Location permission that was granted earlier has been rejected - sharing aborted"); //NON-NLS
|
||||
}
|
||||
|
|
@ -145,32 +152,52 @@ public final class LocationPushService extends Service {
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
if (this.listenCoarse != null) {
|
||||
Log.i("Service %s destroyed; removing updates from coarse location provider", this); //NON-NLS
|
||||
Log.i("Service destroyed; removing updates from coarse location provider"); //NON-NLS
|
||||
this.locMan.removeUpdates(this.listenCoarse);
|
||||
}
|
||||
Log.i("Service %s destroyed; removing updates from fine location provider", this); //NON-NLS
|
||||
this.listenFine.onStopped();
|
||||
Log.i("Service destroyed; removing updates from fine location provider"); //NON-NLS
|
||||
this.locMan.removeUpdates(this.listenFine);
|
||||
|
||||
Log.i("Removing callbacks from handler %s", this.handler); //NON-NLS
|
||||
this.handler.removeCallbacksAndMessages(null);
|
||||
this.gnssActiveTask = new MultiTargetGNSSHandlerProxy();
|
||||
|
||||
Log.i("Stopping foreground service"); //NON-NLS
|
||||
stopForeground(true);
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the listeners to the location manager to request updates.
|
||||
*
|
||||
* @throws SecurityException If location permission is missing.
|
||||
*/
|
||||
private void attachToLocationServices() throws SecurityException {
|
||||
Log.i("Requesting location updates from device location services"); //NON-NLS
|
||||
try {
|
||||
this.locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, this.share.getSession().getIntervalMillis(), 0.0F, this.listenCoarse);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Log.w("Coarse location provider does not exist!", ex); //NON-NLS
|
||||
this.listenCoarse = null;
|
||||
}
|
||||
this.locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, this.share.getSession().getIntervalMillis(), 0.0F, this.listenFine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when either the coarse or the fine location provider has received a location update.
|
||||
* Pushes the location update to the session backend.
|
||||
*
|
||||
* @param location The location received from the device's location services.
|
||||
*/
|
||||
private void onLocationChanged(Location location, LocationProvider accuracy) {
|
||||
private void onLocationChanged(Location location) {
|
||||
Log.v("Sending location update packet"); //NON-NLS
|
||||
new LocationUpdatePacketImpl(location, accuracy).send();
|
||||
new LocationUpdatePacket(this, this.share.getSession(), location) {
|
||||
@Override
|
||||
public void onShareListReceived(String linkFormat, String[] shares) {
|
||||
Log.v("Received list of shares from server"); //NON-NLS
|
||||
LocationPushService.this.gnssActiveTask.onShareListReceived(linkFormat, shares);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailure(Exception ex) {
|
||||
// Errors can be due to intermittent connectivity. Ignore them.
|
||||
Log.w("Failed to push location update to server", ex); //NON-NLS
|
||||
}
|
||||
}.send();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
@ -178,159 +205,4 @@ public final class LocationPushService extends Service {
|
|||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coarse location provider implementation (network-based location).
|
||||
*/
|
||||
private final class CoarseLocationListener extends LocationListenerBase {
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if (!LocationPushService.this.hasRunCoarseTask) {
|
||||
// Notify the main activity that coarse GPS data is now being received,
|
||||
// such that the UI can be updated.
|
||||
LocationPushService.this.hasRunCoarseTask = true;
|
||||
LocationPushService.this.gnssActiveTask.onCoarseLocationReceived();
|
||||
}
|
||||
Log.v("Location was received on coarse location provider"); //NON-NLS
|
||||
LocationPushService.this.onLocationChanged(location, LocationProvider.COARSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean request(LocationManager manager) throws SecurityException {
|
||||
Log.i("Requesting location updates from device location services"); //NON-NLS
|
||||
try {
|
||||
manager.requestLocationUpdates(
|
||||
LocationManager.NETWORK_PROVIDER,
|
||||
LocationPushService.this.share.getSession().getIntervalMillis(),
|
||||
LocationPushService.this.share.getSession().getMinimumDistance(),
|
||||
this
|
||||
);
|
||||
return true;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Log.w("Coarse location provider does not exist!", ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fine location provider implementation (GNSS-based location).
|
||||
*/
|
||||
private final class FineLocationListener extends LocationListenerBase {
|
||||
private final Handler noGnssTimer;
|
||||
private final PreferenceManager prefs;
|
||||
private Location locationOfLastUpdate;
|
||||
private float minDistance;
|
||||
|
||||
private FineLocationListener() {
|
||||
this.noGnssTimer = new Handler();
|
||||
this.prefs = new PreferenceManager(LocationPushService.this);
|
||||
this.locationOfLastUpdate = null;
|
||||
this.minDistance = LocationPushService.this.share.getSession().getMinimumDistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if (LocationPushService.this.listenCoarse != null) {
|
||||
// Unregister the coarse location listener, since we are now receiving
|
||||
// accurate location data.
|
||||
Log.i("Accurate location found; removing updates from coarse location provider"); //NON-NLS
|
||||
LocationPushService.this.locMan.removeUpdates(LocationPushService.this.listenCoarse);
|
||||
LocationPushService.this.listenCoarse = null;
|
||||
}
|
||||
if (!LocationPushService.this.hasRunAccurateTask) {
|
||||
// Notify the main activity that accurate GPS data is now being
|
||||
// received, such that the UI can be updated.
|
||||
LocationPushService.this.hasRunAccurateTask = true;
|
||||
LocationPushService.this.gnssActiveTask.onAccurateLocationReceived();
|
||||
}
|
||||
Log.v("Location was received on fine location provider"); //NON-NLS
|
||||
|
||||
// Set a timeout for the location updates to detect if the provider stops working. If
|
||||
// that happens, fall back to the coarse location provider.
|
||||
this.noGnssTimer.removeCallbacksAndMessages(null);
|
||||
this.noGnssTimer.postDelayed(new CoarseLocationFallbackTask(), LocationPushService.this.share.getSession().getIntervalMillis() + this.prefs.get(Constants.PREF_NO_GNSS_FALLBACK) * TimeUtils.MILLIS_PER_SECOND);
|
||||
|
||||
// Only update the location if it is more than the minimum distance specified in
|
||||
// settings. Done manually rather than delegating to
|
||||
// LocationManager.requestLocationUpdates; see issue #124
|
||||
float distance = this.locationOfLastUpdate == null ? -1 : this.locationOfLastUpdate.distanceTo(location);
|
||||
if (this.locationOfLastUpdate == null || distance >= this.minDistance) {
|
||||
Log.v("Received distance %s, more than minimum distance %s", distance, this.minDistance); //NON-NLS
|
||||
this.locationOfLastUpdate = location;
|
||||
LocationPushService.this.onLocationChanged(location, LocationProvider.FINE);
|
||||
} else {
|
||||
Log.v("Received distance %s, less than minimum distance %s", distance, this.minDistance); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the session is stopped and updates removed from this listener. This
|
||||
* prevents the timeout from activating after the session has been stopped.
|
||||
*/
|
||||
private void onStopped() {
|
||||
this.noGnssTimer.removeCallbacksAndMessages(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean request(LocationManager manager) throws SecurityException {
|
||||
manager.requestLocationUpdates(
|
||||
LocationManager.GPS_PROVIDER,
|
||||
LocationPushService.this.share.getSession().getIntervalMillis(),
|
||||
0.0F, // See https://github.com/bilde2910/Hauk/issues/124
|
||||
this
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
private final class CoarseLocationFallbackTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
// No location updates have been received for the timeout period. Rebind the coarse
|
||||
// location listener while we wait for the fine listener to become functional again.
|
||||
Log.w("Location fix lost. Rebinding coarse location provider."); //NON-NLS
|
||||
LocationPushService.this.gnssActiveTask.onCoarseRebound();
|
||||
LocationPushService.this.hasRunCoarseTask = false;
|
||||
LocationPushService.this.hasRunAccurateTask = false;
|
||||
LocationPushService.this.listenCoarse = new CoarseLocationListener();
|
||||
if (!LocationPushService.this.listenCoarse.request(LocationPushService.this.locMan)) {
|
||||
LocationPushService.this.listenCoarse = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LocationUpdatePacketImpl extends LocationUpdatePacket {
|
||||
private LocationUpdatePacketImpl(Location location, LocationProvider accuracy) {
|
||||
super(LocationPushService.this, LocationPushService.this.share.getSession(), location, accuracy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShareListReceived(String linkFormat, String[] shares) {
|
||||
Log.v("Received list of shares from server"); //NON-NLS
|
||||
LocationPushService.this.gnssActiveTask.onShareListReceived(linkFormat, shares);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSuccess(String[] data, Version backendVersion) throws ServerException {
|
||||
// Check if connection was lost previously, and notify upstream if that's the case.
|
||||
if (!LocationPushService.this.connected) {
|
||||
LocationPushService.this.connected = true;
|
||||
Log.i("Connection to the backend was restored."); //NON-NLS
|
||||
LocationPushService.this.gnssActiveTask.onServerConnectionRestored();
|
||||
}
|
||||
super.onSuccess(data, backendVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailure(Exception ex) {
|
||||
Log.w("Failed to push location update to server", ex); //NON-NLS
|
||||
// Notify upstream about connectivity loss.
|
||||
if (LocationPushService.this.connected) {
|
||||
LocationPushService.this.connected = false;
|
||||
Log.i("Connection to the backend was lost."); //NON-NLS
|
||||
LocationPushService.this.gnssActiveTask.onServerConnectionLost();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
package info.varden.hauk.service;
|
||||
|
||||
/**
|
||||
* Proxy class that forwards GNSS activity events to multiple upstream {@link GNSSActiveHandler}s.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
final class MultiTargetGNSSHandlerProxy implements GNSSActiveHandler {
|
||||
private final GNSSActiveHandler[] upstream;
|
||||
|
||||
MultiTargetGNSSHandlerProxy(GNSSActiveHandler... upstream) {
|
||||
this.upstream = upstream.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoarseRebound() {
|
||||
for (GNSSActiveHandler up : this.upstream) up.onCoarseRebound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoarseLocationReceived() {
|
||||
for (GNSSActiveHandler up : this.upstream) up.onCoarseLocationReceived();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccurateLocationReceived() {
|
||||
for (GNSSActiveHandler up : this.upstream) up.onAccurateLocationReceived();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionLost() {
|
||||
for (GNSSActiveHandler up : this.upstream) up.onServerConnectionLost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionRestored() {
|
||||
for (GNSSActiveHandler up : this.upstream) up.onServerConnectionRestored();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShareListReceived(String linkFormat, String[] shareIDs) {
|
||||
for (GNSSActiveHandler up : this.upstream) up.onShareListReceived(linkFormat, shareIDs);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ public final class KeyDerivable implements Serializable {
|
|||
/**
|
||||
* End-to-end password to encrypt outgoing data with.
|
||||
*/
|
||||
@SuppressWarnings("FieldNotUsedInToString")
|
||||
private final String password;
|
||||
|
||||
/**
|
||||
|
|
@ -63,7 +62,7 @@ public final class KeyDerivable implements Serializable {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeyDerivable{password=<hidden>"
|
||||
return "KeyDerivable{password=" + this.password
|
||||
+ ",salt=0x" + StringUtils.bytesToHex(this.salt)
|
||||
+ "}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import java.util.Date;
|
|||
import java.util.Locale;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.http.ConnectionParameters;
|
||||
import info.varden.hauk.utils.TimeUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -17,18 +16,13 @@ import info.varden.hauk.utils.TimeUtils;
|
|||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class Session implements Serializable {
|
||||
private static final long serialVersionUID = 315568255735934584L;
|
||||
private static final long serialVersionUID = 8424014563201300999L;
|
||||
|
||||
/**
|
||||
* The Hauk backend server base URL.
|
||||
*/
|
||||
private final String serverURL;
|
||||
|
||||
/**
|
||||
* Connection parameters for the backend connection.
|
||||
*/
|
||||
private final ConnectionParameters connParams;
|
||||
|
||||
/**
|
||||
* The version the backend is running.
|
||||
*/
|
||||
|
|
@ -51,32 +45,24 @@ public final class Session implements Serializable {
|
|||
*/
|
||||
private final int interval;
|
||||
|
||||
/**
|
||||
* The minimum distance between each location update, in meters.
|
||||
*/
|
||||
private final float minDistance;
|
||||
|
||||
/**
|
||||
* End-to-end encryption parameters.
|
||||
*/
|
||||
@Nullable
|
||||
private final KeyDerivable e2eParams;
|
||||
|
||||
public Session(String serverURL, ConnectionParameters connParams, Version backendVersion, String sessionID, long expiry, int interval, float minDistance, @Nullable KeyDerivable e2eParams) {
|
||||
public Session(String serverURL, Version backendVersion, String sessionID, long expiry, int interval, @Nullable KeyDerivable e2eParams) {
|
||||
this.serverURL = serverURL;
|
||||
this.backendVersion = backendVersion;
|
||||
this.sessionID = sessionID;
|
||||
this.expiry = expiry;
|
||||
this.interval = interval;
|
||||
this.minDistance = minDistance;
|
||||
this.e2eParams = e2eParams;
|
||||
this.connParams = connParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Session{serverURL=" + this.serverURL
|
||||
+ ",connParams=" + this.connParams
|
||||
+ ",backendVersion=" + this.backendVersion
|
||||
+ ",sessionID=" + this.sessionID
|
||||
+ ",expiry=" + this.expiry
|
||||
|
|
@ -89,10 +75,6 @@ public final class Session implements Serializable {
|
|||
return this.serverURL;
|
||||
}
|
||||
|
||||
public ConnectionParameters getConnectionParameters() {
|
||||
return this.connParams;
|
||||
}
|
||||
|
||||
public Version getBackendVersion() {
|
||||
return this.backendVersion;
|
||||
}
|
||||
|
|
@ -118,7 +100,7 @@ public final class Session implements Serializable {
|
|||
* Returns the expiration time of this session as a human-readable string.
|
||||
*/
|
||||
public String getExpiryString() {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(Constants.DATE_FORMAT_UI, Locale.getDefault());
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(Constants.DATE_FORMAT, Locale.getDefault());
|
||||
return formatter.format(getExpiryDate());
|
||||
}
|
||||
|
||||
|
|
@ -158,13 +140,6 @@ public final class Session implements Serializable {
|
|||
return getIntervalSeconds() * TimeUtils.MILLIS_PER_SECOND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum distance between each location update, in meters.
|
||||
*/
|
||||
public float getMinimumDistance() {
|
||||
return this.minDistance;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyDerivable getDerivableE2EKey() {
|
||||
return this.e2eParams;
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
package info.varden.hauk.system.launcher;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
/**
|
||||
* Listener that opens a URI in the browser on click.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class OpenLinkListener implements Preference.OnPreferenceClickListener, View.OnClickListener {
|
||||
private final Context ctx;
|
||||
private final Uri uri;
|
||||
|
||||
/**
|
||||
* Creates the click listener.
|
||||
*
|
||||
* @param ctx Android application context.
|
||||
* @param uriResource A string resource representing the link to open.
|
||||
*/
|
||||
public OpenLinkListener(Context ctx, int uriResource) {
|
||||
this(ctx, Uri.parse(ctx.getString(uriResource)));
|
||||
}
|
||||
|
||||
private OpenLinkListener(Context ctx, Uri uri) {
|
||||
this.ctx = ctx;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
this.ctx.startActivity(new Intent(Intent.ACTION_VIEW, this.uri));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
this.ctx.startActivity(new Intent(Intent.ACTION_VIEW, this.uri));
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ public enum Device {
|
|||
XIAOMI(3,
|
||||
R.string.manufacturer_xiaomi,
|
||||
Build.HOST,
|
||||
Pattern.compile("(-miui-)|(xiaomi)"),
|
||||
Pattern.compile("-miui-"),
|
||||
new ComponentLauncher(
|
||||
"com.miui.powerkeeper",
|
||||
".ui.HiddenAppsContainerManagementActivity"
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public final class DeviceChecker {
|
|||
dialogSvc.showDialog(
|
||||
R.string.battery_savings_title,
|
||||
String.format(this.ctx.getString(R.string.battery_savings_body), this.ctx.getString(device.getManufacturerStringResource())),
|
||||
Buttons.Two.SETTINGS_DISMISS,
|
||||
Buttons.SETTINGS_DISMISS,
|
||||
new WarningDialog(this.ctx, prefs, device)
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* A base class for enum-like values that can be stored in preferences. A class can extend this
|
||||
* class to allow it to be stored as an integer in preferences and be retrieved directly using
|
||||
* {@link PreferenceManager#get(Preference)}.
|
||||
*
|
||||
* @param <T> The type that extends this class.
|
||||
*/
|
||||
public abstract class IndexedEnum<T extends IndexedEnum<T>> implements Serializable {
|
||||
private static final long serialVersionUID = -1867612075461184507L;
|
||||
|
||||
/**
|
||||
* An internal ID for this enum member that represents the value it is stored as in preferences.
|
||||
*/
|
||||
private final int index;
|
||||
|
||||
protected IndexedEnum(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MethodOverloadsMethodOfSuperclass")
|
||||
public final boolean equals(IndexedEnum<T> other) {
|
||||
return this.getIndex() == other.getIndex();
|
||||
}
|
||||
|
||||
public final int getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an enum member by its index.
|
||||
*
|
||||
* @param index The index of the enum member.
|
||||
* @return An enum member.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
final T fromIndex(int index) throws IllegalAccessException, InstantiationException {
|
||||
return (T) fromIndex(getClass(), index);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends IndexedEnum<T>> T fromIndex(Class<T> type, int index) throws IllegalAccessException, InstantiationException {
|
||||
Field[] fields = type.getFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getType().isAssignableFrom(type)) {
|
||||
IndexedEnum<T> instance = (IndexedEnum<T>) field.get(null);
|
||||
if (instance != null && instance.getIndex() == index) {
|
||||
return (T) instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new InstantiationException("Failed to find a member with this index");
|
||||
}
|
||||
|
||||
@SuppressWarnings("DesignForExtension")
|
||||
@Override
|
||||
public String toString() {
|
||||
return "index=" + this.index;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
|
||||
/**
|
||||
* An exception that is thrown when attempting to read a setting that is not compatible with the
|
||||
* given type on the settings screen.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
class InvalidPreferenceTypeException extends RuntimeException {
|
||||
private static final long serialVersionUID = -1966346602996946755L;
|
||||
|
||||
InvalidPreferenceTypeException(Object value, Class<?> target) {
|
||||
super(String.format("Cannot direct-cast %s to %s", value.toString(), target.getName())); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
|
||||
/**
|
||||
* An exception that is thrown if assignment ot a value to a preference fails due to a downstream
|
||||
* exception.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
class PreferenceAssignmentException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8233494694851033874L;
|
||||
|
||||
PreferenceAssignmentException(Exception parent) {
|
||||
super(parent);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.PreferenceDataStore;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Preference interceptor data store that redirects preference storage requests to
|
||||
* {@link PreferenceManager} and {@link Preference} for proper validation and storage. This allows
|
||||
* preferences to be encrypted, for example.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class PreferenceHandler extends PreferenceDataStore {
|
||||
/**
|
||||
* Mapping between all preference keys and {@link Preference}s.
|
||||
*/
|
||||
private static final Map<String, Preference> map;
|
||||
|
||||
static {
|
||||
// Initialize the preference map.
|
||||
map = new HashMap<>();
|
||||
|
||||
// Find all Preferences declared in the Constants class and add them to the map.
|
||||
Field[] fields = Constants.class.getFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getType().isAssignableFrom(Preference.class)) {
|
||||
try {
|
||||
Log.v("Found field %s of type Preference in Constants, adding to map", field.getName()); //NON-NLS
|
||||
Preference p = (Preference) field.get(null);
|
||||
map.put(p.getKey(), p);
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.wtf("Failed to read constant from Constants", e); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hauk preference manager.
|
||||
*/
|
||||
private final PreferenceManager manager;
|
||||
|
||||
public PreferenceHandler(Context ctx) {
|
||||
this.manager = new PreferenceManager(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defValue) {
|
||||
Log.v("Getting boolean key %s", key); //NON-NLS
|
||||
if (!map.containsKey(key)) throw new PreferenceNotFoundException(key);
|
||||
Object value = this.manager.get(map.get(key));
|
||||
if (value instanceof Boolean) {
|
||||
return (boolean) value;
|
||||
} else {
|
||||
throw new InvalidPreferenceTypeException(value, Boolean.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(String key, float defValue) {
|
||||
Log.v("Getting float key %s", key); //NON-NLS
|
||||
if (!map.containsKey(key)) throw new PreferenceNotFoundException(key);
|
||||
Object value = this.manager.get(map.get(key));
|
||||
if (value instanceof Float) {
|
||||
return (float) value;
|
||||
} else {
|
||||
throw new InvalidPreferenceTypeException(value, Float.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String key, int defValue) {
|
||||
Log.v("Getting int key %s", key); //NON-NLS
|
||||
if (!map.containsKey(key)) throw new PreferenceNotFoundException(key);
|
||||
Object value = this.manager.get(map.get(key));
|
||||
if (value instanceof Integer) {
|
||||
return (int) value;
|
||||
} else {
|
||||
throw new InvalidPreferenceTypeException(value, Integer.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String key, long defValue) {
|
||||
Log.v("Getting long key %s", key); //NON-NLS
|
||||
if (!map.containsKey(key)) throw new PreferenceNotFoundException(key);
|
||||
Object value = this.manager.get(map.get(key));
|
||||
if (value instanceof Long) {
|
||||
return (long) value;
|
||||
} else {
|
||||
throw new InvalidPreferenceTypeException(value, Long.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key, String defValue) {
|
||||
Log.v("Getting string key %s", key); //NON-NLS
|
||||
if (!map.containsKey(key)) throw new PreferenceNotFoundException(key);
|
||||
Object value = this.manager.get(map.get(key));
|
||||
if (value instanceof String) {
|
||||
return (String) value;
|
||||
} else if (value instanceof Integer || value instanceof Float || value instanceof Long) {
|
||||
// EditTextPreference calls getString() instead of getInt(), getFloat() and getLong()
|
||||
// because it is a text input field, despite the type of data it is set to store. This
|
||||
// must be handled properly.
|
||||
return String.valueOf(value);
|
||||
} else if (value instanceof IndexedEnum) {
|
||||
return String.valueOf(((IndexedEnum) value).getIndex());
|
||||
} else {
|
||||
throw new InvalidPreferenceTypeException(value, String.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBoolean(String key, boolean value) {
|
||||
this.manager.set(map.get(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putFloat(String key, float value) {
|
||||
this.manager.set(map.get(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putInt(String key, int value) {
|
||||
this.manager.set(map.get(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putLong(String key, long value) {
|
||||
this.manager.set(map.get(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putString(String key, String value) {
|
||||
// EditTextPreferences calls putString() instead of putInt(), putFloat() and putLong()
|
||||
// because it is a text input field, despite the type of data it is set to store. This
|
||||
// must be handled properly.
|
||||
Class<?> type = map.get(key).getPreferenceType();
|
||||
if (type == Integer.class) {
|
||||
putInt(key, Integer.valueOf(value));
|
||||
} else if (type == Float.class) {
|
||||
putFloat(key, Float.valueOf(value));
|
||||
} else if (type == Long.class) {
|
||||
putLong(key, Long.valueOf(value));
|
||||
} else if (type == IndexedEnum.class) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Preference<IndexedEnum> pref = (Preference<IndexedEnum>) map.get(key);
|
||||
try {
|
||||
this.manager.set(pref, pref.getDefault().fromIndex(Integer.valueOf(value)));
|
||||
} catch (Exception e) {
|
||||
throw new PreferenceAssignmentException(e);
|
||||
}
|
||||
} else {
|
||||
this.manager.set(map.get(key), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
|
||||
/**
|
||||
* Exception that is thrown when {@link info.varden.hauk.system.preferences.ui.SettingsActivity}
|
||||
* tries to read a setting that does not exist in Hauk.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
class PreferenceNotFoundException extends RuntimeException {
|
||||
private static final long serialVersionUID = 6201186189243885309L;
|
||||
|
||||
PreferenceNotFoundException(String key) {
|
||||
super(String.format("Preference %s was requested but does not exist in Hauk", key)); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.indexresolver;
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
/**
|
||||
* An enum preference that maps night mode styles for the app.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class NightModeStyle extends Resolver<NightModeStyle, Integer> {
|
||||
private static final long serialVersionUID = 1926796368584326815L;
|
||||
|
||||
public static final NightModeStyle FOLLOW_SYSTEM = new NightModeStyle(0, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
public static final NightModeStyle AUTO_BATTERY = new NightModeStyle(1, AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
|
||||
public static final NightModeStyle ALWAYS_DARK = new NightModeStyle(2, AppCompatDelegate.MODE_NIGHT_YES);
|
||||
public static final NightModeStyle NEVER_DARK = new NightModeStyle(3, AppCompatDelegate.MODE_NIGHT_NO);
|
||||
|
||||
private NightModeStyle(int index, Integer mapping) {
|
||||
super(index, mapping);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.indexresolver;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* An enum representing the various types of proxies available on the system, and their ID when
|
||||
* stored in preferences.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class ProxyTypeResolver extends Resolver<ProxyTypeResolver, Proxy.Type> {
|
||||
private static final long serialVersionUID = -2687503543989317320L;
|
||||
|
||||
public static final ProxyTypeResolver SYSTEM_DEFAULT = new ProxyTypeResolver(0, null);
|
||||
public static final ProxyTypeResolver DIRECT = new ProxyTypeResolver(1, Proxy.Type.DIRECT);
|
||||
public static final ProxyTypeResolver HTTP = new ProxyTypeResolver(2, Proxy.Type.HTTP);
|
||||
public static final ProxyTypeResolver SOCKS = new ProxyTypeResolver(3, Proxy.Type.SOCKS);
|
||||
|
||||
private ProxyTypeResolver(int index, Proxy.Type mapping) {
|
||||
super(index, mapping);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.indexresolver;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import info.varden.hauk.system.preferences.IndexedEnum;
|
||||
|
||||
/**
|
||||
* A class that provides mappings between an index and a specific type of object for easy
|
||||
* translation when reading preferences.
|
||||
*
|
||||
* @param <T1> The type of class implementing this class.
|
||||
* @param <T2> The class that each entry in the parent class should map to.
|
||||
*/
|
||||
public abstract class Resolver<T1 extends IndexedEnum<T1>, T2 extends Serializable> extends IndexedEnum<T1> {
|
||||
private static final long serialVersionUID = 1829235445367254385L;
|
||||
|
||||
private final T2 mapping;
|
||||
|
||||
protected Resolver(int index, T2 mapping) {
|
||||
super(index);
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
public final T2 resolve() {
|
||||
return this.mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return getClass().getSimpleName() + "{mapping=" + this.mapping + "," + super.toString() + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import info.varden.hauk.BuildConfig;
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.R;
|
||||
import info.varden.hauk.system.launcher.OpenLinkListener;
|
||||
import info.varden.hauk.system.preferences.PreferenceHandler;
|
||||
import info.varden.hauk.system.preferences.ui.listener.CascadeBindListener;
|
||||
import info.varden.hauk.system.preferences.ui.listener.CascadeChangeListener;
|
||||
import info.varden.hauk.system.preferences.ui.listener.FloatBoundChangeListener;
|
||||
import info.varden.hauk.system.preferences.ui.listener.HintBindListener;
|
||||
import info.varden.hauk.system.preferences.ui.listener.InputTypeBindListener;
|
||||
import info.varden.hauk.system.preferences.ui.listener.IntegerBoundChangeListener;
|
||||
import info.varden.hauk.system.preferences.ui.listener.NightModeChangeListener;
|
||||
import info.varden.hauk.system.preferences.ui.listener.ProxyPreferenceChangeListener;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Settings activity that allows the user to change app preferences.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class SettingsActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.settings_activity);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, new SettingsFragment())
|
||||
.commit();
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private Context ctx = null;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
PreferenceManager manager = getPreferenceManager();
|
||||
info.varden.hauk.system.preferences.PreferenceManager prefs = new info.varden.hauk.system.preferences.PreferenceManager(this.ctx);
|
||||
|
||||
// Intercept all reads and writes so that values are properly validated and encrypted if
|
||||
// required by Preference.
|
||||
manager.setPreferenceDataStore(new PreferenceHandler(this.ctx));
|
||||
|
||||
// Load the preferences layout.
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||
|
||||
// Set InputType and other attributes for text edit boxes.
|
||||
setTextEditParams(manager, Constants.PREF_SERVER_ENCRYPTED, new InputTypeBindListener(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI), new HintBindListener(R.string.pref_cryptServer_hint));
|
||||
setTextEditParams(manager, Constants.PREF_USERNAME_ENCRYPTED, new InputTypeBindListener(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME), new HintBindListener(R.string.pref_cryptUsername_hint));
|
||||
setTextEditParams(manager, Constants.PREF_PASSWORD_ENCRYPTED, new InputTypeBindListener(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD));
|
||||
setTextEditParams(manager, Constants.PREF_E2E_PASSWORD, new InputTypeBindListener(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD));
|
||||
setTextEditParams(manager, Constants.PREF_INTERVAL, new InputTypeBindListener(InputType.TYPE_CLASS_NUMBER));
|
||||
setTextEditParams(manager, Constants.PREF_UPDATE_DISTANCE, new InputTypeBindListener(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL));
|
||||
setTextEditParams(manager, Constants.PREF_CUSTOM_ID, new InputTypeBindListener(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE), new HintBindListener(R.string.pref_requestLink_hint));
|
||||
setTextEditParams(manager, Constants.PREF_PROXY_HOST, new InputTypeBindListener(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI));
|
||||
setTextEditParams(manager, Constants.PREF_PROXY_PORT, new InputTypeBindListener(InputType.TYPE_CLASS_NUMBER));
|
||||
setTextEditParams(manager, Constants.PREF_CONNECTION_TIMEOUT, new InputTypeBindListener(InputType.TYPE_CLASS_NUMBER));
|
||||
|
||||
// Set value bounds checks.
|
||||
setChangeListeners(manager, Constants.PREF_INTERVAL, new IntegerBoundChangeListener(1, Integer.MAX_VALUE));
|
||||
setChangeListeners(manager, Constants.PREF_UPDATE_DISTANCE, new FloatBoundChangeListener(0.0F, Float.MAX_VALUE));
|
||||
setChangeListeners(manager, Constants.PREF_PROXY_PORT, new IntegerBoundChangeListener(Constants.PORT_MIN, Constants.PORT_MAX));
|
||||
setChangeListeners(manager, Constants.PREF_CONNECTION_TIMEOUT, new IntegerBoundChangeListener(1, Integer.MAX_VALUE));
|
||||
|
||||
// Set proxy settings disabled if proxy is set to default or none.
|
||||
setChangeListeners(manager, Constants.PREF_PROXY_TYPE, new ProxyPreferenceChangeListener(new Preference[]{
|
||||
manager.findPreference(Constants.PREF_PROXY_HOST.getKey()),
|
||||
manager.findPreference(Constants.PREF_PROXY_PORT.getKey())
|
||||
}));
|
||||
Preference proxyTypePref = manager.findPreference(Constants.PREF_PROXY_TYPE.getKey());
|
||||
if (proxyTypePref != null) proxyTypePref.callChangeListener(String.valueOf(prefs.get(Constants.PREF_PROXY_TYPE).getIndex()));
|
||||
|
||||
// Update night mode when its preference is changed.
|
||||
setChangeListeners(manager, Constants.PREF_NIGHT_MODE, new NightModeChangeListener());
|
||||
|
||||
manager.findPreference("dummy_version").setSummary(BuildConfig.VERSION_NAME);
|
||||
manager.findPreference("dummy_sourceCode").setOnPreferenceClickListener(new OpenLinkListener(this.ctx, R.string.label_source_link));
|
||||
manager.findPreference("dummy_reportIssue").setOnPreferenceClickListener(new OpenLinkListener(this.ctx, R.string.link_issue_tracker));
|
||||
}
|
||||
|
||||
private static void setTextEditParams(PreferenceManager manager, info.varden.hauk.system.preferences.Preference<?> preference, EditTextPreference.OnBindEditTextListener... listeners) {
|
||||
EditTextPreference pref = manager.findPreference(preference.getKey());
|
||||
if (pref != null) {
|
||||
pref.setOnBindEditTextListener(new CascadeBindListener(listeners));
|
||||
} else {
|
||||
Log.wtf("Could not find setting for preference %s setting OnBindEditTextListener", preference); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
private static void setChangeListeners(PreferenceManager manager, info.varden.hauk.system.preferences.Preference<?> preference, Preference.OnPreferenceChangeListener... listeners) {
|
||||
Preference pref = manager.findPreference(preference.getKey());
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceChangeListener(new CascadeChangeListener(listeners));
|
||||
} else {
|
||||
Log.wtf("Could not find setting for preference %s when setting OnPreferenceChangeListener", preference); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context ctx) {
|
||||
super.onAttach(ctx);
|
||||
this.ctx = ctx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.EditTextPreference;
|
||||
|
||||
/**
|
||||
* Edit text bind listener that cascades the bind event to several
|
||||
* {@link androidx.preference.EditTextPreference.OnBindEditTextListener}s.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class CascadeBindListener implements EditTextPreference.OnBindEditTextListener {
|
||||
private final EditTextPreference.OnBindEditTextListener[] listeners;
|
||||
|
||||
public CascadeBindListener(EditTextPreference.OnBindEditTextListener[] listeners) {
|
||||
this.listeners = listeners.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindEditText(@NonNull EditText editText) {
|
||||
for (EditTextPreference.OnBindEditTextListener listener : this.listeners) {
|
||||
listener.onBindEditText(editText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
/**
|
||||
* Preference change listener that cascades the change event to several
|
||||
* {@link androidx.preference.Preference.OnPreferenceChangeListener}s.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class CascadeChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
private final Preference.OnPreferenceChangeListener[] listeners;
|
||||
|
||||
public CascadeChangeListener(Preference.OnPreferenceChangeListener[] listeners) {
|
||||
this.listeners = listeners.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
for (Preference.OnPreferenceChangeListener listener : this.listeners) {
|
||||
if (!listener.onPreferenceChange(preference, newValue)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Bounds checking preference change listener that ensures the given value is between two floating
|
||||
* point values (inclusive).
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class FloatBoundChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
private final float min;
|
||||
private final float max;
|
||||
|
||||
public FloatBoundChangeListener(float min, float max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
try {
|
||||
float value = Float.parseFloat((String) newValue);
|
||||
return value >= this.min && value <= this.max;
|
||||
} catch (NumberFormatException ex) {
|
||||
Log.e("Number %s is not a valid float", ex, newValue); //NON-NLS
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.EditTextPreference;
|
||||
|
||||
/**
|
||||
* Edit text bind listener that sets the hint of an {@link EditTextPreference}.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class HintBindListener implements EditTextPreference.OnBindEditTextListener {
|
||||
private final int hintResource;
|
||||
|
||||
public HintBindListener(int hintResource) {
|
||||
this.hintResource = hintResource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindEditText(@NonNull EditText editText) {
|
||||
editText.setHint(this.hintResource);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.EditTextPreference;
|
||||
|
||||
/**
|
||||
* Edit text bind listener that sets the input type of an {@link EditTextPreference}.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class InputTypeBindListener implements EditTextPreference.OnBindEditTextListener {
|
||||
private final int inputType;
|
||||
|
||||
public InputTypeBindListener(int inputType) {
|
||||
this.inputType = inputType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindEditText(@NonNull EditText editText) {
|
||||
editText.setInputType(this.inputType);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Bounds checking preference change listener that ensures the given value is between two integer
|
||||
* values (inclusive).
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class IntegerBoundChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
private final int min;
|
||||
private final int max;
|
||||
|
||||
public IntegerBoundChangeListener(int min, int max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
try {
|
||||
int value = Integer.parseInt((String) newValue);
|
||||
return value >= this.min && value <= this.max;
|
||||
} catch (NumberFormatException ex) {
|
||||
Log.e("Number %s is not a valid integer", ex, newValue); //NON-NLS
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import info.varden.hauk.system.preferences.IndexedEnum;
|
||||
import info.varden.hauk.system.preferences.indexresolver.NightModeStyle;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Value change listener for the night mode preference that sets the new night mode style on
|
||||
* selection.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class NightModeChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
try {
|
||||
// Resolve the night mode (an instance of NightModeStyle is required for this).
|
||||
int mode = IndexedEnum.fromIndex(NightModeStyle.class, Integer.valueOf((String) newValue)).resolve();
|
||||
Log.i("Setting night mode %s", mode); //NON-NLS
|
||||
AppCompatDelegate.setDefaultNightMode(mode);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e("Could not determine night mode style for value %s", e, newValue); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package info.varden.hauk.system.preferences.ui.listener;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import info.varden.hauk.system.preferences.indexresolver.ProxyTypeResolver;
|
||||
|
||||
/**
|
||||
* Value change listener for the proxy type selection preference that disables the other proxy
|
||||
* settings if a selection is made to the type that makes the other proxy settings unnecessary.
|
||||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class ProxyPreferenceChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
private final Preference[] prefsToDisable;
|
||||
|
||||
public ProxyPreferenceChangeListener(Preference[] prefsToDisable) {
|
||||
this.prefsToDisable = prefsToDisable.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
int choice = Integer.valueOf((String) newValue);
|
||||
boolean enable = choice != ProxyTypeResolver.SYSTEM_DEFAULT.getIndex() && choice != ProxyTypeResolver.DIRECT.getIndex();
|
||||
for (Preference pref : this.prefsToDisable) {
|
||||
pref.setEnabled(enable);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,9 +22,6 @@ final class GNSSStatusLabelUpdater implements GNSSStatusUpdateListener {
|
|||
*/
|
||||
private final TextView statusLabel;
|
||||
|
||||
private int lastStatus = R.string.label_status_none;
|
||||
private int lastColor = R.color.statusOff;
|
||||
|
||||
GNSSStatusLabelUpdater(Context ctx, TextView statusLabel) {
|
||||
this.ctx = ctx;
|
||||
this.statusLabel = statusLabel;
|
||||
|
|
@ -33,30 +30,15 @@ final class GNSSStatusLabelUpdater implements GNSSStatusUpdateListener {
|
|||
@Override
|
||||
public void onShutdown() {
|
||||
Log.d("Resetting GNSS status label"); //NON-NLS
|
||||
this.statusLabel.setText(R.string.label_status_none);
|
||||
this.statusLabel.setText(this.ctx.getString(R.string.label_status_none));
|
||||
this.statusLabel.setTextColor(this.ctx.getColor(R.color.statusOff));
|
||||
this.lastStatus = R.string.label_status_none;
|
||||
this.lastColor = R.color.statusOff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStarted() {
|
||||
Log.d("Set GNSS status label to initial state"); //NON-NLS
|
||||
this.statusLabel.setText(R.string.label_status_wait);
|
||||
this.statusLabel.setText(this.ctx.getString(R.string.label_status_wait));
|
||||
this.statusLabel.setTextColor(this.ctx.getColor(R.color.statusWait));
|
||||
this.lastStatus = R.string.label_status_wait;
|
||||
this.lastColor = R.color.statusWait;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGNSSConnectionLost() {
|
||||
// Indicate to the user that the GNSS connection was lost, and that we are now searching for
|
||||
// a location again.
|
||||
Log.i("GNSS location provider has stopped working; bound to coarse location provider"); //NON-NLS
|
||||
this.statusLabel.setText(R.string.label_status_lost_gnss);
|
||||
this.statusLabel.setTextColor(this.ctx.getColor(R.color.statusWait));
|
||||
this.lastStatus = R.string.label_status_lost_gnss;
|
||||
this.lastColor = R.color.statusWait;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -64,10 +46,7 @@ final class GNSSStatusLabelUpdater implements GNSSStatusUpdateListener {
|
|||
// Indicate to the user that GPS data is being received when the location pusher starts
|
||||
// receiving GPS data.
|
||||
Log.i("Initial coarse location was received, awaiting high accuracy fix"); //NON-NLS
|
||||
this.statusLabel.setText(R.string.label_status_coarse);
|
||||
this.statusLabel.setTextColor(this.ctx.getColor(R.color.statusWait));
|
||||
this.lastStatus = R.string.label_status_coarse;
|
||||
this.lastColor = R.color.statusWait;
|
||||
this.statusLabel.setText(this.ctx.getString(R.string.label_status_coarse));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -75,23 +54,7 @@ final class GNSSStatusLabelUpdater implements GNSSStatusUpdateListener {
|
|||
// Indicate to the user that GPS data is being received when the location pusher starts
|
||||
// receiving GPS data.
|
||||
Log.i("Initial high accuracy location was received, using GNSS location data for all future location updates"); //NON-NLS
|
||||
this.statusLabel.setText(R.string.label_status_ok);
|
||||
this.statusLabel.setText(this.ctx.getString(R.string.label_status_ok));
|
||||
this.statusLabel.setTextColor(this.ctx.getColor(R.color.statusOn));
|
||||
this.lastStatus = R.string.label_status_ok;
|
||||
this.lastColor = R.color.statusOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionLost() {
|
||||
// Indicate to the user that the backend connection was lost.
|
||||
this.statusLabel.setText(R.string.label_status_disconnected);
|
||||
this.statusLabel.setTextColor(this.ctx.getColor(R.color.statusDisconnected));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerConnectionRestored() {
|
||||
// Restore the previous status when connection to the backend is restored.
|
||||
this.statusLabel.setText(this.lastStatus);
|
||||
this.statusLabel.setTextColor(this.ctx.getColor(this.lastColor));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,20 +6,17 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
|
|
@ -28,27 +25,27 @@ import info.varden.hauk.caching.ResumePrompt;
|
|||
import info.varden.hauk.dialog.Buttons;
|
||||
import info.varden.hauk.dialog.CustomDialogBuilder;
|
||||
import info.varden.hauk.dialog.DialogService;
|
||||
import info.varden.hauk.dialog.StopSharingConfirmationPrompt;
|
||||
import info.varden.hauk.http.SessionInitiationPacket;
|
||||
import info.varden.hauk.manager.PromptCallback;
|
||||
import info.varden.hauk.manager.SessionInitiationReason;
|
||||
import info.varden.hauk.manager.SessionInitiationResponseHandler;
|
||||
import info.varden.hauk.manager.SessionListener;
|
||||
import info.varden.hauk.manager.SessionManager;
|
||||
import info.varden.hauk.manager.ShareListener;
|
||||
import info.varden.hauk.struct.AdoptabilityPreference;
|
||||
import info.varden.hauk.struct.Session;
|
||||
import info.varden.hauk.struct.Share;
|
||||
import info.varden.hauk.struct.ShareMode;
|
||||
import info.varden.hauk.struct.Version;
|
||||
import info.varden.hauk.system.launcher.OpenLinkListener;
|
||||
import info.varden.hauk.system.LocationPermissionsNotGrantedException;
|
||||
import info.varden.hauk.system.LocationServicesDisabledException;
|
||||
import info.varden.hauk.system.powersaving.DeviceChecker;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
import info.varden.hauk.system.preferences.ui.SettingsActivity;
|
||||
import info.varden.hauk.ui.listener.AddLinkClickListener;
|
||||
import info.varden.hauk.ui.listener.InitiateAdoptionClickListener;
|
||||
import info.varden.hauk.ui.listener.RememberPasswordPreferenceChangedListener;
|
||||
import info.varden.hauk.ui.listener.SelectionModeChangedListener;
|
||||
import info.varden.hauk.utils.DeprecationMigrator;
|
||||
import info.varden.hauk.utils.Log;
|
||||
import info.varden.hauk.utils.PreferenceManager;
|
||||
import info.varden.hauk.utils.TimeUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -105,13 +102,17 @@ public final class MainActivity extends AppCompatActivity {
|
|||
|
||||
Log.i("Creating main activity"); //NON-NLS
|
||||
setContentView(R.layout.activity_main);
|
||||
setSupportActionBar((Toolbar) findViewById(R.id.mainToolbar));
|
||||
|
||||
setClassVariables();
|
||||
((TextView) findViewById(R.id.labelAdoptWhatsThis)).setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);
|
||||
|
||||
Log.d("Attaching event handlers"); //NON-NLS
|
||||
|
||||
// Add an on checked handler to the password remember checkbox to save their encryption
|
||||
// password immediately.
|
||||
((CompoundButton) findViewById(R.id.chkRemember)).setOnCheckedChangeListener(
|
||||
new RememberPasswordPreferenceChangedListener(this, (EditText) findViewById(R.id.txtE2EPassword))
|
||||
);
|
||||
|
||||
// Add an event handler to the sharing mode selector.
|
||||
//noinspection OverlyStrongTypeCast
|
||||
((Spinner) findViewById(R.id.selMode)).setOnItemSelectedListener(new SelectionModeChangedListener(
|
||||
|
|
@ -121,7 +122,6 @@ public final class MainActivity extends AppCompatActivity {
|
|||
));
|
||||
|
||||
loadPreferences();
|
||||
|
||||
this.manager.resumeShares(new ResumePrompt() {
|
||||
@Override
|
||||
public void promptForResumption(Context ctx, Session session, Share[] shares, PromptCallback response) {
|
||||
|
|
@ -129,7 +129,7 @@ public final class MainActivity extends AppCompatActivity {
|
|||
new DialogService(ctx).showDialog(
|
||||
R.string.resume_title,
|
||||
String.format(ctx.getString(R.string.resume_body), shares.length, session.getExpiryString()),
|
||||
Buttons.Two.YES_NO,
|
||||
Buttons.YES_NO,
|
||||
new ResumeDialogBuilder(response)
|
||||
);
|
||||
}
|
||||
|
|
@ -139,50 +139,21 @@ public final class MainActivity extends AppCompatActivity {
|
|||
new DeviceChecker(this).performCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.title_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
this.uiStopTask.setActivityDestroyed();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Set app logo visibility.
|
||||
PreferenceManager prefs = new PreferenceManager(this);
|
||||
findViewById(R.id.imgLogo).setVisibility(prefs.get(Constants.PREF_HIDE_LOGO) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* On-tap handler for the "start sharing" and "stop sharing" button.
|
||||
*/
|
||||
public void startSharing(@SuppressWarnings("unused") View view) {
|
||||
PreferenceManager prefs = new PreferenceManager(this);
|
||||
|
||||
// If there is an executable stop task, that means that sharing is already active. Shut down
|
||||
// the share by running the stop task instead of starting a new share.
|
||||
if (this.manager.isSessionActive()) {
|
||||
Log.i("Sharing is being stopped from main activity"); //NON-NLS
|
||||
stopSharing(prefs);
|
||||
this.manager.stopSharing();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -190,74 +161,72 @@ public final class MainActivity extends AppCompatActivity {
|
|||
findViewById(R.id.btnShare).setEnabled(false);
|
||||
disableUI();
|
||||
|
||||
String server = prefs.get(Constants.PREF_SERVER_ENCRYPTED).trim();
|
||||
String username = prefs.get(Constants.PREF_USERNAME_ENCRYPTED).trim();
|
||||
String password = prefs.get(Constants.PREF_PASSWORD_ENCRYPTED);
|
||||
int duration;
|
||||
int interval = prefs.get(Constants.PREF_INTERVAL);
|
||||
float minDistance = prefs.get(Constants.PREF_UPDATE_DISTANCE);
|
||||
String customID = prefs.get(Constants.PREF_CUSTOM_ID).trim();
|
||||
boolean useE2E = prefs.get(Constants.PREF_ENABLE_E2E);
|
||||
String e2ePass = !useE2E ? "" : prefs.get(Constants.PREF_E2E_PASSWORD);
|
||||
String server = ((TextView) findViewById(R.id.txtServer)).getText().toString().trim();
|
||||
String username = ((TextView) findViewById(R.id.txtUsername)).getText().toString().trim();
|
||||
String password = ((TextView) findViewById(R.id.txtPassword)).getText().toString();
|
||||
int duration = Integer.parseInt(((TextView) findViewById(R.id.txtDuration)).getText().toString());
|
||||
int interval = Integer.parseInt(((TextView) findViewById(R.id.txtInterval)).getText().toString());
|
||||
String customID = ((TextView) findViewById(R.id.txtCustomID)).getText().toString().trim();
|
||||
String e2ePass = ((TextView) findViewById(R.id.txtE2EPassword)).getText().toString();
|
||||
String nickname = ((TextView) findViewById(R.id.txtNickname)).getText().toString().trim();
|
||||
@SuppressWarnings("OverlyStrongTypeCast") ShareMode mode = ShareMode.fromMode(((Spinner) findViewById(R.id.selMode)).getSelectedItemPosition());
|
||||
String groupPin = ((TextView) findViewById(R.id.txtGroupCode)).getText().toString();
|
||||
boolean allowAdoption = ((Checkable) findViewById(R.id.chkAllowAdopt)).isChecked();
|
||||
@SuppressWarnings("OverlyStrongTypeCast") int durUnit = ((Spinner) findViewById(R.id.selUnit)).getSelectedItemPosition();
|
||||
|
||||
assert mode != null;
|
||||
server = server.endsWith("/") ? server : server + "/";
|
||||
|
||||
// Save connection preferences for next launch, so the user doesn't have to enter URL etc.
|
||||
// every time.
|
||||
Log.i("Updating connection preferences"); //NON-NLS
|
||||
PreferenceManager prefs = new PreferenceManager(this);
|
||||
prefs.set(Constants.PREF_SERVER_ENCRYPTED, server);
|
||||
prefs.set(Constants.PREF_USERNAME_ENCRYPTED, username);
|
||||
prefs.set(Constants.PREF_PASSWORD_ENCRYPTED, password);
|
||||
prefs.set(Constants.PREF_DURATION, duration);
|
||||
prefs.set(Constants.PREF_INTERVAL, interval);
|
||||
prefs.set(Constants.PREF_CUSTOM_ID, customID);
|
||||
prefs.set(Constants.PREF_DURATION_UNIT, durUnit);
|
||||
prefs.set(Constants.PREF_NICKNAME, nickname);
|
||||
prefs.set(Constants.PREF_ALLOW_ADOPTION, allowAdoption);
|
||||
|
||||
// If password saving is enabled, save the password as well.
|
||||
if (((Checkable) findViewById(R.id.chkRemember)).isChecked()) {
|
||||
Log.i("Saving E2E password"); //NON-NLS
|
||||
prefs.set(Constants.PREF_REMEMBER_PASSWORD, true);
|
||||
prefs.set(Constants.PREF_E2E_PASSWORD, e2ePass);
|
||||
}
|
||||
|
||||
assert mode != null;
|
||||
server = server.endsWith("/") ? server : server + "/";
|
||||
|
||||
// The backend takes duration in seconds, so convert the minutes supplied by the user.
|
||||
duration = TimeUtils.timeUnitsToSeconds(duration, durUnit);
|
||||
|
||||
SessionInitiationPacket.InitParameters initParams = new SessionInitiationPacket.InitParameters(server, username, password, duration, interval, customID, e2ePass);
|
||||
SessionInitiationResponseHandler responseHandler = new SessionInitiationResponseHandlerImpl();
|
||||
|
||||
try {
|
||||
// Try to parse the duration.
|
||||
duration = Integer.parseInt(((TextView) findViewById(R.id.txtDuration)).getText().toString());
|
||||
prefs.set(Constants.PREF_DURATION, duration);
|
||||
switch (mode) {
|
||||
case CREATE_ALONE:
|
||||
this.manager.shareLocation(initParams, responseHandler, allowAdoption ? AdoptabilityPreference.ALLOW_ADOPTION : AdoptabilityPreference.DISALLOW_ADOPTION);
|
||||
break;
|
||||
|
||||
// The backend takes duration in seconds, hence it must be converted.
|
||||
duration = TimeUtils.timeUnitsToSeconds(duration, durUnit);
|
||||
} catch (NumberFormatException | ArithmeticException ex) {
|
||||
Log.e("Illegal duration value", ex); //NON-NLS
|
||||
this.dialogSvc.showDialog(R.string.err_client, R.string.err_invalid_duration, this.uiResetTask);
|
||||
return;
|
||||
}
|
||||
case CREATE_GROUP:
|
||||
this.manager.shareLocation(initParams, responseHandler, nickname);
|
||||
break;
|
||||
|
||||
if ((mode == ShareMode.CREATE_GROUP || mode == ShareMode.JOIN_GROUP) && nickname.isEmpty()) {
|
||||
Log.e("No nickname set!"); //NON-NLS
|
||||
this.dialogSvc.showDialog(R.string.err_client, R.string.err_no_nickname, this.uiResetTask);
|
||||
return;
|
||||
}
|
||||
case JOIN_GROUP:
|
||||
this.manager.shareLocation(initParams, responseHandler, nickname, groupPin);
|
||||
break;
|
||||
|
||||
if (server.isEmpty()) {
|
||||
// If the user hasn't set up a server yet, open the settings menu and prompt them to
|
||||
// configure the backend.
|
||||
this.uiResetTask.run();
|
||||
Toast.makeText(this, R.string.err_server_not_configured, Toast.LENGTH_LONG).show();
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return;
|
||||
}
|
||||
|
||||
SessionInitiationPacket.InitParameters initParams = new SessionInitiationPacket.InitParameters(server, username, password, duration, interval, minDistance, customID, e2ePass);
|
||||
new ProxyHostnameResolverImpl(this, this.manager, this.uiResetTask, prefs, new SessionInitiationResponseHandlerImpl(), initParams, mode, allowAdoption, nickname, groupPin).resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops sharing. If the setting to prompt for confirmation is enabled, a dialog box is shown to
|
||||
* confirm that the share should be stopped.
|
||||
*
|
||||
* @param prefs A preference manager.
|
||||
*/
|
||||
private void stopSharing(PreferenceManager prefs) {
|
||||
if (prefs.get(Constants.PREF_CONFIRM_STOP)) {
|
||||
this.dialogSvc.showDialog(R.string.dialog_confirm_stop_title, R.string.dialog_confirm_stop_body, Buttons.Three.YES_NO_REMEMBER, new StopSharingConfirmationPrompt(prefs, this.manager));
|
||||
} else {
|
||||
this.manager.stopSharing();
|
||||
default:
|
||||
Log.wtf("Unknown sharing mode. This is not supposed to happen, ever"); //NON-NLS
|
||||
break;
|
||||
}
|
||||
} catch (LocationServicesDisabledException e) {
|
||||
Log.e("Share initiation was stopped because location services are disabled", e); //NON-NLS
|
||||
this.dialogSvc.showDialog(R.string.err_client, R.string.err_location_disabled, this.uiResetTask);
|
||||
} catch (LocationPermissionsNotGrantedException e) {
|
||||
Log.w("Share initiation was stopped because the user has not granted location permissions yet", e); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -281,10 +250,14 @@ public final class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
/**
|
||||
* On-tap handler for the header logo and link that opens the Hauk project page on GitHub.
|
||||
* On-tap handler for the "show advanced settings" button.
|
||||
*/
|
||||
public void openProjectSite(View view) {
|
||||
new OpenLinkListener(this, R.string.label_source_link).onClick(view);
|
||||
public void showAdvancedSettings(View view) {
|
||||
view.setVisibility(View.GONE);
|
||||
findViewById(R.id.rowUpdateInterval).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.rowCustomID).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.rowE2EPassword).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.rowRemember).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -294,7 +267,13 @@ public final class MainActivity extends AppCompatActivity {
|
|||
private void setClassVariables() {
|
||||
Log.d("Setting class variables"); //NON-NLS
|
||||
this.lockWhileRunning = new View[] {
|
||||
findViewById(R.id.txtServer),
|
||||
findViewById(R.id.txtUsername),
|
||||
findViewById(R.id.txtPassword),
|
||||
findViewById(R.id.txtDuration),
|
||||
findViewById(R.id.txtInterval),
|
||||
findViewById(R.id.txtCustomID),
|
||||
findViewById(R.id.txtE2EPassword),
|
||||
|
||||
findViewById(R.id.selUnit),
|
||||
findViewById(R.id.selMode),
|
||||
|
|
@ -322,7 +301,7 @@ public final class MainActivity extends AppCompatActivity {
|
|||
this.manager.attachShareListener(new ShareListenerImpl());
|
||||
this.manager.attachSessionListener(new SessionListenerImpl());
|
||||
|
||||
this.linkList = new ShareLinkLayoutManager(this, this.manager, (ViewGroup) findViewById(R.id.tableLinks), (TextView) findViewById(R.id.headerLinks));
|
||||
this.linkList = new ShareLinkLayoutManager(this, this.manager, (ViewGroup) findViewById(R.id.tableLinks));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -331,18 +310,20 @@ public final class MainActivity extends AppCompatActivity {
|
|||
private void loadPreferences() {
|
||||
Log.i("Loading preferences..."); //NON-NLS
|
||||
PreferenceManager prefs = new PreferenceManager(this);
|
||||
((TextView) findViewById(R.id.txtServer)).setText(prefs.get(Constants.PREF_SERVER_ENCRYPTED));
|
||||
((TextView) findViewById(R.id.txtUsername)).setText(prefs.get(Constants.PREF_USERNAME_ENCRYPTED));
|
||||
((TextView) findViewById(R.id.txtDuration)).setText(String.valueOf(prefs.get(Constants.PREF_DURATION)));
|
||||
((TextView) findViewById(R.id.txtInterval)).setText(String.valueOf(prefs.get(Constants.PREF_INTERVAL)));
|
||||
((TextView) findViewById(R.id.txtCustomID)).setText(prefs.get(Constants.PREF_CUSTOM_ID));
|
||||
((TextView) findViewById(R.id.txtE2EPassword)).setText(prefs.get(Constants.PREF_E2E_PASSWORD));
|
||||
((TextView) findViewById(R.id.txtPassword)).setText(prefs.get(Constants.PREF_PASSWORD_ENCRYPTED));
|
||||
((TextView) findViewById(R.id.txtNickname)).setText(prefs.get(Constants.PREF_NICKNAME));
|
||||
// Because I can choose between an unchecked cast warning and an overly strong cast warning,
|
||||
// I'm going to with the latter.
|
||||
//noinspection OverlyStrongTypeCast
|
||||
((Spinner) findViewById(R.id.selUnit)).setSelection(prefs.get(Constants.PREF_DURATION_UNIT));
|
||||
((Checkable) findViewById(R.id.chkRemember)).setChecked(prefs.get(Constants.PREF_REMEMBER_PASSWORD));
|
||||
((Checkable) findViewById(R.id.chkAllowAdopt)).setChecked(prefs.get(Constants.PREF_ALLOW_ADOPTION));
|
||||
|
||||
// Set night mode preference.
|
||||
AppCompatDelegate.setDefaultNightMode(prefs.get(Constants.PREF_NIGHT_MODE).resolve());
|
||||
// Set app logo visibility.
|
||||
findViewById(R.id.imgLogo).setVisibility(prefs.get(Constants.PREF_HIDE_LOGO) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -473,7 +454,7 @@ public final class MainActivity extends AppCompatActivity {
|
|||
*/
|
||||
private final class SessionListenerImpl implements SessionListener {
|
||||
@Override
|
||||
public void onSessionCreated(Session session, final Share share, SessionInitiationReason reason) {
|
||||
public void onSessionCreated(Session session, final Share share) {
|
||||
// We now have a link to share, so we enable the additional link creation button if the backend supports it. Add an event handler to handle the user clicking on it.
|
||||
if (session.getBackendVersion().isAtLeast(Constants.VERSION_COMPAT_VIEW_ID)) {
|
||||
boolean allowNewLinkAdoption = ((Checkable) findViewById(R.id.chkAllowAdopt)).isChecked();
|
||||
|
|
@ -482,7 +463,7 @@ public final class MainActivity extends AppCompatActivity {
|
|||
btnLink.setOnClickListener(new AddLinkClickListener(MainActivity.this, session, allowNewLinkAdoption) {
|
||||
@Override
|
||||
public void onShareCreated(Share share) {
|
||||
MainActivity.this.manager.shareLocation(share, SessionInitiationReason.SHARE_ADDED);
|
||||
MainActivity.this.manager.shareLocation(share);
|
||||
}
|
||||
});
|
||||
btnLink.setEnabled(true);
|
||||
|
|
@ -496,35 +477,31 @@ public final class MainActivity extends AppCompatActivity {
|
|||
MainActivity.this.shareCountdown.start(session.getRemainingSeconds());
|
||||
|
||||
// Re-enable the start (stop) button and inform the user.
|
||||
disableUI();
|
||||
findViewById(R.id.btnShare).setEnabled(true);
|
||||
|
||||
// Service relaunches should be handled silently.
|
||||
if (reason == SessionInitiationReason.USER_STARTED) {
|
||||
MainActivity.this.dialogSvc.showDialog(R.string.ok_title, R.string.ok_message, Buttons.Two.OK_SHARE, new CustomDialogBuilder() {
|
||||
@Override
|
||||
public void onPositive() {
|
||||
// OK button
|
||||
}
|
||||
MainActivity.this.dialogSvc.showDialog(R.string.ok_title, R.string.ok_message, Buttons.OK_SHARE, new CustomDialogBuilder() {
|
||||
@Override
|
||||
public void onPositive() {
|
||||
// OK button
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegative() {
|
||||
// Share button
|
||||
Log.i("User requested to share %s", share); //NON-NLS
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType(Constants.INTENT_TYPE_COPY_LINK);
|
||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT, MainActivity.this.getString(R.string.share_subject));
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, share.getViewURL());
|
||||
MainActivity.this.startActivity(Intent.createChooser(shareIntent, MainActivity.this.getString(R.string.share_via)));
|
||||
}
|
||||
@Override
|
||||
public void onNegative() {
|
||||
// Share button
|
||||
Log.i("User requested to share %s", share); //NON-NLS
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType(Constants.INTENT_TYPE_COPY_LINK);
|
||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT, MainActivity.this.getString(R.string.share_subject));
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, share.getViewURL());
|
||||
MainActivity.this.startActivity(Intent.createChooser(shareIntent, MainActivity.this.getString(R.string.share_via)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View createView(Context ctx) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
@Nullable
|
||||
@Override
|
||||
public View createView(Context ctx) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
package info.varden.hauk.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.Proxy;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.R;
|
||||
import info.varden.hauk.dialog.Buttons;
|
||||
import info.varden.hauk.dialog.CustomDialogBuilder;
|
||||
import info.varden.hauk.dialog.DialogService;
|
||||
import info.varden.hauk.http.ConnectionParameters;
|
||||
import info.varden.hauk.http.SessionInitiationPacket;
|
||||
import info.varden.hauk.http.proxy.NameResolverTask;
|
||||
import info.varden.hauk.http.security.CertificateValidationPolicy;
|
||||
import info.varden.hauk.manager.SessionInitiationResponseHandler;
|
||||
import info.varden.hauk.manager.SessionManager;
|
||||
import info.varden.hauk.struct.AdoptabilityPreference;
|
||||
import info.varden.hauk.struct.ShareMode;
|
||||
import info.varden.hauk.system.LocationPermissionsNotGrantedException;
|
||||
import info.varden.hauk.system.LocationServicesDisabledException;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
import info.varden.hauk.utils.Log;
|
||||
import info.varden.hauk.utils.TimeUtils;
|
||||
|
||||
/**
|
||||
* Implementation of {@link NameResolverTask} for {@link MainActivity}. This implementation is
|
||||
* responsible for starting shares after the proxy configuration has been resolved.
|
||||
*/
|
||||
public final class ProxyHostnameResolverImpl extends NameResolverTask {
|
||||
private final PreferenceManager prefs;
|
||||
private final WeakReference<Activity> ctx;
|
||||
private final SessionManager manager;
|
||||
private final Runnable uiResetTask;
|
||||
private final SessionInitiationResponseHandler responseHandler;
|
||||
private final ShareMode mode;
|
||||
private final SessionInitiationPacket.InitParameters initParams;
|
||||
private final boolean allowAdoption;
|
||||
private final String nickname;
|
||||
private final String groupPin;
|
||||
|
||||
private final Object progressLock = new Object();
|
||||
private ProgressDialog progress = null;
|
||||
|
||||
ProxyHostnameResolverImpl(Activity ctx, SessionManager manager, Runnable uiResetTask, PreferenceManager prefs, SessionInitiationResponseHandler responseHandler, SessionInitiationPacket.InitParameters initParams, ShareMode mode, boolean allowAdoption, String nickname, String groupPin) {
|
||||
super(prefs);
|
||||
this.prefs = prefs;
|
||||
this.ctx = new WeakReference<>(ctx);
|
||||
this.manager = manager;
|
||||
this.uiResetTask = uiResetTask;
|
||||
this.responseHandler = responseHandler;
|
||||
this.initParams = initParams;
|
||||
this.mode = mode;
|
||||
this.allowAdoption = allowAdoption;
|
||||
this.nickname = nickname;
|
||||
this.groupPin = groupPin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResolutionStarted(String hostname) {
|
||||
// Show a progress dialog only if resolution has to take place. Otherwise, no progress
|
||||
// dialog is shown.
|
||||
synchronized (this.progressLock) {
|
||||
this.progress = new ProgressDialog(this.ctx.get());
|
||||
this.progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
this.progress.setTitle(R.string.progress_connect_title);
|
||||
this.progress.setMessage(this.ctx.get().getString(R.string.progress_resolving_proxy));
|
||||
this.progress.setIndeterminate(true);
|
||||
this.progress.setCancelable(false);
|
||||
this.progress.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHostUnresolved(final String hostname) {
|
||||
// The hostname couldn't be resolved. Show an error message.
|
||||
final Activity ctx = this.ctx.get();
|
||||
if (ctx != null) {
|
||||
ctx.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Hide progress dialog if visible.
|
||||
synchronized (ProxyHostnameResolverImpl.this.progressLock) {
|
||||
if (ProxyHostnameResolverImpl.this.progress != null) {
|
||||
ProxyHostnameResolverImpl.this.progress.dismiss();
|
||||
}
|
||||
}
|
||||
new DialogService(ctx).showDialog(R.string.err_connect, ctx.getString(R.string.err_proxy_host_resolution, hostname), ProxyHostnameResolverImpl.this.uiResetTask);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSuccess(@Nullable Proxy proxy) {
|
||||
// Hide progress dialog if visible.
|
||||
synchronized (this.progressLock) {
|
||||
if (this.progress != null) {
|
||||
this.progress.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
// Set the connection parameters.
|
||||
int timeout = this.prefs.get(Constants.PREF_CONNECTION_TIMEOUT) * (int) TimeUtils.MILLIS_PER_SECOND;
|
||||
CertificateValidationPolicy tlsPolicy = this.prefs.get(Constants.PREF_CERTIFICATE_VALIDATION);
|
||||
ConnectionParameters params;
|
||||
if (proxy == null) {
|
||||
params = new ConnectionParameters(null, null, timeout, tlsPolicy);
|
||||
} else {
|
||||
params = new ConnectionParameters(proxy.type(), proxy.address(), timeout, tlsPolicy);
|
||||
}
|
||||
this.initParams.setConnectionParameters(params);
|
||||
|
||||
// Start the location share.
|
||||
try {
|
||||
switch (this.mode) {
|
||||
case CREATE_ALONE:
|
||||
this.manager.shareLocation(this.initParams, this.responseHandler, this.allowAdoption ? AdoptabilityPreference.ALLOW_ADOPTION : AdoptabilityPreference.DISALLOW_ADOPTION);
|
||||
break;
|
||||
|
||||
case CREATE_GROUP:
|
||||
this.manager.shareLocation(this.initParams, this.responseHandler, this.nickname);
|
||||
break;
|
||||
|
||||
case JOIN_GROUP:
|
||||
this.manager.shareLocation(this.initParams, this.responseHandler, this.nickname, this.groupPin);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.wtf("Unknown sharing mode. This is not supposed to happen, ever"); //NON-NLS
|
||||
break;
|
||||
}
|
||||
} catch (LocationServicesDisabledException e) {
|
||||
Log.e("Share initiation was stopped because location services are disabled", e); //NON-NLS
|
||||
final Context ctx = this.ctx.get();
|
||||
if (ctx != null) {
|
||||
new DialogService(ctx).showDialog(R.string.err_client, R.string.err_location_disabled, Buttons.Two.SETTINGS_OK, new CustomDialogBuilder() {
|
||||
@Override
|
||||
public void onPositive() {
|
||||
// OK button
|
||||
ProxyHostnameResolverImpl.this.uiResetTask.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegative() {
|
||||
// Open Settings button
|
||||
ProxyHostnameResolverImpl.this.uiResetTask.run();
|
||||
ctx.startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View createView(Context ctx) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (LocationPermissionsNotGrantedException e) {
|
||||
Log.w("Share initiation was stopped because the user has not granted location permissions yet", e); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Exception ex) {
|
||||
// Proxy configuration failed for some reason. Show the error message to the user in a
|
||||
// dialog.
|
||||
final Activity ctx = this.ctx.get();
|
||||
if (ctx != null) {
|
||||
ctx.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Hide progress dialog if visible.
|
||||
synchronized (ProxyHostnameResolverImpl.this.progressLock) {
|
||||
if (ProxyHostnameResolverImpl.this.progress != null) {
|
||||
ProxyHostnameResolverImpl.this.progress.dismiss();
|
||||
}
|
||||
}
|
||||
new DialogService(ctx).showDialog(R.string.err_connect, ctx.getString(R.string.err_proxy_failure, ex.getLocalizedMessage()), ProxyHostnameResolverImpl.this.uiResetTask);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
package info.varden.hauk.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
|
@ -26,7 +24,7 @@ import info.varden.hauk.utils.Log;
|
|||
*
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class ShareLinkLayoutManager {
|
||||
final class ShareLinkLayoutManager {
|
||||
/**
|
||||
* The activity on which the links should be placed.
|
||||
*/
|
||||
|
|
@ -42,22 +40,16 @@ public final class ShareLinkLayoutManager {
|
|||
*/
|
||||
private final ViewGroup linkLayout;
|
||||
|
||||
/**
|
||||
* The header above the link list, used to change the text when there are no active shares.
|
||||
*/
|
||||
private final TextView headerView;
|
||||
|
||||
/**
|
||||
* A list of links displayed on the UI that the client is contributing to, paired with the View
|
||||
* representing the link of that share and its controls in the link list.
|
||||
*/
|
||||
private final Map<Share, View> shareViewMap;
|
||||
|
||||
ShareLinkLayoutManager(Activity act, SessionManager manager, ViewGroup linkLayout, TextView headerView) {
|
||||
ShareLinkLayoutManager(Activity act, SessionManager manager, ViewGroup linkLayout) {
|
||||
this.act = act;
|
||||
this.manager = manager;
|
||||
this.linkLayout = linkLayout;
|
||||
this.headerView = headerView;
|
||||
this.shareViewMap = new HashMap<>();
|
||||
removeAll();
|
||||
}
|
||||
|
|
@ -72,30 +64,14 @@ public final class ShareLinkLayoutManager {
|
|||
this.act.runOnUiThread(new AddTask(share));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of shares that are visible on the UI.
|
||||
*/
|
||||
public int getShareViewCount() {
|
||||
return this.shareViewMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a link from the list of links that represents the given share.
|
||||
*
|
||||
* @param share The share whose link should be removed from the link list.
|
||||
*/
|
||||
@SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
|
||||
void remove(final Share share) {
|
||||
this.act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ShareLinkLayoutManager.this.linkLayout.removeView(ShareLinkLayoutManager.this.shareViewMap.remove(share));
|
||||
if (ShareLinkLayoutManager.this.shareViewMap.isEmpty()) {
|
||||
ShareLinkLayoutManager.this.headerView.setText(R.string.label_heading_no_links);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
void remove(Share share) {
|
||||
this.linkLayout.removeView(this.shareViewMap.remove(share));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -103,14 +79,8 @@ public final class ShareLinkLayoutManager {
|
|||
*/
|
||||
@SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
|
||||
void removeAll() {
|
||||
this.act.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ShareLinkLayoutManager.this.linkLayout.removeAllViews();
|
||||
ShareLinkLayoutManager.this.shareViewMap.clear();
|
||||
ShareLinkLayoutManager.this.headerView.setText(R.string.label_heading_no_links);
|
||||
}
|
||||
});
|
||||
this.linkLayout.removeAllViews();
|
||||
this.shareViewMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,19 +99,13 @@ public final class ShareLinkLayoutManager {
|
|||
|
||||
// Get the table row layout and inflate it into a view.
|
||||
LayoutInflater inflater = ShareLinkLayoutManager.this.act.getLayoutInflater();
|
||||
View linkView = inflater.inflate(R.layout.content_link, ShareLinkLayoutManager.this.linkLayout, false);
|
||||
View linkView = inflater.inflate(R.layout.content_link, null);
|
||||
|
||||
// Add an event handler for the stop button. This will stop the given share only.
|
||||
Button btnStop = linkView.findViewById(R.id.linkBtnStop);
|
||||
if (this.share.getSession().getBackendVersion().isAtLeast(Constants.VERSION_COMPAT_VIEW_ID)) {
|
||||
Log.i("Server is compatible with individual share termination"); //NON-NLS
|
||||
btnStop.setOnClickListener(new StopLinkClickListener(
|
||||
ShareLinkLayoutManager.this.act,
|
||||
ShareLinkLayoutManager.this.manager,
|
||||
this.share,
|
||||
ShareLinkLayoutManager.this
|
||||
));
|
||||
btnStop.setLayoutParams(new TableRow.LayoutParams(calculateRealWidth(btnStop), ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
btnStop.setOnClickListener(new StopLinkClickListener(ShareLinkLayoutManager.this.manager, this.share));
|
||||
} else {
|
||||
Log.i("Server is not compatible with individual share termination"); //NON-NLS
|
||||
btnStop.setVisibility(View.GONE);
|
||||
|
|
@ -150,7 +114,6 @@ public final class ShareLinkLayoutManager {
|
|||
// Add an event handler for the share button.
|
||||
Button btnShare = linkView.findViewById(R.id.linkBtnShare);
|
||||
btnShare.setOnClickListener(new ShareLinkClickListener(ShareLinkLayoutManager.this.act, this.share));
|
||||
btnShare.setLayoutParams(new TableRow.LayoutParams(calculateRealWidth(btnShare), ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
// Update the text on the UI.
|
||||
TextView txtLink = linkView.findViewById(R.id.linkTxtLink);
|
||||
|
|
@ -163,33 +126,6 @@ public final class ShareLinkLayoutManager {
|
|||
Log.i("Putting share in class-level share list"); //NON-NLS
|
||||
ShareLinkLayoutManager.this.shareViewMap.put(this.share, linkView);
|
||||
ShareLinkLayoutManager.this.linkLayout.addView(linkView);
|
||||
ShareLinkLayoutManager.this.headerView.setText(R.string.label_heading_links);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the real displayed with of a {@link TextView}. The measurement of text view
|
||||
* (button) width is unreliable when inflating views, as it does not appear to take into account
|
||||
* drawables or padding properly. This method calculates the drawn width manually instead,
|
||||
* giving a reliable width that prevents character wrapping.
|
||||
*
|
||||
* @param view The text view to calculate the width for.
|
||||
* @return A width in pixels.
|
||||
*/
|
||||
private static int calculateRealWidth(TextView view) {
|
||||
// Calculate the padding of the view.
|
||||
int realWidth = view.getCompoundDrawablePadding() + view.getTotalPaddingStart() + view.getTotalPaddingEnd();
|
||||
|
||||
// Add the width of the contained text.
|
||||
String text = view.getTransformationMethod().getTransformation(view.getText(), view).toString();
|
||||
realWidth += view.getPaint().measureText(text);
|
||||
|
||||
// Add the widths of any drawables on the button.
|
||||
for (Drawable drawable : view.getCompoundDrawablesRelative()) {
|
||||
if (drawable != null) realWidth += drawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
// Return the result.
|
||||
return realWidth;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ public abstract class AddLinkClickListener implements View.OnClickListener {
|
|||
|
||||
@Override
|
||||
public final void onClick(View view) {
|
||||
this.dialogSvc.showDialog(R.string.create_link_title, R.string.create_link_body, Buttons.Two.CREATE_CANCEL, new CustomDialogBuilder() {
|
||||
this.dialogSvc.showDialog(R.string.create_link_title, R.string.create_link_body, Buttons.CREATE_CANCEL, new CustomDialogBuilder() {
|
||||
|
||||
private CheckBox chkAdopt;
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public final class InitiateAdoptionClickListener implements View.OnClickListener
|
|||
this.dialogSvc.showDialog(
|
||||
R.string.adopt_title,
|
||||
String.format(this.ctx.getString(R.string.adopt_body), this.share.getSession().getServerURL()),
|
||||
Buttons.Two.OK_CANCEL,
|
||||
Buttons.OK_CANCEL,
|
||||
new AdoptDialogBuilder(this.ctx, this.share) {
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package info.varden.hauk.ui.listener;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.utils.Log;
|
||||
import info.varden.hauk.utils.PreferenceManager;
|
||||
|
||||
/**
|
||||
* On-checked-change listener for the checkbox that lets users change their preference of whether or
|
||||
* not they want the app to save their password.
|
||||
*
|
||||
* @see info.varden.hauk.ui.MainActivity
|
||||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class RememberPasswordPreferenceChangedListener implements CompoundButton.OnCheckedChangeListener {
|
||||
/**
|
||||
* Android application context.
|
||||
*/
|
||||
private final Context ctx;
|
||||
|
||||
/**
|
||||
* The text input box that contains the password.
|
||||
*/
|
||||
private final EditText passwordBox;
|
||||
|
||||
public RememberPasswordPreferenceChangedListener(Context ctx, EditText passwordBox) {
|
||||
this.ctx = ctx;
|
||||
this.passwordBox = passwordBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Log.i("Password remember preference changed, remember=%s", isChecked); //NON-NLS
|
||||
// Update the stored password immediately. Clear the password from preferences if the box
|
||||
// was unchecked.
|
||||
PreferenceManager prefs = new PreferenceManager(this.ctx);
|
||||
prefs.set(Constants.PREF_REMEMBER_PASSWORD, isChecked);
|
||||
prefs.set(Constants.PREF_E2E_PASSWORD, isChecked ? this.passwordBox.getText().toString() : "");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,9 @@
|
|||
package info.varden.hauk.ui.listener;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.R;
|
||||
import info.varden.hauk.dialog.Buttons;
|
||||
import info.varden.hauk.dialog.DialogService;
|
||||
import info.varden.hauk.dialog.StopSharingConfirmationPrompt;
|
||||
import info.varden.hauk.manager.SessionManager;
|
||||
import info.varden.hauk.struct.Share;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
import info.varden.hauk.ui.ShareLinkLayoutManager;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
|
|
@ -22,57 +14,24 @@ import info.varden.hauk.utils.Log;
|
|||
* @author Marius Lindvall
|
||||
*/
|
||||
public final class StopLinkClickListener implements View.OnClickListener {
|
||||
/**
|
||||
* Service for showing dialogs.
|
||||
*/
|
||||
private final DialogService dialogSvc;
|
||||
|
||||
/**
|
||||
* Preference manager, for checking if a confirmation dialog has to be displayed.
|
||||
*/
|
||||
private final PreferenceManager prefs;
|
||||
|
||||
/**
|
||||
* Android application context.
|
||||
*/
|
||||
private final SessionManager manager;
|
||||
|
||||
/**
|
||||
* Link layout on the UI.
|
||||
*/
|
||||
private final ShareLinkLayoutManager layout;
|
||||
|
||||
/**
|
||||
* The share to share the link for.
|
||||
*/
|
||||
private final Share share;
|
||||
|
||||
public StopLinkClickListener(Context ctx, SessionManager manager, Share share, ShareLinkLayoutManager layout) {
|
||||
public StopLinkClickListener(SessionManager manager, Share share) {
|
||||
this.manager = manager;
|
||||
this.share = share;
|
||||
this.layout = layout;
|
||||
this.prefs = new PreferenceManager(ctx);
|
||||
this.dialogSvc = new DialogService(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.i("User requested to stop sharing %s", this.share); //NON-NLS
|
||||
// If there is only one share still active, stop the entire session rather than just this
|
||||
// one share.
|
||||
if (this.layout.getShareViewCount() == 1) {
|
||||
Log.i("Stopping session because there is only one share left"); //NON-NLS
|
||||
if (this.prefs.get(Constants.PREF_CONFIRM_STOP)) {
|
||||
this.dialogSvc.showDialog(R.string.dialog_confirm_stop_title, R.string.dialog_confirm_stop_body, Buttons.Three.YES_NO_REMEMBER, new StopSharingConfirmationPrompt(this.prefs, this.manager));
|
||||
} else {
|
||||
this.manager.stopSharing();
|
||||
}
|
||||
} else {
|
||||
if (this.prefs.get(Constants.PREF_CONFIRM_STOP)) {
|
||||
this.dialogSvc.showDialog(R.string.dialog_confirm_stop_title, R.string.dialog_confirm_stop_share, Buttons.Three.YES_NO_REMEMBER, new StopSharingConfirmationPrompt(this.prefs, this.manager, this.share));
|
||||
} else {
|
||||
this.manager.stopSharing(this.share);
|
||||
}
|
||||
}
|
||||
this.manager.stopSharing(this.share);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package info.varden.hauk.utils;
|
|||
import android.content.Context;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.system.preferences.PreferenceManager;
|
||||
|
||||
/**
|
||||
* Helper utility to migrate old, deprecated settings saved in shared preferences to modern storage.
|
||||
|
|
@ -43,10 +42,5 @@ public final class DeprecationMigrator {
|
|||
prefs.set(Constants.PREF_PASSWORD_ENCRYPTED, pass);
|
||||
prefs.clear(Constants.PREF_PASSWORD);
|
||||
}
|
||||
if (!prefs.has(Constants.PREF_ENABLE_E2E)) {
|
||||
boolean enableE2E = !prefs.get(Constants.PREF_E2E_PASSWORD).isEmpty();
|
||||
Log.i("Setting E2E enabled preference to %s based on stored preferences", String.valueOf(enableE2E)); //NON-NLS
|
||||
prefs.set(Constants.PREF_ENABLE_E2E, enableE2E);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
package info.varden.hauk.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import info.varden.hauk.BuildConfig;
|
||||
import info.varden.hauk.Constants;
|
||||
|
||||
/**
|
||||
* Log wrapper to simplify logging in Hauk.
|
||||
|
|
@ -18,16 +13,14 @@ public enum Log {
|
|||
private static final int STACK_DEPTH = 4;
|
||||
|
||||
/**
|
||||
* Returns the timestamp and caller of the log function.
|
||||
* Returns the caller of the log function.
|
||||
*/
|
||||
private static String getLogPrefix() {
|
||||
String timestamp = new SimpleDateFormat(Constants.DATE_FORMAT_LOG, Locale.US).format(new Date());
|
||||
|
||||
private static String getCaller() {
|
||||
String caller = Thread.currentThread().getStackTrace()[STACK_DEPTH].toString();
|
||||
if (caller.startsWith(BuildConfig.APPLICATION_ID)) {
|
||||
caller = caller.substring(BuildConfig.APPLICATION_ID.length());
|
||||
}
|
||||
return timestamp + ": " + caller + ": ";
|
||||
return caller;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,98 +37,98 @@ public enum Log {
|
|||
}
|
||||
|
||||
public static void e(String msg) {
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getLogPrefix() + msg);
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg);
|
||||
}
|
||||
|
||||
public static void e(String msg, Object... args) {
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)));
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)));
|
||||
}
|
||||
|
||||
public static void e(String msg, Throwable tr) {
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getLogPrefix() + msg, tr);
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg, tr);
|
||||
}
|
||||
|
||||
public static void e(String msg, Throwable tr, Object... args) {
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)), tr);
|
||||
android.util.Log.e(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)), tr);
|
||||
}
|
||||
|
||||
public static void w(String msg) {
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getLogPrefix() + msg);
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg);
|
||||
}
|
||||
|
||||
public static void w(String msg, Object... args) {
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)));
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)));
|
||||
}
|
||||
|
||||
public static void w(String msg, Throwable tr) {
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getLogPrefix() + msg, tr);
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg, tr);
|
||||
}
|
||||
|
||||
public static void w(String msg, Throwable tr, Object... args) {
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)), tr);
|
||||
android.util.Log.w(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)), tr);
|
||||
}
|
||||
|
||||
public static void i(String msg) {
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getLogPrefix() + msg);
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg);
|
||||
}
|
||||
|
||||
public static void i(String msg, Object... args) {
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)));
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)));
|
||||
}
|
||||
|
||||
public static void i(String msg, Throwable tr) {
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getLogPrefix() + msg, tr);
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg, tr);
|
||||
}
|
||||
|
||||
public static void i(String msg, Throwable tr, Object... args) {
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)), tr);
|
||||
android.util.Log.i(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)), tr);
|
||||
}
|
||||
|
||||
public static void v(String msg) {
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getLogPrefix() + msg);
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg);
|
||||
}
|
||||
|
||||
public static void v(String msg, Object... args) {
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)));
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)));
|
||||
}
|
||||
|
||||
public static void v(String msg, Throwable tr) {
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getLogPrefix() + msg, tr);
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg, tr);
|
||||
}
|
||||
|
||||
public static void v(String msg, Throwable tr, Object... args) {
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)), tr);
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)), tr);
|
||||
}
|
||||
|
||||
public static void d(String msg) {
|
||||
android.util.Log.d(BuildConfig.APPLICATION_ID, getLogPrefix() + msg);
|
||||
android.util.Log.d(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg);
|
||||
}
|
||||
|
||||
public static void d(String msg, Object... args) {
|
||||
android.util.Log.d(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)));
|
||||
android.util.Log.d(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)));
|
||||
}
|
||||
|
||||
public static void d(String msg, Throwable tr) {
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getLogPrefix() + msg, tr);
|
||||
android.util.Log.v(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg, tr);
|
||||
}
|
||||
|
||||
public static void d(String msg, Throwable tr, Object... args) {
|
||||
android.util.Log.d(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)), tr);
|
||||
android.util.Log.d(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)), tr);
|
||||
}
|
||||
|
||||
public static void wtf(String msg) {
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getLogPrefix() + msg);
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg);
|
||||
}
|
||||
|
||||
public static void wtf(String msg, Object... args) {
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)));
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)));
|
||||
}
|
||||
|
||||
public static void wtf(String msg, Throwable tr) {
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getLogPrefix() + msg, tr);
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getCaller() + ": " + msg, tr);
|
||||
}
|
||||
|
||||
public static void wtf(String msg, Throwable tr, Object... args) {
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getLogPrefix() + String.format(msg, argsToStrings(args)), tr);
|
||||
android.util.Log.wtf(BuildConfig.APPLICATION_ID, getCaller() + ": " + String.format(msg, argsToStrings(args)), tr);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
package info.varden.hauk.utils;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
|
|
@ -6,8 +6,6 @@ import info.varden.hauk.system.security.EncryptedData;
|
|||
import info.varden.hauk.system.security.EncryptionException;
|
||||
import info.varden.hauk.system.security.KeyStoreAlias;
|
||||
import info.varden.hauk.system.security.KeyStoreHelper;
|
||||
import info.varden.hauk.utils.Log;
|
||||
import info.varden.hauk.utils.StringSerializer;
|
||||
|
||||
/**
|
||||
* Represents a preference key to default value mapping pair for use with storing preferences for
|
||||
|
|
@ -18,34 +16,6 @@ import info.varden.hauk.utils.StringSerializer;
|
|||
*/
|
||||
public abstract class Preference<T> {
|
||||
|
||||
private final java.lang.String key;
|
||||
private final T def;
|
||||
private final Class<?> type;
|
||||
|
||||
private Preference(java.lang.String key, T def, Class<?> type) {
|
||||
this.key = key;
|
||||
this.def = def;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key of this preference in the {@link SharedPreferences} instance.
|
||||
*/
|
||||
public final java.lang.String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public final T getDefault() {
|
||||
return this.def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of data this preference stores.
|
||||
*/
|
||||
public final Class<?> getPreferenceType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the preference from the given preference object.
|
||||
*
|
||||
|
|
@ -66,24 +36,14 @@ public abstract class Preference<T> {
|
|||
*
|
||||
* @param prefs The shared preferences to check for preference existence in.
|
||||
*/
|
||||
public final boolean has(SharedPreferences prefs) {
|
||||
return prefs.contains(this.key);
|
||||
}
|
||||
abstract boolean has(SharedPreferences prefs);
|
||||
|
||||
/**
|
||||
* Clears the preference from the given preference object.
|
||||
*
|
||||
* @param prefs The shared preferences to clear the value from.
|
||||
*/
|
||||
public final void clear(SharedPreferences.Editor prefs) {
|
||||
prefs.remove(this.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this preference is expected to contain sensitive information that
|
||||
* should not be logged.
|
||||
*/
|
||||
abstract boolean isSensitive();
|
||||
abstract void clear(SharedPreferences.Editor prefs);
|
||||
|
||||
/**
|
||||
* Represents a String-value preference.
|
||||
|
|
@ -93,7 +53,6 @@ public abstract class Preference<T> {
|
|||
private final java.lang.String def;
|
||||
|
||||
public String(java.lang.String key, java.lang.String def) {
|
||||
super(key, def, java.lang.String.class);
|
||||
this.key = key;
|
||||
this.def = def;
|
||||
}
|
||||
|
|
@ -109,8 +68,13 @@ public abstract class Preference<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isSensitive() {
|
||||
return false;
|
||||
boolean has(SharedPreferences prefs) {
|
||||
return prefs.contains(this.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
void clear(SharedPreferences.Editor prefs) {
|
||||
prefs.remove(this.key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicateStringLiteralInspection")
|
||||
|
|
@ -128,7 +92,6 @@ public abstract class Preference<T> {
|
|||
private final java.lang.String def;
|
||||
|
||||
public EncryptedString(java.lang.String key, java.lang.String def) {
|
||||
super(key, def, java.lang.String.class);
|
||||
this.key = key;
|
||||
this.def = def;
|
||||
}
|
||||
|
|
@ -156,8 +119,13 @@ public abstract class Preference<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isSensitive() {
|
||||
return true;
|
||||
boolean has(SharedPreferences prefs) {
|
||||
return prefs.contains(this.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
void clear(SharedPreferences.Editor prefs) {
|
||||
prefs.remove(this.key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicateStringLiteralInspection")
|
||||
|
|
@ -175,7 +143,6 @@ public abstract class Preference<T> {
|
|||
private final int def;
|
||||
|
||||
public Integer(java.lang.String key, int def) {
|
||||
super(key, def, java.lang.Integer.class);
|
||||
this.key = key;
|
||||
this.def = def;
|
||||
}
|
||||
|
|
@ -191,8 +158,13 @@ public abstract class Preference<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isSensitive() {
|
||||
return false;
|
||||
boolean has(SharedPreferences prefs) {
|
||||
return prefs.contains(this.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
void clear(SharedPreferences.Editor prefs) {
|
||||
prefs.remove(this.key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicateStringLiteralInspection")
|
||||
|
|
@ -202,77 +174,6 @@ public abstract class Preference<T> {
|
|||
}
|
||||
}
|
||||
|
||||
public static final class Enum<U extends IndexedEnum<U>> extends Preference<U> {
|
||||
private final java.lang.String key;
|
||||
private final U def;
|
||||
|
||||
public Enum(java.lang.String key, U def) {
|
||||
super(key, def, IndexedEnum.class);
|
||||
this.key = key;
|
||||
this.def = def;
|
||||
}
|
||||
|
||||
@Override
|
||||
U get(SharedPreferences prefs) {
|
||||
try {
|
||||
return this.def.fromIndex(prefs.getInt(this.key, this.def.getIndex()));
|
||||
} catch (Exception e) {
|
||||
return this.def;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void set(SharedPreferences.Editor prefs, IndexedEnum value) {
|
||||
prefs.putInt(this.key, value.getIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isSensitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicateStringLiteralInspection")
|
||||
@Override
|
||||
public java.lang.String toString() {
|
||||
return "Preference<Enum>{key=" + this.key + ",default=" + this.def + "}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Float-value preference.
|
||||
*/
|
||||
public static final class Float extends Preference<java.lang.Float> {
|
||||
private final java.lang.String key;
|
||||
private final float def;
|
||||
|
||||
public Float(java.lang.String key, float def) {
|
||||
super(key, def, java.lang.Float.class);
|
||||
this.key = key;
|
||||
this.def = def;
|
||||
}
|
||||
|
||||
@Override
|
||||
java.lang.Float get(SharedPreferences prefs) {
|
||||
return prefs.getFloat(this.key, this.def);
|
||||
}
|
||||
|
||||
@Override
|
||||
void set(SharedPreferences.Editor prefs, java.lang.Float value) {
|
||||
prefs.putFloat(this.key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isSensitive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicateStringLiteralInspection")
|
||||
@Override
|
||||
public java.lang.String toString() {
|
||||
return "Preference<Float>{key=" + this.key + ",default=" + this.def + "}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Boolean-value preference.
|
||||
*/
|
||||
|
|
@ -282,7 +183,6 @@ public abstract class Preference<T> {
|
|||
|
||||
@SuppressWarnings("BooleanParameter")
|
||||
public Boolean(java.lang.String key, boolean def) {
|
||||
super(key, def, java.lang.Boolean.class);
|
||||
this.key = key;
|
||||
this.def = def;
|
||||
}
|
||||
|
|
@ -298,8 +198,13 @@ public abstract class Preference<T> {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isSensitive() {
|
||||
return false;
|
||||
boolean has(SharedPreferences prefs) {
|
||||
return prefs.contains(this.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
void clear(SharedPreferences.Editor prefs) {
|
||||
prefs.remove(this.key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicateStringLiteralInspection")
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
package info.varden.hauk.system.preferences;
|
||||
package info.varden.hauk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import info.varden.hauk.Constants;
|
||||
import info.varden.hauk.utils.Log;
|
||||
|
||||
/**
|
||||
* Utility class that manages connection preferences in Hauk.
|
||||
|
|
@ -40,7 +39,7 @@ public final class PreferenceManager {
|
|||
* @see Constants
|
||||
*/
|
||||
public <T> void set(Preference<T> pair, T value) {
|
||||
Log.v("Setting preference %s, value=%s", pair, pair.isSensitive() ? "<hidden>" : value); //NON-NLS
|
||||
Log.v("Setting preference %s, value=%s", pair, value); //NON-NLS
|
||||
SharedPreferences.Editor editor = this.prefs.edit();
|
||||
pair.set(editor, value);
|
||||
editor.apply();
|
||||
|
|
@ -44,21 +44,15 @@ public enum TimeUtils {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
public static int timeUnitsToSeconds(int scalar, int unit) throws ArithmeticException {
|
||||
public static int timeUnitsToSeconds(int scalar, int unit) {
|
||||
switch (unit) {
|
||||
case Constants.DURATION_UNIT_MINUTES:
|
||||
if (Integer.MAX_VALUE / SECONDS_PER_MINUTE < scalar)
|
||||
throw new ArithmeticException(String.format("Integer will overflow when converting %d minutes to seconds", scalar));
|
||||
return scalar * SECONDS_PER_MINUTE;
|
||||
|
||||
case Constants.DURATION_UNIT_HOURS:
|
||||
if (Integer.MAX_VALUE / SECONDS_PER_HOUR < scalar)
|
||||
throw new ArithmeticException(String.format("Integer will overflow when converting %d hours to seconds", scalar));
|
||||
return scalar * SECONDS_PER_HOUR;
|
||||
|
||||
case Constants.DURATION_UNIT_DAYS:
|
||||
if (Integer.MAX_VALUE / SECONDS_PER_DAY < scalar)
|
||||
throw new ArithmeticException(String.format("Integer will overflow when converting %d days to seconds", scalar));
|
||||
return scalar * SECONDS_PER_DAY;
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M9,2c-1.05,0 -2.05,0.16 -3,0.46 4.06,1.27 7,5.06 7,9.54 0,4.48 -2.94,8.27 -7,9.54 0.95,0.3 1.95,0.46 3,0.46 5.52,0 10,-4.48 10,-10S14.52,2 9,2z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:alpha="0.6"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0"
|
||||
android:tint="#333333"
|
||||
android:width="24dp">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||
</vector>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M13.5,5.5c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM9.8,8.9L7,23h2.1l1.8,-8 2.1,2v6h2v-7.5l-2.1,-2 0.6,-3C14.8,12 16.8,13 19,13v-2c-1.9,0 -3.5,-1 -4.3,-2.4l-1,-1.6c-0.4,-0.6 -1,-1 -1.7,-1 -0.3,0 -0.5,0.1 -0.8,0.1L6,8.3V13h2V9.6l1.8,-0.7"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15L23,13v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zM16.27,17.54C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.55,-3.09 1.46,-4.27l9.81,9.81z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z"/>
|
||||
</vector>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillAlpha=".3"
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M22,8V2L2,22h16V8z"/>
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M20,10v8h2v-8h-2zM12,22L12,12L2,22h10zM20,22h2v-2h-2v2z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M7.77,6.76L6.23,5.48 0.82,12l5.41,6.52 1.54,-1.28L3.42,12l4.35,-5.24zM7,13h2v-2L7,11v2zM17,11h-2v2h2v-2zM11,13h2v-2h-2v2zM17.77,5.48l-1.54,1.28L20.58,12l-4.35,5.24 1.54,1.28L23.18,12l-5.41,-6.52z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z"/>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M16,17.01V10h-2v7.01h-3L15,21l4,-3.99h-3zM9,3L5,6.99h3V14h2V6.99h3L9,3z"/>
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue