Commit 46252016 authored by Jonas L.'s avatar Jonas L.

Initial commit

parents
*.iml
.gradle
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="5">
<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" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<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" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
\ No newline at end of file
<?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>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
This diff is collapsed.
/build
/release
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 27
defaultConfig {
applicationId "de.determapp.android"
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dataBinding {
enabled = true
}
androidExtensions {
experimental = true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'io.resourcepool:ssdp-client:2.1.0'
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
implementation "android.arch.lifecycle:common-java8:$lifecycle_version"
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.android.support:support-v4:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation "android.arch.persistence.room:runtime:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version"
kapt "com.android.databinding:compiler:$gradle_version"
implementation "me.henrytao:recycler-pager-adapter:2.1.0"
implementation 'com.squareup.picasso:picasso:2.71828'
implementation "android.arch.work:work-runtime-ktx:$work_version"
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn com.squareup.okhttp3.**
-dontwarn okhttp3.**
-dontwarn okio.**
\ No newline at end of file
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "16935562b0f842002cf35a3f32cf78eb",
"entities": [
{
"tableName": "downloaded_local_network_project",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`project_id` TEXT NOT NULL, `hash` TEXT NOT NULL, `local_filename` TEXT NOT NULL, `file_size` INTEGER NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, PRIMARY KEY(`project_id`))",
"fields": [
{
"fieldPath": "projectId",
"columnName": "project_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "hash",
"columnName": "hash",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "localFilename",
"columnName": "local_filename",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "fileSize",
"columnName": "file_size",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"project_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "package_source_project",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`project_id` TEXT NOT NULL, `url` TEXT NOT NULL, `package_source_url` TEXT, `local_hash` TEXT, `local_filename` TEXT, `local_file_size` INTEGER, `title` TEXT NOT NULL, `resolution` INTEGER NOT NULL, PRIMARY KEY(`project_id`))",
"fields": [
{
"fieldPath": "projectId",
"columnName": "project_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "packageSourceUrl",
"columnName": "package_source_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "localHash",
"columnName": "local_hash",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "localFilename",
"columnName": "local_filename",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "projectJsonLocalFileSize",
"columnName": "local_file_size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "resolution",
"columnName": "resolution",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"project_id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "package_source",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `last_query_failed` INTEGER NOT NULL, PRIMARY KEY(`url`))",
"fields": [
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastQueryFailed",
"columnName": "last_query_failed",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"url"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"16935562b0f842002cf35a3f32cf78eb\")"
]
}
}
\ No newline at end of file
package de.determapp.android
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("de.determapp.android", appContext.packageName)
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.determapp.android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
<application
android:name=".Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".service.DownloadLocalNetworkPackageService"
android:exported="false" />
<activity
android:exported="false"
android:name=".ui.viewer.ViewerActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>
\ No newline at end of file
package de.determapp.android
import android.app.Application
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import de.determapp.android.constants.NotificationChannels
import de.determapp.android.service.work.UpdateContentWorker
class Application: Application() {
override fun onCreate() {
super.onCreate()
createNotificationChannels()
UpdateContentWorker.enqueuePeriodicallyIfNotDone()
}
private fun createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val downloadChannel = NotificationChannel(
NotificationChannels.DOWNLOAD_PROGRESS,
getString(R.string.notification_channel_download_name),
NotificationManager.IMPORTANCE_DEFAULT
)
downloadChannel.description
downloadChannel.setShowBadge(false)
downloadChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
downloadChannel.enableVibration(false)
downloadChannel.enableLights(false)
downloadChannel.setSound(null, null)
manager.createNotificationChannel(downloadChannel)
}
}
}
\ No newline at end of file
package de.determapp.android
import android.content.Context
import okhttp3.Cache
import okhttp3.OkHttpClient
import java.io.File
object Http {
val uncachedClient = OkHttpClient.Builder()
.build()
// the cached client is used to get streamed content and the content list
// it is NOT used for streamed images, they are requested with Picasso and use its cache
private var cachedClient: OkHttpClient? = null
private val lock = Object()
fun getClientWithCache(context: Context): OkHttpClient {
if (cachedClient == null) {
synchronized(lock, {
if (cachedClient == null) {
val cacheDir = File(
context.cacheDir,
"ok_http_cache"
)
// 10 MB
val cacheSize = 10 * 1024 * 1024
cachedClient = OkHttpClient.Builder()
.cache(Cache(cacheDir, cacheSize.toLong()))
.build()
}
})
}
return cachedClient!!
}
}
\ No newline at end of file
package de.determapp.android.constants
object NotificationChannels {
const val DOWNLOAD_PROGRESS = "DOWNLOAD_PROGRESS"
}
object NotificationIds {
const val DOWNLOAD_PROGRESS_LOCAL_NETWORK = 1
const val DOWNLOAD_PROGRESS_PACKAGE_SOURCE_WITH_TAG = 2
const val UPDATE_PROGRESS = 3
}
\ No newline at end of file
package de.determapp.android.content;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import de.determapp.android.BuildConfig;
import de.determapp.android.content.database.AppDatabase;
import de.determapp.android.content.database.AppDatabaseInstance;
import de.determapp.android.content.database.DownloadedLocalNetworkProject;
import de.determapp.android.content.database.PackageSourceProject;
import de.determapp.android.ui.viewer.ProjectSpec;
import de.determapp.android.util.Async;
public class ContentStorage {
private static final String LOG_TAG = "DataStorage";
private static ContentStorage instance;
public static synchronized ContentStorage with(Context context) {
if (instance == null) {
instance = new ContentStorage(context.getApplicationContext());
}
return instance;
}
private final AppDatabase database;
public final File contentFilesDirectory;
public final File imageFilesBaseDirectory;
// new items should be added before they are written
// items should only be removed when they are deleted manually and not referenced anywhere
public final Set<String> contentFilesToKeep = Collections.synchronizedSet(new HashSet<>());
private ContentStorage(Context context) {
contentFilesDirectory = new File(ContextCompat.getDataDir(context), "determapp_content");
imageFilesBaseDirectory = new File(context.getExternalFilesDir(null), "determapp_image");
database = AppDatabaseInstance.get(context);
contentFilesDirectory.mkdirs();
imageFilesBaseDirectory.mkdirs();
Async.INSTANCE.getDisk().submit((Runnable) this::cleanUpContentFilesStorage);
}
public File getImageDirectory(ProjectSpec project) {
return new File(
imageFilesBaseDirectory,
project.key()
);
}
public static String generateId() {
return UUID.randomUUID().toString();
}
private void cleanUpContentFilesStorage() {
for(DownloadedLocalNetworkProject item: database.localNetworkProjectDao().getProjects()) {
contentFilesToKeep.add(item.localFilename);
}
for(PackageSourceProject item: database.packageSourceProjectDao().getAllSync()) {
if (item.localFilename != null) {
contentFilesToKeep.add(item.localFilename);
}
}
cleanUpContentFilesStorage(contentFilesDirectory, contentFilesToKeep);
}
private void cleanUpContentFilesStorage(File directory, Set<String> filesToKeep) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "cleanUp(" + directory + ")");
}
for (File file: directory.listFiles()) {
if (!filesToKeep.contains(file.getName())) {
if (!file.delete()) {
if (BuildConfig.DEBUG) {
Log.w(LOG_TAG, "Couldn't delete old file: " + file);
}
}
}
}
}
}