From b9479b39dd9cf647a5fe350f673ed19aa36d9c70 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Fri, 19 Jun 2020 23:38:37 -0400 Subject: [PATCH 01/10] Update gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 117 +++++++++++++---------- gradlew.bat | 34 +++++-- 4 files changed, 94 insertions(+), 60 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..62d4c053550b91381bbd28b1afc82d634bf73a8a 100644 GIT binary patch literal 58910 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 literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +64,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,33 +75,14 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -90,7 +106,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -110,10 +126,12 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -138,27 +156,30 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730..a9f778a7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -8,14 +24,17 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +65,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,17 +78,13 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% From ba4617343efde5d115c42af873f018f8ab0169e8 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Fri, 19 Jun 2020 23:41:26 -0400 Subject: [PATCH 02/10] Rearrange gradle, update Studio and buildTools --- app/build.gradle | 30 ++++++++++++++++++++++-------- build.gradle | 15 +++++++++++++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 98fb9be0..8ae7b809 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,8 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 29 - buildToolsVersion '28.0.3' - useLibrary 'org.apache.http.legacy' + buildToolsVersion '29.0.3' defaultConfig { minSdkVersion 15 @@ -20,6 +19,7 @@ android { } } } + signingConfigs { releaseConfig { def propsFile = rootProject.file('keystore.properties') @@ -35,6 +35,7 @@ android { } } } + buildTypes { debug { minifyEnabled false @@ -46,6 +47,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + flavorDimensions "version" productFlavors { full { @@ -59,22 +61,34 @@ android { resValue "string", "search_history_authority", applicationId + ".search.SearchHistoryProvider" } } + lintOptions { disable 'MissingTranslation', 'ExtraTranslation', 'StringFormatInvalid', 'ValidFragment', 'Registered' } + + compileOptions { + encoding = 'UTF-8' + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + useLibrary 'org.apache.http.legacy' } dependencies { - implementation 'org.androidannotations:androidannotations-api:4.7.0' - implementation 'org.androidannotations:ormlite-api:4.7.0' - implementation 'com.j256.ormlite:ormlite-core:4.48' - implementation 'com.j256.ormlite:ormlite-android:4.48' - implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' +// Android support implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:support-annotations:28.0.0' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:support-v4:28.0.0' + +// Other + implementation 'org.androidannotations:androidannotations-api:4.7.0' + implementation 'org.androidannotations:ormlite-api:4.7.0' + implementation 'com.j256.ormlite:ormlite-core:4.48' + implementation 'com.j256.ormlite:ormlite-android:4.48' + implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.nispok:snackbar:2.11.0' implementation 'com.github.aegnor:rencode-java:cb628e824e' @@ -82,7 +96,7 @@ dependencies { transitive = true } implementation 'com.evernote:android-job:1.2.6' + annotationProcessor 'org.androidannotations:androidannotations:4.7.0' annotationProcessor 'org.androidannotations:ormlite:4.7.0' } - diff --git a/build.gradle b/build.gradle index b6e9a793..253838a0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,30 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + buildscript { repositories { google() jcenter() + maven { url "https://jitpack.io" } mavenCentral() } + dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.0.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files } } allprojects { repositories { google() - mavenCentral() jcenter() maven { url "https://jitpack.io" } + mavenCentral() } } + +task clean(type: Delete) { + delete rootProject.buildDir +} From 09e9603133b98b8f832b1a2ebeef92b23b84666c Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 20 Jun 2020 00:00:15 -0400 Subject: [PATCH 03/10] Migrate to AndroidX --- app/build.gradle | 8 +++----- .../core/app/settings/SettingsUtils.java | 4 ++-- .../org/transdroid/core/gui/DetailsActivity.java | 4 ++-- .../org/transdroid/core/gui/DetailsFragment.java | 6 +++--- .../core/gui/TorrentTasksExecutor.java | 4 ++-- .../transdroid/core/gui/TorrentsActivity.java | 16 ++++++++-------- .../transdroid/core/gui/TorrentsFragment.java | 8 ++++---- .../org/transdroid/core/gui/TransdroidApp.java | 4 ++-- .../transdroid/core/gui/log/DatabaseHelper.java | 2 +- .../core/gui/navigation/NavigationHelper.java | 6 +++--- .../core/gui/remoterss/RemoteRssFragment.java | 2 +- .../core/gui/rss/RssFeedsActivity.java | 14 +++++++------- .../core/gui/rss/RssFeedsFragment.java | 2 +- .../core/gui/rss/RssItemsActivity.java | 4 ++-- .../core/gui/rss/RssItemsFragment.java | 4 ++-- .../core/gui/search/SearchActivity.java | 6 +++--- .../core/gui/search/SearchResultsFragment.java | 2 +- .../InterceptableEditTextPreference.java | 2 +- .../gui/settings/PreferenceCompatActivity.java | 10 +++++----- .../gui/settings/ServerSettingsActivity.java | 2 +- .../gui/settings/SystemSettingsActivity.java | 2 +- .../transdroid/core/service/AppUpdateJob.java | 2 +- .../core/service/AppUpdateJobRunner.java | 2 +- .../core/service/ConnectivityHelper.java | 6 +++--- .../transdroid/core/service/RssCheckerJob.java | 2 +- .../core/service/RssCheckerJobRunner.java | 2 +- .../core/service/ScheduledJobCreator.java | 4 ++-- .../core/service/ServerCheckerJob.java | 2 +- .../core/service/ServerCheckerJobRunner.java | 2 +- .../core/widget/ListWidgetConfigActivity.java | 4 ++-- .../core/widget/ListWidgetProvider.java | 2 +- .../org/transdroid/daemon/DaemonException.java | 2 +- .../transdroid/daemon/Deluge/DelugeCommon.java | 2 +- .../daemon/Deluge/DelugeRpcAdapter.java | 2 +- .../daemon/Deluge/DelugeRpcClient.java | 2 +- .../main/res/layout-w600dp/activity_search.xml | 2 +- .../main/res/layout-w600dp/activity_torrents.xml | 8 ++++---- .../main/res/layout-w900dp/activity_rssfeeds.xml | 8 ++++---- .../main/res/layout-w900dp/activity_torrents.xml | 10 +++++----- app/src/main/res/layout/activity_details.xml | 2 +- app/src/main/res/layout/activity_rssfeeds.xml | 8 ++++---- app/src/main/res/layout/activity_rssitems.xml | 2 +- app/src/main/res/layout/activity_search.xml | 4 ++-- app/src/main/res/layout/activity_torrents.xml | 12 ++++++------ app/src/main/res/layout/fragment_details.xml | 8 ++++---- app/src/main/res/layout/fragment_torrents.xml | 4 ++-- app/src/main/res/menu/activity_search.xml | 2 +- app/src/main/res/menu/activity_torrents_main.xml | 2 +- gradle.properties | 2 ++ 49 files changed, 111 insertions(+), 111 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8ae7b809..dffc9a97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,11 +77,9 @@ android { dependencies { // Android support - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:recyclerview-v7:28.0.0' - implementation 'com.android.support:support-annotations:28.0.0' - implementation 'com.android.support:design:28.0.0' - implementation 'com.android.support:support-v4:28.0.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' + implementation 'com.google.android.material:material:1.1.0' // Other implementation 'org.androidannotations:androidannotations-api:4.7.0' diff --git a/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java b/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java index 79989549..1c5a2834 100644 --- a/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java +++ b/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java @@ -2,8 +2,8 @@ package org.transdroid.core.app.settings; import android.content.Context; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.app.AppCompatDelegate; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.Theme; diff --git a/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java b/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java index 2ec7ee0e..2dc28601 100644 --- a/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java @@ -20,8 +20,8 @@ import android.annotation.TargetApi; import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.SnackbarManager; diff --git a/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java b/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java index d701d54e..c4b8baf2 100644 --- a/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java @@ -23,9 +23,9 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.ActionMenuView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.ActionMenuView; import android.view.ActionMode; import android.view.Menu; import android.view.MenuItem; diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java b/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java index dd7c5036..297dec6c 100644 --- a/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java +++ b/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java @@ -16,8 +16,8 @@ */ package org.transdroid.core.gui; -import android.support.v7.widget.ActionMenuView; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.widget.ActionMenuView; +import androidx.appcompat.widget.Toolbar; import org.transdroid.daemon.Priority; import org.transdroid.daemon.Torrent; diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java index 054f140a..0d67a33f 100644 --- a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java @@ -24,14 +24,14 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.view.MenuItemCompat; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.ActionMenuView; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.Toolbar; +import androidx.annotation.NonNull; +import androidx.core.view.MenuItemCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.ActionMenuView; +import androidx.appcompat.widget.SearchView; +import androidx.appcompat.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java b/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java index 9553e107..9e2dccd0 100644 --- a/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java @@ -18,10 +18,10 @@ package org.transdroid.core.gui; import android.app.Fragment; import android.content.Context; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.ActionMenuView; -import android.support.v7.widget.Toolbar; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.ActionMenuView; +import androidx.appcompat.widget.Toolbar; import android.view.ActionMode; import android.view.Menu; import android.view.MenuItem; diff --git a/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java b/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java index 54c5088e..0f8299b0 100644 --- a/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java +++ b/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java @@ -17,8 +17,8 @@ package org.transdroid.core.gui; import android.app.Application; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.evernote.android.job.JobConfig; import com.evernote.android.job.JobManager; import com.evernote.android.job.util.JobLogger; diff --git a/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java b/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java index 448a3df9..dda98570 100644 --- a/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java +++ b/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java @@ -20,7 +20,7 @@ import java.sql.SQLException; import android.content.Context; import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.Keep; +import androidx.annotation.Keep; import android.util.Log; import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java index 2051c814..c7018530 100644 --- a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java +++ b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java @@ -26,9 +26,9 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import android.text.Spannable; import android.text.SpannableString; import android.text.style.TypefaceSpan; diff --git a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java index 43cb2e87..088292c8 100644 --- a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java @@ -17,7 +17,7 @@ package org.transdroid.core.gui.remoterss; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java index 06035b47..c5eba827 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java @@ -22,13 +22,13 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcel; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TabLayout; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.tabs.TabLayout; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java index 894bf4f0..f60acf9f 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java @@ -16,7 +16,7 @@ */ package org.transdroid.core.gui.rss; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.Menu; import android.view.MenuItem; import android.view.View; diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java index 46e9a7aa..7f9794da 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java @@ -20,8 +20,8 @@ import android.annotation.TargetApi; import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.EActivity; diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java index e14c42cf..48133995 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java @@ -23,8 +23,8 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.view.ActionMode; import android.view.Menu; diff --git a/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java b/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java index d7f85bf4..254840fe 100644 --- a/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java @@ -23,9 +23,9 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.SearchRecentSuggestions; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import androidx.core.view.MenuItemCompat; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; diff --git a/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java b/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java index 696d43b0..59d19702 100644 --- a/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java @@ -20,7 +20,7 @@ import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.text.TextUtils; import android.view.ActionMode; import android.view.Menu; diff --git a/app/src/main/java/org/transdroid/core/gui/settings/InterceptableEditTextPreference.java b/app/src/main/java/org/transdroid/core/gui/settings/InterceptableEditTextPreference.java index 71e48b96..c0b49954 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/InterceptableEditTextPreference.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/InterceptableEditTextPreference.java @@ -3,7 +3,7 @@ package org.transdroid.core.gui.settings; import android.content.Context; import android.os.Build; import android.preference.EditTextPreference; -import android.support.annotation.RequiresApi; +import androidx.annotation.RequiresApi; import android.util.AttributeSet; public class InterceptableEditTextPreference extends EditTextPreference { diff --git a/app/src/main/java/org/transdroid/core/gui/settings/PreferenceCompatActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/PreferenceCompatActivity.java index 6553f068..8cd67c2d 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/PreferenceCompatActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/PreferenceCompatActivity.java @@ -3,11 +3,11 @@ package org.transdroid.core.gui.settings; import android.content.res.Configuration; import android.os.Bundle; import android.preference.PreferenceActivity; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatCallback; -import android.support.v7.app.AppCompatDelegate; -import android.support.v7.view.ActionMode; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatCallback; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.view.ActionMode; public class PreferenceCompatActivity extends PreferenceActivity implements AppCompatCallback { diff --git a/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java index 8d3c4727..e8b85992 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java @@ -28,7 +28,7 @@ import android.os.Bundle; import android.preference.EditTextPreference; import android.preference.Preference; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.OptionsItem; diff --git a/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java b/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java index ebc1e066..2995086d 100644 --- a/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java @@ -28,7 +28,7 @@ import android.os.Bundle; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.TextUtils; import com.nispok.snackbar.Snackbar; diff --git a/app/src/main/java/org/transdroid/core/service/AppUpdateJob.java b/app/src/main/java/org/transdroid/core/service/AppUpdateJob.java index 62197eec..cf50c4c7 100644 --- a/app/src/main/java/org/transdroid/core/service/AppUpdateJob.java +++ b/app/src/main/java/org/transdroid/core/service/AppUpdateJob.java @@ -17,7 +17,7 @@ package org.transdroid.core.service; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; diff --git a/app/src/main/java/org/transdroid/core/service/AppUpdateJobRunner.java b/app/src/main/java/org/transdroid/core/service/AppUpdateJobRunner.java index ce41ddb9..23afcd45 100644 --- a/app/src/main/java/org/transdroid/core/service/AppUpdateJobRunner.java +++ b/app/src/main/java/org/transdroid/core/service/AppUpdateJobRunner.java @@ -23,7 +23,7 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; -import android.support.v4.app.NotificationCompat; +import androidx.core.app.NotificationCompat; import com.evernote.android.job.Job; diff --git a/app/src/main/java/org/transdroid/core/service/ConnectivityHelper.java b/app/src/main/java/org/transdroid/core/service/ConnectivityHelper.java index 4e532d36..ad0497c4 100644 --- a/app/src/main/java/org/transdroid/core/service/ConnectivityHelper.java +++ b/app/src/main/java/org/transdroid/core/service/ConnectivityHelper.java @@ -23,9 +23,9 @@ import android.content.DialogInterface; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean.Scope; import org.androidannotations.annotations.SystemService; diff --git a/app/src/main/java/org/transdroid/core/service/RssCheckerJob.java b/app/src/main/java/org/transdroid/core/service/RssCheckerJob.java index 85bf6479..066ce99a 100644 --- a/app/src/main/java/org/transdroid/core/service/RssCheckerJob.java +++ b/app/src/main/java/org/transdroid/core/service/RssCheckerJob.java @@ -17,7 +17,7 @@ package org.transdroid.core.service; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; diff --git a/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java b/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java index 910c90da..f1343a96 100644 --- a/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java +++ b/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java @@ -20,7 +20,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.support.v4.app.NotificationCompat; +import androidx.core.app.NotificationCompat; import com.evernote.android.job.Job; diff --git a/app/src/main/java/org/transdroid/core/service/ScheduledJobCreator.java b/app/src/main/java/org/transdroid/core/service/ScheduledJobCreator.java index 450aeacf..01f8c0f4 100644 --- a/app/src/main/java/org/transdroid/core/service/ScheduledJobCreator.java +++ b/app/src/main/java/org/transdroid/core/service/ScheduledJobCreator.java @@ -16,8 +16,8 @@ */ package org.transdroid.core.service; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.evernote.android.job.Job; import com.evernote.android.job.JobCreator; diff --git a/app/src/main/java/org/transdroid/core/service/ServerCheckerJob.java b/app/src/main/java/org/transdroid/core/service/ServerCheckerJob.java index a0f05bba..218bc129 100644 --- a/app/src/main/java/org/transdroid/core/service/ServerCheckerJob.java +++ b/app/src/main/java/org/transdroid/core/service/ServerCheckerJob.java @@ -17,7 +17,7 @@ package org.transdroid.core.service; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; diff --git a/app/src/main/java/org/transdroid/core/service/ServerCheckerJobRunner.java b/app/src/main/java/org/transdroid/core/service/ServerCheckerJobRunner.java index 8573dfae..094ef47f 100644 --- a/app/src/main/java/org/transdroid/core/service/ServerCheckerJobRunner.java +++ b/app/src/main/java/org/transdroid/core/service/ServerCheckerJobRunner.java @@ -20,7 +20,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.support.v4.app.NotificationCompat; +import androidx.core.app.NotificationCompat; import android.text.TextUtils; import com.evernote.android.job.Job; import org.androidannotations.annotations.Bean; diff --git a/app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java b/app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java index 73f806bb..e4d7d68d 100644 --- a/app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java +++ b/app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java @@ -22,8 +22,8 @@ import android.appwidget.AppWidgetManager; import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.CheckBox; diff --git a/app/src/main/java/org/transdroid/core/widget/ListWidgetProvider.java b/app/src/main/java/org/transdroid/core/widget/ListWidgetProvider.java index 2ef2b471..efc38d58 100644 --- a/app/src/main/java/org/transdroid/core/widget/ListWidgetProvider.java +++ b/app/src/main/java/org/transdroid/core/widget/ListWidgetProvider.java @@ -24,7 +24,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.widget.RemoteViews; import org.androidannotations.annotations.Bean; diff --git a/app/src/main/java/org/transdroid/daemon/DaemonException.java b/app/src/main/java/org/transdroid/daemon/DaemonException.java index ca367540..29040814 100644 --- a/app/src/main/java/org/transdroid/daemon/DaemonException.java +++ b/app/src/main/java/org/transdroid/daemon/DaemonException.java @@ -18,7 +18,7 @@ package org.transdroid.daemon; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; /** * An exception thrown when an error occurs inside a server daemon adapter. diff --git a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeCommon.java b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeCommon.java index 73622761..fdb76964 100644 --- a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeCommon.java +++ b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeCommon.java @@ -17,7 +17,7 @@ */ package org.transdroid.daemon.Deluge; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.transdroid.daemon.Priority; import org.transdroid.daemon.TorrentStatus; diff --git a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java index 252cab19..8c9e9011 100644 --- a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java @@ -17,7 +17,7 @@ */ package org.transdroid.daemon.Deluge; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.base64.android.Base64; import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.remoterss.data.RemoteRssChannel; diff --git a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java index 9906d560..47069f04 100644 --- a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java +++ b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcClient.java @@ -17,7 +17,7 @@ */ package org.transdroid.daemon.Deluge; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException.ExceptionType; import org.transdroid.daemon.DaemonSettings; diff --git a/app/src/main/res/layout-w600dp/activity_search.xml b/app/src/main/res/layout-w600dp/activity_search.xml index cf882efe..149c511f 100644 --- a/app/src/main/res/layout-w600dp/activity_search.xml +++ b/app/src/main/res/layout-w600dp/activity_search.xml @@ -23,7 +23,7 @@ android:orientation="horizontal" tools:context=".core.gui.search.SearchActivity_"> - - - - - - - - @@ -79,6 +79,6 @@ android:layout_height="match_parent" tools:layout="@layout/fragment_torrents" /> - + diff --git a/app/src/main/res/layout-w900dp/activity_torrents.xml b/app/src/main/res/layout-w900dp/activity_torrents.xml index daadda61..04d54f77 100644 --- a/app/src/main/res/layout-w900dp/activity_torrents.xml +++ b/app/src/main/res/layout-w900dp/activity_torrents.xml @@ -30,7 +30,7 @@ android:focusable="true" android:focusableInTouchMode="true"/> - - - - - - - - - @@ -73,6 +73,6 @@ tools:layout="@layout/fragment_torrents" /> - + diff --git a/app/src/main/res/layout/activity_rssitems.xml b/app/src/main/res/layout/activity_rssitems.xml index 1350234e..1b7df4a9 100644 --- a/app/src/main/res/layout/activity_rssitems.xml +++ b/app/src/main/res/layout/activity_rssitems.xml @@ -21,7 +21,7 @@ android:layout_height="match_parent" tools:context=".core.gui.rss.RssItemsActivity_"> - - - + . --> - - - - - - + diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index f6f5af5c..3470e821 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -23,7 +23,7 @@ android:layout_height="match_parent" tools:context=".core.gui.DetailsActivity_"> - - - - + - @@ -36,7 +36,7 @@ tools:listitem="@layout/list_item_torrent" tools:visibility="visible" /> - + Date: Sat, 20 Jun 2020 01:20:32 -0400 Subject: [PATCH 04/10] Replace local Base16Encoder with library --- app/build.gradle | 1 + .../openjpa/lib/util/Base16Encoder.java | 65 ------------------- 2 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 app/src/main/java/org/apache/openjpa/lib/util/Base16Encoder.java diff --git a/app/build.gradle b/app/build.gradle index dffc9a97..fb08995f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -90,6 +90,7 @@ dependencies { implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.nispok:snackbar:2.11.0' implementation 'com.github.aegnor:rencode-java:cb628e824e' + implementation 'org.apache.openjpa:openjpa-lib:3.1.1' implementation('com.github.afollestad.material-dialogs:core:0.9.6.0@aar') { transitive = true } diff --git a/app/src/main/java/org/apache/openjpa/lib/util/Base16Encoder.java b/app/src/main/java/org/apache/openjpa/lib/util/Base16Encoder.java deleted file mode 100644 index 51c301b1..00000000 --- a/app/src/main/java/org/apache/openjpa/lib/util/Base16Encoder.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.openjpa.lib.util; - -/** - * Base 16 encoder. - * - * @author Marc Prud'hommeaux - * @nojavadoc - */ -public class Base16Encoder { - - private final static char[] HEX = new char[]{ - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - /** - * Convert bytes to a base16 string. - */ - public static String encode(byte[] byteArray) { - StringBuffer hexBuffer = new StringBuffer(byteArray.length * 2); - for (int i = 0; i < byteArray.length; i++) - for (int j = 1; j >= 0; j--) - hexBuffer.append(HEX[(byteArray[i] >> (j * 4)) & 0xF]); - return hexBuffer.toString(); - } - - /** - * Convert a base16 string into a byte array. - */ - public static byte[] decode(String s) { - int len = s.length(); - byte[] r = new byte[len / 2]; - for (int i = 0; i < r.length; i++) { - int digit1 = s.charAt(i * 2), digit2 = s.charAt(i * 2 + 1); - if (digit1 >= '0' && digit1 <= '9') - digit1 -= '0'; - else if (digit1 >= 'A' && digit1 <= 'F') - digit1 -= 'A' - 10; - if (digit2 >= '0' && digit2 <= '9') - digit2 -= '0'; - else if (digit2 >= 'A' && digit2 <= 'F') - digit2 -= 'A' - 10; - - r[i] = (byte) ((digit1 << 4) + digit2); - } - return r; - } -} From 0d4a4d30dbe39ef8cc44cd9e4b9e3e7df73f82d6 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 20 Jun 2020 01:33:39 -0400 Subject: [PATCH 05/10] Replace local iharder Base64 with library --- app/build.gradle | 1 + .../main/java/org/base64/android/Base64.java | 1998 ----------------- .../daemon/Aria2c/Aria2Adapter.java | 48 +- .../daemon/Deluge/DelugeRpcAdapter.java | 2 +- .../Transmission/TransmissionAdapter.java | 6 +- .../daemon/Vuze/VuzeXmlOverHttpClient.java | 108 +- 6 files changed, 83 insertions(+), 2080 deletions(-) delete mode 100644 app/src/main/java/org/base64/android/Base64.java diff --git a/app/build.gradle b/app/build.gradle index fb08995f..18044712 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,6 +91,7 @@ dependencies { implementation 'com.nispok:snackbar:2.11.0' implementation 'com.github.aegnor:rencode-java:cb628e824e' implementation 'org.apache.openjpa:openjpa-lib:3.1.1' + implementation 'net.iharder:base64:2.3.9' implementation('com.github.afollestad.material-dialogs:core:0.9.6.0@aar') { transitive = true } diff --git a/app/src/main/java/org/base64/android/Base64.java b/app/src/main/java/org/base64/android/Base64.java deleted file mode 100644 index e4d3ec79..00000000 --- a/app/src/main/java/org/base64/android/Base64.java +++ /dev/null @@ -1,1998 +0,0 @@ -package org.base64.android; - -/** - *

Encodes and decodes to and from Base64 notation. Released as Public Domain software.

- *

Homepage: http://iharder.net/base64.

- * - *

Example:

- * - * String encoded = Base64.encode( myByteArray ); - *
- * byte[] myByteArray = Base64.decode( encoded ); - * - *

The options parameter, which appears in a few places, is used to pass - * several pieces of information to the encoder. In the "higher level" methods such as - * encodeBytes( bytes, options ) the options parameter can be used to indicate such - * things as first gzipping the bytes before encoding them, not inserting linefeeds, - * and encoding using the URL-safe and Ordered dialects.

- * - *

Note, according to RFC3548, - * Section 2.1, implementations should not add line feeds unless explicitly told - * to do so. I've got Base64 set to this behavior now, although earlier versions - * broke lines by default.

- * - *

The constants defined in Base64 can be OR-ed together to combine options, so you - * might make a call like this:

- * - * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); - *

to compress the data before encoding it and then making the output have newline characters.

- *

Also...

- * String encoded = Base64.encodeBytes( crazyString.getBytes() ); - * - * - * - *

- * Change Log: - *

- *
    - *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java - * footprint with its CharEncoders and so forth. Fixed some javadocs that were - * inconsistent. Removed imports and specified things like java.io.IOException - * explicitly inline.
  • - *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the - * final encoded data will be so that the code doesn't have to create two output - * arrays: an oversized initial one and then a final, exact-sized one. Big win - * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not - * using the gzip options which uses a different mechanism with streams and stuff).
  • - *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some - * similar helper methods to be more efficient with memory by not returning a - * String but just a byte array.
  • - *
  • v2.3 - This is not a drop-in replacement! This is two years of comments - * and bug fixes queued up and finally executed. Thanks to everyone who sent - * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. - * Much bad coding was cleaned up including throwing exceptions where necessary - * instead of returning null values or something similar. Here are some changes - * that may affect you: - *
      - *
    • Does not break lines, by default. This is to keep in compliance with - * RFC3548.
    • - *
    • Throws exceptions instead of returning null values. Because some operations - * (especially those that may permit the GZIP option) use IO streams, there - * is a possiblity of an java.io.IOException being thrown. After some discussion and - * thought, I've changed the behavior of the methods to throw java.io.IOExceptions - * rather than return null if ever there's an error. I think this is more - * appropriate, though it will require some changes to your code. Sorry, - * it should have been done this way to begin with.
    • - *
    • Removed all references to System.out, System.err, and the like. - * Shame on me. All I can say is sorry they were ever there.
    • - *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as needed - * such as when passed arrays are null or offsets are invalid.
    • - *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. - * This was especially annoying before for people who were thorough in their - * own projects and then had gobs of javadoc warnings on this file.
    • - *
    - *
  • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug - * when using very small files (~< 40 bytes).
  • - *
  • v2.2 - Added some helper methods for encoding/decoding directly from - * one file to the next. Also added a main() method to support command line - * encoding/decoding from one file to the next. Also added these Base64 dialects: - *
      - *
    1. The default is RFC3548 format.
    2. - *
    3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates - * URL and file name friendly format as described in Section 4 of RFC3548. - * http://www.faqs.org/rfcs/rfc3548.html
    4. - *
    5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates - * URL and file name friendly format that preserves lexical ordering as described - * in http://www.faqs.org/qa/rfcc-1940.html
    6. - *
    - * Special thanks to Jim Kellerman at http://www.powerset.com/ - * for contributing the new Base64 dialects. - *
  • - * - *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added - * some convenience methods for reading and writing to and from files.
  • - *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems - * with other encodings (like EBCDIC).
  • - *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the - * encoded data was a single byte.
  • - *
  • v2.0 - I got rid of methods that used booleans to set options. - * Now everything is more consolidated and cleaner. The code now detects - * when data that's being decoded is gzip-compressed and will decompress it - * automatically. Generally things are cleaner. You'll probably have to - * change some method calls that you were making to support the new - * options format (ints that you "OR" together).
  • - *
  • v1.5.1 - Fixed bug when decompressing and decoding to a - * byte[] using decode( String s, boolean gzipCompressed ). - * Added the ability to "suspend" encoding in the Output Stream so - * you can turn on and off the encoding if you need to embed base64 - * data in an otherwise "normal" stream (like an XML file).
  • - *
  • v1.5 - Output stream pases on flush() command but doesn't do anything itself. - * This helps when using GZIP streams. - * Added the ability to GZip-compress objects before encoding them.
  • - *
  • v1.4 - Added helper methods to read/write files.
  • - *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • - *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream - * where last buffer being read, if not completely full, was not returned.
  • - *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • - *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • - *
- * - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/base64 - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.3.3 - */ -public class Base64 -{ - -/* ******** P U B L I C F I E L D S ******** */ - - - /** No options specified. Value is zero. */ - public final static int NO_OPTIONS = 0; - - /** Specify encoding in first bit. Value is one. */ - public final static int ENCODE = 1; - - - /** Specify decoding in first bit. Value is zero. */ - public final static int DECODE = 0; - - - /** Specify that data should be gzip-compressed in second bit. Value is two. */ - public final static int GZIP = 2; - - - /** Do break lines when encoding. Value is 8. */ - public final static int DO_BREAK_LINES = 8; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described - * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * It is important to note that data encoded this way is not officially valid Base64, - * or at the very least should not be called Base64 without also specifying that is - * was encoded using the URL- and Filename-safe dialect. - */ - public final static int URL_SAFE = 16; - - - /** - * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** Maximum line length (76) of Base64 output. */ - private final static int MAX_LINE_LENGTH = 76; - - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte)'='; - - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte)'\n'; - - - /** Preferred encoding. */ - private final static String PREFERRED_ENCODING = "US-ASCII"; - - - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - -/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - - /** The 64 valid Base64 values. */ - /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ - private final static byte[] _STANDARD_ALPHABET = { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' - }; - - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] _STANDARD_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9,-9,-9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9 // Decimal 123 - 126 - /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - -/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." - */ - private final static byte[] _URL_SAFE_ALPHABET = { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' - }; - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ - private final static byte[] _URL_SAFE_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9 // Decimal 123 - 126 - /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - - -/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ - - /** - * I don't get the point of this technique, but someone requested it, - * and it is described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - private final static byte[] _ORDERED_ALPHABET = { - (byte)'-', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', - (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'_', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' - }; - - /** - * Used in decoding the "ordered" dialect of Base64. - */ - private final static byte[] _ORDERED_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 0, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' - 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 37, // Underscore at decimal 95 - -9, // Decimal 96 - 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' - 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' - -9,-9,-9,-9 // Decimal 123 - 126 - /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - -/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ - - - /** - * Returns one of the _SOMETHING_ALPHABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URLSAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getAlphabet( int options ) { - if ((options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_ALPHABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_ALPHABET; - } else { - return _STANDARD_ALPHABET; - } - } // end getAlphabet - - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URL_SAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getDecodabet( int options ) { - if( (options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_DECODABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_DECODABET; - } else { - return _STANDARD_DECODABET; - } - } // end getAlphabet - - - - /** Defeats instantiation. */ - private Base64(){} - - - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * Code can reuse a byte array by passing a four-byte array as b4. - * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - * @since 1.5.1 - */ - private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { - encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); - return b4; - } // end encode3to4 - - - /** - *

Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes.

- *

This is the lowest level of the encoding methods with - * all possible parameters.

- * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset, int options ) { - - byte[] ALPHABET = getAlphabet( options ); - - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) - | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) - | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); - - switch( numSigBytes ) - { - case 3: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; - return destination; - - case 2: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - case 1: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = EQUALS_SIGN; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded ByteBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while( raw.hasRemaining() ){ - int rem = Math.min(3,raw.remaining()); - raw.get(raw3,0,rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); - encoded.put(enc4); - } // end input remaining - } - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded CharBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while( raw.hasRemaining() ){ - int rem = Math.min(3,raw.remaining()); - raw.get(raw3,0,rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); - for( int i = 0; i < 4; i++ ){ - encoded.put( (char)(enc4[i] & 0xFF) ); - } - } // end input remaining - } - - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - * - *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - * @throws java.io.IOException if there is an error - * @throws NullPointerException if serializedObject is null - * @since 1.4 - */ - public static String encodeObject( java.io.Serializable serializableObject ) - throws java.io.IOException { - return encodeObject( serializableObject, NO_OPTIONS ); - } // end encodeObject - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - * - *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * The object is not GZip-compressed before being encoded. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     * 
- *

- * Example: encodeObject( myObj, Base64.GZIP ) or - *

- * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @since 2.0 - */ - public static String encodeObject( java.io.Serializable serializableObject, int options ) - throws java.io.IOException { - - if( serializableObject == null ){ - throw new NullPointerException( "Cannot serialize a null object." ); - } // end if: null - - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.io.ObjectOutputStream oos = null; - - - try { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - // Note that the optional GZIPping is handled by Base64.OutputStream. - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | options ); - oos = new java.io.ObjectOutputStream( b64os ); - oos.writeObject( serializableObject ); - } // end try - catch( java.io.IOException e ) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try{ oos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - // Return value according to relevant encoding. - try { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue){ - // Fall back to some Java default - return new String( baos.toByteArray() ); - } // end catch - - } // end encode - - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @return The data in Base64-encoded form - * @throws NullPointerException if source array is null - * @since 1.4 - */ - public static String encodeBytes( byte[] source ) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * - *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * - * @param source The data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { - return encodeBytes( source, 0, source.length, options ); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - *

As of v 2.3, if there is an error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @return The Base64-encoded data as a String - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 1.4 - */ - public static String encodeBytes( byte[] source, int off, int len ) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes( source, off, len, NO_OPTIONS ); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * - *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { - byte[] encoded = encodeBytesToBytes( source, off, len, options ); - - // Return value according to relevant encoding. - try { - return new String( encoded, PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - return new String( encoded ); - } // end catch - - } // end encodeBytes - - - - - /** - * Similar to {@link #encodeBytes(byte[])} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * - * @param source The data to convert - * @return The Base64-encoded data as a byte[] (of ASCII characters) - * @throws NullPointerException if source array is null - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes( byte[] source ) { - byte[] encoded = null; - try { - encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); - } catch( java.io.IOException ex ) { - assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); - } - return encoded; - } - - - /** - * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { - - if( source == null ){ - throw new NullPointerException( "Cannot serialize a null array." ); - } // end if: null - - if( off < 0 ){ - throw new IllegalArgumentException( "Cannot have negative offset: " + off ); - } // end if: off < 0 - - if( len < 0 ){ - throw new IllegalArgumentException( "Cannot have length offset: " + len ); - } // end if: len < 0 - - if( off + len > source.length ){ - throw new IllegalArgumentException( - String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); - } // end if: off < 0 - - - - // Compress? - if( (options & GZIP) > 0 ) { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - Base64.OutputStream b64os = null; - - try { - // GZip -> Base64 -> ByteArray - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | options ); - gzos = new java.util.zip.GZIPOutputStream( b64os ); - - gzos.write( source, off, len ); - gzos.close(); - } // end try - catch( java.io.IOException e ) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - return baos.toByteArray(); - } // end if: compress - - // Else, don't compress. Better not to use streams at all then. - else { - boolean breakLines = (options & DO_BREAK_LINES) > 0; - - //int len43 = len * 4 / 3; - //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - // Try to determine more precisely how big the array needs to be. - // If we get it right, we don't have to do an array copy, and - // we save a bunch of memory. - int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding - if( breakLines ){ - encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters - } - byte[] outBuff = new byte[ encLen ]; - - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for( ; d < len2; d+=3, e+=4 ) { - encode3to4( source, d+off, 3, outBuff, e, options ); - - lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) - { - outBuff[e+4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if( d < len ) { - encode3to4( source, d+off, len - d, outBuff, e, options ); - e += 4; - } // end if: some padding needed - - - // Only resize array if we didn't guess it right. - if( e < outBuff.length - 1 ){ - byte[] finalOut = new byte[e]; - System.arraycopy(outBuff,0, finalOut,0,e); - //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); - return finalOut; - } else { - //System.err.println("No need to resize array."); - return outBuff; - } - - } // end else: don't compress - - } // end encodeBytesToBytes - - - - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - *

This is the lowest level of the decoding methods with - * all possible parameters.

- * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) - * @return the number of decoded bytes converted - * @throws NullPointerException if source or destination arrays are null - * @throws IllegalArgumentException if srcOffset or destOffset are invalid - * or there is not enough room in the array. - * @since 1.3 - */ - private static int decode4to3( - byte[] source, int srcOffset, - byte[] destination, int destOffset, int options ) { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Source array was null." ); - } // end if - if( destination == null ){ - throw new NullPointerException( "Destination array was null." ); - } // end if - if( srcOffset < 0 || srcOffset + 3 >= source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); - } // end if - if( destOffset < 0 || destOffset +2 >= destination.length ){ - throw new IllegalArgumentException( String.format( - "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); - } // end if - - - byte[] DECODABET = getDecodabet( options ); - - // Example: Dk== - if( source[ srcOffset + 2] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - return 1; - } - - // Example: DkL= - else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); - return 2; - } - - // Example: DkLE - else { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) - | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - - - destination[ destOffset ] = (byte)( outBuff >> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); - destination[ destOffset + 2 ] = (byte)( outBuff ); - - return 3; - } - } // end decodeToBytes - - - - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 2.3.1 - */ - public static byte[] decode( byte[] source ){ - byte[] decoded = null; - try { - decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); - } catch( java.io.IOException ex ) { - assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); - } - return decoded; - } - - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param options Can specify options such as alphabet type to use - * @return decoded data - * @throws java.io.IOException If bogus characters exist in source data - * @since 1.3 - */ - public static byte[] decode( byte[] source, int off, int len, int options ) - throws java.io.IOException { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Cannot decode null source array." ); - } // end if - if( off < 0 || off + len > source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); - } // end if - - if( len == 0 ){ - return new byte[0]; - }else if( len < 4 ){ - throw new IllegalArgumentException( - "Base64-encoded string must have at least four characters, but length specified was " + len ); - } // end if - - byte[] DECODABET = getDecodabet( options ); - - int len34 = len * 3 / 4; // Estimate on array size - byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output - int outBuffPosn = 0; // Keep track of where we're writing - - byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space - int b4Posn = 0; // Keep track of four byte input buffer - int i = 0; // Source array counter - byte sbiCrop = 0; // Low seven bits (ASCII) of input - byte sbiDecode = 0; // Special value from DECODABET - - for( i = off; i < off+len; i++ ) { // Loop through source - - sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits - sbiDecode = DECODABET[ sbiCrop ]; // Special value - - // White space, Equals sign, or legit Base64 character - // Note the values such as -5 and -9 in the - // DECODABETs at the top of the file. - if( sbiDecode >= WHITE_SPACE_ENC ) { - if( sbiDecode >= EQUALS_SIGN_ENC ) { - b4[ b4Posn++ ] = sbiCrop; // Save non-whitespace - if( b4Posn > 3 ) { // Time to decode? - outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if( sbiCrop == EQUALS_SIGN ) { - break; - } // end if: equals sign - } // end if: quartet built - } // end if: equals sign or better - } // end if: white space, equals sign or better - else { - // There's a bad input character in the Base64 stream. - throw new java.io.IOException( String.format( - "Bad Base64 input character '%c' in array position %d", source[i], i ) ); - } // end else: - } // each input character - - byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); - return out; - } // end decode - - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - * @throws java.io.IOException If there is a problem - * @since 1.4 - */ - public static byte[] decode( String s ) throws java.io.IOException { - return decode( s, NO_OPTIONS ); - } - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @param options encode options such as URL_SAFE - * @return the decoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if s is null - * @since 1.4 - */ - public static byte[] decode( String s, int options ) throws java.io.IOException { - - if( s == null ){ - throw new NullPointerException( "Input string was null." ); - } // end if - - byte[] bytes; - try { - bytes = s.getBytes( PREFERRED_ENCODING ); - } // end try - catch( java.io.UnsupportedEncodingException uee ) { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode( bytes, 0, bytes.length, options ); - - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - if( bytes != null && bytes.length >= 4 ) { - - int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream( bytes ); - gzis = new java.util.zip.GZIPInputStream( bais ); - - while( ( length = gzis.read( buffer ) ) >= 0 ) { - baos.write(buffer,0,length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch( java.io.IOException e ) { - // Just return originally-decoded bytes - } // end catch - finally { - try{ baos.close(); } catch( Exception e ){} - try{ gzis.close(); } catch( Exception e ){} - try{ bais.close(); } catch( Exception e ){} - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 1.5 - */ - public static Object decodeToObject( String encodedObject ) - throws java.io.IOException, java.lang.ClassNotFoundException { - - // Decode and gunzip if necessary - byte[] objBytes = decode( encodedObject ); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try { - bais = new java.io.ByteArrayInputStream( objBytes ); - ois = new java.io.ObjectInputStream( bais ); - - obj = ois.readObject(); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - catch( java.lang.ClassNotFoundException e ) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - finally { - try{ bais.close(); } catch( Exception e ){} - try{ ois.close(); } catch( Exception e ){} - } // end finally - - return obj; - } // end decodeObject - - - - /** - * Convenience method for encoding data to a file. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if dataToEncode is null - * @since 2.1 - */ - public static void encodeToFile( byte[] dataToEncode, String filename ) - throws java.io.IOException { - - if( dataToEncode == null ){ - throw new NullPointerException( "Data to encode was null." ); - } // end iff - - Base64.OutputStream bos = null; - try { - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.ENCODE ); - bos.write( dataToEncode ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - } // end encodeToFile - - - /** - * Convenience method for decoding data to a file. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static void decodeToFile( String dataToDecode, String filename ) - throws java.io.IOException { - - Base64.OutputStream bos = null; - try{ - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.DECODE ); - bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - } // end decodeToFile - - - - - /** - * Convenience method for reading a base64-encoded - * file and decoding it. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading encoded data - * @return decoded byte array - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static byte[] decodeFromFile( String filename ) - throws java.io.IOException { - - byte[] decodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = null; - int length = 0; - int numBytes = 0; - - // Check for size of file - if( file.length() > Integer.MAX_VALUE ) - { - throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); - } // end if: file too big for int index - buffer = new byte[ (int)file.length() ]; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.DECODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { - length += numBytes; - } // end while - - // Save in a variable to return - decodedData = new byte[ length ]; - System.arraycopy( buffer, 0, decodedData, 0, length ); - - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return decodedData; - } // end decodeFromFile - - - - /** - * Convenience method for reading a binary file - * and base64-encoding it. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading binary data - * @return base64-encoded string - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static String encodeFromFile( String filename ) - throws java.io.IOException { - - String encodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4),40) ]; // Need max() for math on small files (v2.2.1) - int length = 0; - int numBytes = 0; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.ENCODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { - length += numBytes; - } // end while - - // Save in a variable to return - encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); - - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return encodedData; - } // end encodeFromFile - - /** - * Reads infile and encodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void encodeFileToFile( String infile, String outfile ) - throws java.io.IOException { - - String encoded = Base64.encodeFromFile( infile ); - java.io.OutputStream out = null; - try{ - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream( outfile ) ); - out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { out.close(); } - catch( Exception ex ){} - } // end finally - } // end encodeFileToFile - - - /** - * Reads infile and decodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void decodeFileToFile( String infile, String outfile ) - throws java.io.IOException { - - byte[] decoded = Base64.decodeFromFile( infile ); - java.io.OutputStream out = null; - try{ - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream( outfile ) ); - out.write( decoded ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { out.close(); } - catch( Exception ex ){} - } // end finally - } // end decodeFileToFile - - - /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - - /** - * A {@link Base64.InputStream} will read data from another - * java.io.InputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class InputStream extends java.io.FilterInputStream { - - private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; - private boolean breakLines; // Break lines at less than 80 characters - private int options; // Record options used to create the stream. - private byte[] decodabet; // Local copies to avoid extra method calls - - - /** - * Constructs a {@link Base64.InputStream} in DECODE mode. - * - * @param in the java.io.InputStream from which to read data. - * @since 1.3 - */ - public InputStream( java.io.InputStream in ) { - this( in, DECODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.InputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.InputStream( in, Base64.DECODE ) - * - * - * @param in the java.io.InputStream from which to read data. - * @param options Specified options - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 2.0 - */ - public InputStream( java.io.InputStream in, int options ) { - - super( in ); - this.options = options; // Record for later - this.breakLines = (options & DO_BREAK_LINES) > 0; - this.encode = (options & ENCODE) > 0; - this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[ bufferLength ]; - this.position = -1; - this.lineLength = 0; - this.decodabet = getDecodabet(options); - } // end constructor - - /** - * Reads enough of the input stream to convert - * to/from Base64 and returns the next byte. - * - * @return next byte - * @since 1.3 - */ - @Override - public int read() throws java.io.IOException { - - // Do we need to get data? - if( position < 0 ) { - if( encode ) { - byte[] b3 = new byte[3]; - int numBinaryBytes = 0; - for( int i = 0; i < 3; i++ ) { - int b = in.read(); - - // If end of stream, b is -1. - if( b >= 0 ) { - b3[i] = (byte)b; - numBinaryBytes++; - } else { - break; // out of for loop - } // end else: end of stream - - } // end for: each needed input byte - - if( numBinaryBytes > 0 ) { - encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); - position = 0; - numSigBytes = 4; - } // end if: got data - else { - return -1; // Must be end of stream - } // end else - } // end if: encoding - - // Else decoding - else { - byte[] b4 = new byte[4]; - int i = 0; - for( i = 0; i < 4; i++ ) { - // Read four "meaningful" bytes: - int b = 0; - do{ b = in.read(); } - while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); - - if( b < 0 ) { - break; // Reads a -1 if end of stream - } // end if: end of stream - - b4[i] = (byte)b; - } // end for: each needed input byte - - if( i == 4 ) { - numSigBytes = decode4to3( b4, 0, buffer, 0, options ); - position = 0; - } // end if: got four characters - else if( i == 0 ){ - return -1; - } // end else if: also padded correctly - else { - // Must have broken out from above. - throw new java.io.IOException( "Improperly padded Base64 input." ); - } // end - - } // end else: decode - } // end else: get data - - // Got data? - if( position >= 0 ) { - // End of relevant data? - if( /*!encode &&*/ position >= numSigBytes ){ - return -1; - } // end if: got data - - if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { - lineLength = 0; - return '\n'; - } // end if - else { - lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[ position++ ]; - - if( position >= bufferLength ) { - position = -1; - } // end if: end - - return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. - } // end else - } // end if: position >= 0 - - // Else error - else { - throw new java.io.IOException( "Error in Base64 code reading stream." ); - } // end else - } // end read - - - /** - * Calls {@link #read()} repeatedly until the end of stream - * is reached or len bytes are read. - * Returns number of bytes read into array or -1 if - * end of stream is encountered. - * - * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array - * @return bytes read into array or -1 if end of stream is encountered. - * @since 1.3 - */ - @Override - public int read( byte[] dest, int off, int len ) - throws java.io.IOException { - int i; - int b; - for( i = 0; i < len; i++ ) { - b = read(); - - if( b >= 0 ) { - dest[off + i] = (byte) b; - } - else if( i == 0 ) { - return -1; - } - else { - break; // Out of 'for' loop - } // Out of 'for' loop - } // end for: each byte read - return i; - } // end read - - } // end inner class InputStream - - - - - - - /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - - /** - * A {@link Base64.OutputStream} will write data to another - * java.io.OutputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class OutputStream extends java.io.FilterOutputStream { - - private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; - private boolean breakLines; - private byte[] b4; // Scratch used in a few places - private boolean suspendEncoding; - private int options; // Record for later - private byte[] decodabet; // Local copies to avoid extra method calls - - /** - * Constructs a {@link Base64.OutputStream} in ENCODE mode. - * - * @param out the java.io.OutputStream to which data will be written. - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out ) { - this( out, ENCODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.OutputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: don't break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.OutputStream( out, Base64.ENCODE ) - * - * @param out the java.io.OutputStream to which data will be written. - * @param options Specified options. - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out, int options ) { - super( out ); - this.breakLines = (options & DO_BREAK_LINES) > 0; - this.encode = (options & ENCODE) > 0; - this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[ bufferLength ]; - this.position = 0; - this.lineLength = 0; - this.suspendEncoding = false; - this.b4 = new byte[4]; - this.options = options; - this.decodabet = getDecodabet(options); - } // end constructor - - - /** - * Writes the byte to the output stream after - * converting to/from Base64 notation. - * When encoding, bytes are buffered three - * at a time before the output stream actually - * gets a write() call. - * When decoding, bytes are buffered four - * at a time. - * - * @param theByte the byte to write - * @since 1.3 - */ - @Override - public void write(int theByte) - throws java.io.IOException { - // Encoding suspended? - if( suspendEncoding ) { - super.out.write( theByte ); - return; - } // end if: supsended - - // Encode? - if( encode ) { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) { // Enough to encode. - - out.write( encode3to4( b4, buffer, bufferLength, options ) ); - - lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) { - out.write( NEW_LINE ); - lineLength = 0; - } // end if: end of line - - position = 0; - } // end if: enough to output - } // end if: encoding - - // Else, Decoding - else { - // Meaningful Base64 character? - if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) { // Enough to output. - - int len = Base64.decode4to3( buffer, 0, b4, 0, options ); - out.write( b4, 0, len ); - position = 0; - } // end if: enough to output - } // end if: meaningful base64 character - else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { - throw new java.io.IOException( "Invalid character in Base64 data." ); - } // end else: not white space either - } // end else: decoding - } // end write - - - - /** - * Calls {@link #write(int)} repeatedly until len - * bytes are written. - * - * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array - * @since 1.3 - */ - @Override - public void write( byte[] theBytes, int off, int len ) - throws java.io.IOException { - // Encoding suspended? - if( suspendEncoding ) { - super.out.write( theBytes, off, len ); - return; - } // end if: supsended - - for( int i = 0; i < len; i++ ) { - write( theBytes[ off + i ] ); - } // end for: each byte written - - } // end write - - - - /** - * Method added by PHIL. [Thanks, PHIL. -Rob] - * This pads the buffer without closing the stream. - * @throws java.io.IOException if there's an error. - */ - public void flushBase64() throws java.io.IOException { - if( position > 0 ) { - if( encode ) { - out.write( encode3to4( b4, buffer, position, options ) ); - position = 0; - } // end if: encoding - else { - throw new java.io.IOException( "Base64 input not properly padded." ); - } // end else: decoding - } // end if: buffer partially full - - } // end flush - - /** - * Flushes the stream (and the enclosing streams). - * @throws java.io.IOException - * @since 2.3 - */ - @Override - public void flush() throws java.io.IOException { - flushBase64(); - super.flush(); - } - - /** - * Flushes and closes (I think, in the superclass) the stream. - * - * @since 1.3 - */ - @Override - public void close() throws java.io.IOException { - // 1. Ensure that pending characters are written - flush(); - - // 2. Actually close the stream - // Base class both flushes and closes. - super.close(); - - buffer = null; - out = null; - } // end close - - - - /** - * Suspends encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @throws java.io.IOException if there's an error flushing - * @since 1.5.1 - */ - public void suspendEncoding() throws java.io.IOException { - flushBase64(); - this.suspendEncoding = true; - } // end suspendEncoding - - - /** - * Resumes encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @since 1.5.1 - */ - public void resumeEncoding() { - this.suspendEncoding = false; - } // end resumeEncoding - - - - } // end inner class OutputStream - - -} // end class Base64 diff --git a/app/src/main/java/org/transdroid/daemon/Aria2c/Aria2Adapter.java b/app/src/main/java/org/transdroid/daemon/Aria2c/Aria2Adapter.java index 1e02ef9e..d7806e80 100644 --- a/app/src/main/java/org/transdroid/daemon/Aria2c/Aria2Adapter.java +++ b/app/src/main/java/org/transdroid/daemon/Aria2c/Aria2Adapter.java @@ -1,19 +1,19 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Aria2c; @@ -25,7 +25,7 @@ import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; -import org.base64.android.Base64; +import net.iharder.Base64; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -360,27 +360,27 @@ public class Aria2Adapter implements IDaemonAdapter { } // @formatter:off torrents.add(new Torrent( - j, - tor.getString("gid"), - name, - status, - tor.getString("dir"), + j, + tor.getString("gid"), + name, + status, + tor.getString("dir"), downloadSpeed, - tor.getInt("uploadSpeed"), - tor.getInt("connections"), - numSeeders , - tor.getInt("connections"), - numSeeders, - (downloadSpeed > 0? (int) (totalLength / downloadSpeed): -1), - completedLength, + tor.getInt("uploadSpeed"), + tor.getInt("connections"), + numSeeders , + tor.getInt("connections"), + numSeeders, + (downloadSpeed > 0? (int) (totalLength / downloadSpeed): -1), + completedLength, tor.getLong("uploadLength"), - totalLength, + totalLength, completedLength / (float) totalLength, // Percentage to [0..1] 0f, // Not available null, // Not available null, // Not available null, // Not available - error, + error, settings.getType())); // @formatter:on @@ -403,12 +403,12 @@ public class Aria2Adapter implements IDaemonAdapter { } // @formatter:off files.add(new TorrentFile( - Integer.toString(file.getInt("index")), - rel, - rel, - file.getString("path"), + Integer.toString(file.getInt("index")), + rel, + rel, + file.getString("path"), file.getLong("length"), - file.getLong("completedLength"), + file.getLong("completedLength"), file.getBoolean("selected") ? Priority.Normal : Priority.Off)); // @formatter:on diff --git a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java index 8c9e9011..31e371f3 100644 --- a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeRpcAdapter.java @@ -18,7 +18,7 @@ package org.transdroid.daemon.Deluge; import androidx.annotation.NonNull; -import org.base64.android.Base64; +import net.iharder.Base64; import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.remoterss.data.RemoteRssChannel; import org.transdroid.core.gui.remoterss.data.RemoteRssItem; diff --git a/app/src/main/java/org/transdroid/daemon/Transmission/TransmissionAdapter.java b/app/src/main/java/org/transdroid/daemon/Transmission/TransmissionAdapter.java index 2cf42baf..7acc1e85 100644 --- a/app/src/main/java/org/transdroid/daemon/Transmission/TransmissionAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Transmission/TransmissionAdapter.java @@ -22,8 +22,8 @@ import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; -import org.base64.android.Base64; -import org.base64.android.Base64.InputStream; +import net.iharder.Base64; +import net.iharder.Base64.InputStream; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -192,7 +192,7 @@ public class TransmissionAdapter implements IDaemonAdapter { // Encode the .torrent file's data InputStream in = - new Base64.InputStream(new FileInputStream(new File(URI.create(file))), Base64.ENCODE); + new InputStream(new FileInputStream(new File(URI.create(file))), Base64.ENCODE); StringWriter writer = new StringWriter(); int c; while ((c = in.read()) != -1) { diff --git a/app/src/main/java/org/transdroid/daemon/Vuze/VuzeXmlOverHttpClient.java b/app/src/main/java/org/transdroid/daemon/Vuze/VuzeXmlOverHttpClient.java index 67eec402..8bfc8b74 100644 --- a/app/src/main/java/org/transdroid/daemon/Vuze/VuzeXmlOverHttpClient.java +++ b/app/src/main/java/org/transdroid/daemon/Vuze/VuzeXmlOverHttpClient.java @@ -1,19 +1,19 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Vuze; @@ -43,7 +43,7 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; -import org.base64.android.Base64; +import net.iharder.Base64; import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonException.ExceptionType; @@ -56,16 +56,16 @@ import org.xmlpull.v1.XmlSerializer; import android.util.Xml; /** - * Implements an XML-RPC-like client that build and parses XML following + * Implements an XML-RPC-like client that build and parses XML following * Vuze's XML over HTTP plug-in (which unfortunately is incompatible with * the default XML-RPC protocol). - * + * * The documentation can be found at http://azureus.sourceforge.net/plugin_details.php?plugin=xml_http_if&docu=1#1 * and some stuff is at http://wiki.vuze.com/index.php/XML_over_HTTP - * + * * A lot of it is copied from the org.xmlrpc.android library's XMLRPCClient * as can be found at http://code.google.com/p/android-xmlrpc - * + * * @author erickok * */ @@ -87,7 +87,7 @@ public class VuzeXmlOverHttpClient { private final static String TAG_ANNOUNCE = "announce_result"; private final static String TAG_SCRAPE = "scrape_result"; private final static String TAG_CACHED_PROPERTY_NAMES = "cached_property_names"; - + private DefaultHttpClient client; private HttpPost postMethod; private Random random; @@ -105,14 +105,14 @@ public class VuzeXmlOverHttpClient { postMethod.addHeader("Content-Type", "text/xml"); // WARNING - // I had to disable "Expect: 100-Continue" header since I had - // two second delay between sending http POST request and POST body + // I had to disable "Expect: 100-Continue" header since I had + // two second delay between sending http POST request and POST body HttpParams httpParams = postMethod.getParams(); HttpProtocolParams.setUseExpectContinue(httpParams, false); HttpConnectionParams.setConnectionTimeout(httpParams, settings.getTimeoutInMilliseconds()); - HttpConnectionParams.setSoTimeout(httpParams, settings.getTimeoutInMilliseconds()); - + HttpConnectionParams.setSoTimeout(httpParams, settings.getTimeoutInMilliseconds()); + SchemeRegistry registry = new SchemeRegistry(); SocketFactory httpsSocketFactory; if (settings.getSslTrustKey() != null) { @@ -124,7 +124,7 @@ public class VuzeXmlOverHttpClient { } registry.register(new Scheme("http", new PlainSocketFactory(), 80)); registry.register(new Scheme("https", httpsSocketFactory, 443)); - + client = new DefaultHttpClient(new ThreadSafeClientConnManager(httpParams, registry), httpParams); if (settings.shouldUseAuthentication()) { if (settings.getUsername() == null || settings.getPassword() == null) { @@ -137,11 +137,11 @@ public class VuzeXmlOverHttpClient { new UsernamePasswordCredentials(username, password)); } } - + random = new Random(); random.nextInt(); } - + /** * Convenience constructor. Creates new instance based on server String address * @param settings The server connection settings @@ -151,23 +151,23 @@ public class VuzeXmlOverHttpClient { public VuzeXmlOverHttpClient(DaemonSettings settings, String url) throws DaemonException { this(settings, URI.create(url)); } - + protected Map callXMLRPC(Long object, String method, Object[] params, Long connectionID, boolean paramsAreVuzeObjects) throws DaemonException { try { - + // prepare POST body XmlSerializer serializer = Xml.newSerializer(); StringWriter bodyWriter = new StringWriter(); serializer.setOutput(bodyWriter); serializer.startDocument(null, null); serializer.startTag(null, TAG_REQUEST); - + // set object if (object != null) { serializer.startTag(null, TAG_OBJECT).startTag(null, TAG_OBJECT_ID) .text(object.toString()).endTag(null, TAG_OBJECT_ID).endTag(null, TAG_OBJECT); } - + // set method serializer.startTag(null, TAG_METHOD).text(method).endTag(null, TAG_METHOD); if (params != null && params.length != 0) { @@ -188,7 +188,7 @@ public class VuzeXmlOverHttpClient { } serializer.endTag(null, TAG_PARAMS); } - + // set connection id if (connectionID != null) { serializer.startTag(null, TAG_CONNECTION_ID).text(connectionID.toString()).endTag(null, TAG_CONNECTION_ID); @@ -199,16 +199,16 @@ public class VuzeXmlOverHttpClient { serializer.endTag(null, TAG_REQUEST); serializer.endDocument(); - + // set POST body HttpEntity entity = new StringEntity(bodyWriter.toString()); postMethod.setEntity(entity); - + // Force preemptive authentication - // This makes sure there is an 'Authentication: ' header being send before trying and failing and retrying + // This makes sure there is an 'Authentication: ' header being send before trying and failing and retrying // by the basic authentication mechanism of DefaultHttpClient postMethod.addHeader("Authorization", "Basic " + Base64.encodeBytes((username + ":" + password).getBytes())); - + // execute HTTP POST request HttpResponse response = client.execute(postMethod); @@ -225,33 +225,33 @@ public class VuzeXmlOverHttpClient { // setup pull parser XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser(); entity = response.getEntity(); - //String temp = HttpHelper.ConvertStreamToString(entity.getContent()); + //String temp = HttpHelper.ConvertStreamToString(entity.getContent()); //Reader reader = new StringReader(temp); Reader reader = new InputStreamReader(entity.getContent()); pullParser.setInput(reader); - + // lets start pulling... pullParser.nextTag(); pullParser.require(XmlPullParser.START_TAG, null, TAG_RESPONSE); - + // build list of returned values int next = pullParser.nextTag(); // skip to first start tag in list String name = pullParser.getName(); // get name of the first start tag - + // Empty response? if (next == XmlPullParser.END_TAG && name.equals(TAG_RESPONSE)) { - + return null; - + } else if (name.equals(TAG_ERROR)) { - + // Error String errorText = pullParser.nextText(); // the value of the ERROR entity.consumeContent(); throw new DaemonException(ExceptionType.ConnectionError, errorText); - + } else { - + // Consume a list of ENTRYs? if (name.equals(TAG_ENTRY)) { @@ -262,15 +262,15 @@ public class VuzeXmlOverHttpClient { } entity.consumeContent(); return entries; - + } else { - + // Only a single object was returned, not an entry listing return consumeObject(pullParser); } - + } - + } catch (IOException e) { throw new DaemonException(ExceptionType.ConnectionError, e.toString()); } catch (XmlPullParserException e) { @@ -282,11 +282,11 @@ public class VuzeXmlOverHttpClient { int next = pullParser.nextTag(); String name = pullParser.getName(); - + // Consume the ENTRY objects Map returnValues = new HashMap(); while (next == XmlPullParser.START_TAG) { - + if (name.equals(TAG_TORRENT) || name.equals(TAG_ANNOUNCE) || name.equals(TAG_SCRAPE) || name.equals(TAG_STATS)) { // One of the objects contained inside an entry pullParser.nextTag(); @@ -297,21 +297,21 @@ public class VuzeXmlOverHttpClient { } next = pullParser.nextTag(); // skip to next start tag name = pullParser.getName(); // get name of the new start tag - + } - + // Consume ENTRY ending pullParser.nextTag(); - + return returnValues; - + } private Map consumeObject(XmlPullParser pullParser) throws XmlPullParserException, IOException { int next = XmlPullParser.START_TAG; String name = pullParser.getName(); - + // Consume bottom-level (contains no objects of its own) object Map returnValues = new HashMap(); while (next == XmlPullParser.START_TAG && !(name.equals(TAG_CACHED_PROPERTY_NAMES))) { @@ -326,11 +326,11 @@ public class VuzeXmlOverHttpClient { } next = pullParser.nextTag(); // skip to next start tag name = pullParser.getName(); // get name of the new start tag - + } - + return returnValues; - + } private String serialize(Object value) { @@ -344,7 +344,7 @@ public class VuzeXmlOverHttpClient { return null; } - /* For now cast all integers as Long; this prevents casting problems later on when + /* For now cast all integers as Long; this prevents casting problems later on when * we know it's a long but the value was small so it is casted to an Integer here // Integer? try { @@ -353,7 +353,7 @@ public class VuzeXmlOverHttpClient { } catch (NumberFormatException e) { // Just continue trying the next type }*/ - + // Long? try { Long longnum = Long.parseLong(rawText); @@ -361,7 +361,7 @@ public class VuzeXmlOverHttpClient { } catch (NumberFormatException e) { // Just continue trying the next type } - + // Double? try { Double doublenum = Double.parseDouble(rawText); @@ -369,9 +369,9 @@ public class VuzeXmlOverHttpClient { } catch (NumberFormatException e) { // Just continue trying the next type } - + // String otherwise return rawText; } - + } From 660478aafcbceb32e633285b2a37fa7607de9233 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 20 Jun 2020 01:37:04 -0400 Subject: [PATCH 06/10] Update ColorPicker to 696bb05 --- app/build.gradle | 1 + .../colorpicker/AlphaPatternDrawable.java | 141 +- .../colorpicker/ColorPickerDialog.java | 321 ++-- .../colorpicker/ColorPickerPanelView.java | 210 +-- .../colorpicker/ColorPickerPreference.java | 551 ++++--- .../colorpicker/ColorPickerView.java | 1357 +++++++++-------- .../res/layout-land/dialog_color_picker.xml | 131 +- .../main/res/layout/dialog_color_picker.xml | 137 +- 8 files changed, 1533 insertions(+), 1316 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 18044712..6791fdb0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,6 +78,7 @@ android { dependencies { // Android support implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' implementation 'com.google.android.material:material:1.1.0' diff --git a/app/src/main/java/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java b/app/src/main/java/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java index dbd04276..c31dad08 100644 --- a/app/src/main/java/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java +++ b/app/src/main/java/net/margaritov/preference/colorpicker/AlphaPatternDrawable.java @@ -14,6 +14,8 @@ * limitations under the License. */ +// Taken from https://github.com/attenzione/android-ColorPickerPreference/tree/696bb050527d6a7ae14e47b7472a0640a7ff08e6 + package net.margaritov.preference.colorpicker; import android.graphics.Bitmap; @@ -28,101 +30,102 @@ import android.graphics.drawable.Drawable; * This drawable that draws a simple white and gray chessboard pattern. * It's pattern you will often see as a background behind a * partly transparent image in many applications. + * * @author Daniel Nilsson */ public class AlphaPatternDrawable extends Drawable { - private int mRectangleSize = 10; + private int mRectangleSize = 10; - private Paint mPaint = new Paint(); - private Paint mPaintWhite = new Paint(); - private Paint mPaintGray = new Paint(); + private Paint mPaint = new Paint(); + private Paint mPaintWhite = new Paint(); + private Paint mPaintGray = new Paint(); - private int numRectanglesHorizontal; - private int numRectanglesVertical; + private int numRectanglesHorizontal; + private int numRectanglesVertical; - /** - * Bitmap in which the pattern will be cahched. - */ - private Bitmap mBitmap; + /** + * Bitmap in which the pattern will be cahched. + */ + private Bitmap mBitmap; - public AlphaPatternDrawable(int rectangleSize) { - mRectangleSize = rectangleSize; - mPaintWhite.setColor(0xffffffff); - mPaintGray.setColor(0xffcbcbcb); - } + public AlphaPatternDrawable(int rectangleSize) { + mRectangleSize = rectangleSize; + mPaintWhite.setColor(0xffffffff); + mPaintGray.setColor(0xffcbcbcb); + } - @Override - public void draw(Canvas canvas) { - canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); - } + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); + } - @Override - public int getOpacity() { - return 0; - } + @Override + public int getOpacity() { + return 0; + } - @Override - public void setAlpha(int alpha) { - throw new UnsupportedOperationException("Alpha is not supported by this drawwable."); - } + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawwable."); + } - @Override - public void setColorFilter(ColorFilter cf) { - throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable."); - } + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable."); + } - @Override - protected void onBoundsChange(Rect bounds) { - super.onBoundsChange(bounds); + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); - int height = bounds.height(); - int width = bounds.width(); + int height = bounds.height(); + int width = bounds.width(); - numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize)); - numRectanglesVertical = (int) Math.ceil(height / mRectangleSize); + numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize)); + numRectanglesVertical = (int) Math.ceil(height / mRectangleSize); - generatePatternBitmap(); + generatePatternBitmap(); - } + } - /** - * This will generate a bitmap with the pattern - * as big as the rectangle we were allow to draw on. - * We do this to chache the bitmap so we don't need to - * recreate it each time draw() is called since it - * takes a few milliseconds. - */ - private void generatePatternBitmap() { + /** + * This will generate a bitmap with the pattern + * as big as the rectangle we were allow to draw on. + * We do this to chache the bitmap so we don't need to + * recreate it each time draw() is called since it + * takes a few milliseconds. + */ + private void generatePatternBitmap() { - if (getBounds().width() <= 0 || getBounds().height() <= 0) { - return; - } + if (getBounds().width() <= 0 || getBounds().height() <= 0) { + return; + } - mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); - Canvas canvas = new Canvas(mBitmap); + mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + Canvas canvas = new Canvas(mBitmap); - Rect r = new Rect(); - boolean verticalStartWhite = true; - for (int i = 0; i <= numRectanglesVertical; i++) { + Rect r = new Rect(); + boolean verticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { - boolean isWhite = verticalStartWhite; - for (int j = 0; j <= numRectanglesHorizontal; j++) { + boolean isWhite = verticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { - r.top = i * mRectangleSize; - r.left = j * mRectangleSize; - r.bottom = r.top + mRectangleSize; - r.right = r.left + mRectangleSize; + r.top = i * mRectangleSize; + r.left = j * mRectangleSize; + r.bottom = r.top + mRectangleSize; + r.right = r.left + mRectangleSize; - canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray); + canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray); - isWhite = !isWhite; - } + isWhite = !isWhite; + } - verticalStartWhite = !verticalStartWhite; + verticalStartWhite = !verticalStartWhite; - } + } - } + } -} \ No newline at end of file +} diff --git a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerDialog.java b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerDialog.java index fddf6984..aa0e96fb 100644 --- a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerDialog.java +++ b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerDialog.java @@ -14,123 +14,240 @@ * limitations under the License. */ +// Taken from https://github.com/attenzione/android-ColorPickerPreference/tree/696bb050527d6a7ae14e47b7472a0640a7ff08e6 + package net.margaritov.preference.colorpicker; -import android.app.Dialog; import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.PixelFormat; import android.os.Bundle; +import android.text.InputFilter; +import android.text.InputType; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewTreeObserver; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.TextView; -import org.transdroid.R; - -public class ColorPickerDialog extends Dialog implements ColorPickerView.OnColorChangedListener, View.OnClickListener { - - private ColorPickerView mColorPicker; - - private ColorPickerPanelView mOldColor; - private ColorPickerPanelView mNewColor; - - private OnColorChangedListener mListener; - - public interface OnColorChangedListener { - public void onColorChanged(int color); - } - - public ColorPickerDialog(Context context, int initialColor) { - super(context); - - init(initialColor); - } - - private void init(int color) { - // To fight color banding. - getWindow().setFormat(PixelFormat.RGBA_8888); - - setUp(color); - - } +import androidx.appcompat.app.AppCompatDialog; - private void setUp(int color) { - - LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - View layout = inflater.inflate(R.layout.dialog_color_picker, null); - - setContentView(layout); - - setTitle(R.string.dialog_color_picker); - - mColorPicker = (ColorPickerView) layout.findViewById(R.id.color_picker_view); - mOldColor = (ColorPickerPanelView) layout.findViewById(R.id.old_color_panel); - mNewColor = (ColorPickerPanelView) layout.findViewById(R.id.new_color_panel); - - ((LinearLayout) mOldColor.getParent()) - .setPadding(Math.round(mColorPicker.getDrawingOffset()), 0, Math.round(mColorPicker.getDrawingOffset()), - 0); - - mOldColor.setOnClickListener(this); - mNewColor.setOnClickListener(this); - mColorPicker.setOnColorChangedListener(this); - mOldColor.setColor(color); - mColorPicker.setColor(color, true); - - } - - @Override - public void onColorChanged(int color) { +import org.transdroid.R; - mNewColor.setColor(color); +import java.util.Locale; + +public class ColorPickerDialog + extends + AppCompatDialog + implements + ColorPickerView.OnColorChangedListener, + View.OnClickListener, ViewTreeObserver.OnGlobalLayoutListener { + + private ColorPickerView mColorPicker; + + private ColorPickerPanelView mOldColor; + private ColorPickerPanelView mNewColor; + + private EditText mHexVal; + private boolean mHexValueEnabled = false; + private ColorStateList mHexDefaultTextColor; + + private OnColorChangedListener mListener; + private int mOrientation; + private View mLayout; + + private String mTitle; + + @Override + public void onGlobalLayout() { + if (getContext().getResources().getConfiguration().orientation != mOrientation) { + final int oldcolor = mOldColor.getColor(); + final int newcolor = mNewColor.getColor(); + mLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); + setUp(oldcolor); + mNewColor.setColor(newcolor); + mColorPicker.setColor(newcolor); + } + } + + public interface OnColorChangedListener { + public void onColorChanged(int color); + } + + public ColorPickerDialog(Context context, int initialColor, String title) { + super(context); + + mTitle = title; + init(initialColor); + } + + private void init(int color) { + // To fight color banding. + getWindow().setFormat(PixelFormat.RGBA_8888); + + setUp(color); + + } + + private void setUp(int color) { + + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + mLayout = inflater.inflate(R.layout.dialog_color_picker, null); + mLayout.getViewTreeObserver().addOnGlobalLayoutListener(this); + + mOrientation = getContext().getResources().getConfiguration().orientation; + setContentView(mLayout); + + setTitle(mTitle); + + mColorPicker = (ColorPickerView) mLayout.findViewById(R.id.color_picker_view); + mOldColor = (ColorPickerPanelView) mLayout.findViewById(R.id.old_color_panel); + mNewColor = (ColorPickerPanelView) mLayout.findViewById(R.id.new_color_panel); + + mHexVal = (EditText) mLayout.findViewById(R.id.hex_val); + mHexVal.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + mHexDefaultTextColor = mHexVal.getTextColors(); + + mHexVal.setOnEditorActionListener(new TextView.OnEditorActionListener() { + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + String s = mHexVal.getText().toString(); + if (s.length() > 5 || s.length() < 10) { + try { + int c = ColorPickerPreference.convertToColorInt(s.toString()); + mColorPicker.setColor(c, true); + mHexVal.setTextColor(mHexDefaultTextColor); + } catch (IllegalArgumentException e) { + mHexVal.setTextColor(Color.RED); + } + } else { + mHexVal.setTextColor(Color.RED); + } + return true; + } + return false; + } + }); + + ((LinearLayout) mOldColor.getParent()).setPadding( + Math.round(mColorPicker.getDrawingOffset()), + 0, + Math.round(mColorPicker.getDrawingOffset()), + 0 + ); + + mOldColor.setOnClickListener(this); + mNewColor.setOnClickListener(this); + mColorPicker.setOnColorChangedListener(this); + mOldColor.setColor(color); + mColorPicker.setColor(color, true); + + } + + @Override + public void onColorChanged(int color) { + + mNewColor.setColor(color); + + if (mHexValueEnabled) + updateHexValue(color); /* - if (mListener != null) { + if (mListener != null) { mListener.onColorChanged(color); } */ - } - - public void setAlphaSliderVisible(boolean visible) { - mColorPicker.setAlphaSliderVisible(visible); - } - - /** - * Set a OnColorChangedListener to get notified when the color - * selected by the user has changed. - * @param listener - */ - public void setOnColorChangedListener(OnColorChangedListener listener) { - mListener = listener; - } - - public int getColor() { - return mColorPicker.getColor(); - } - - @Override - public void onClick(View v) { - if (v.getId() == R.id.new_color_panel) { - if (mListener != null) { - mListener.onColorChanged(mNewColor.getColor()); - } - } - dismiss(); - } - - @Override - public Bundle onSaveInstanceState() { - Bundle state = super.onSaveInstanceState(); - state.putInt("old_color", mOldColor.getColor()); - state.putInt("new_color", mNewColor.getColor()); - return state; - } - - @Override - public void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mOldColor.setColor(savedInstanceState.getInt("old_color")); - mColorPicker.setColor(savedInstanceState.getInt("new_color"), true); - } + } + + public void setHexValueEnabled(boolean enable) { + mHexValueEnabled = enable; + if (enable) { + mHexVal.setVisibility(View.VISIBLE); + updateHexLengthFilter(); + updateHexValue(getColor()); + } else + mHexVal.setVisibility(View.GONE); + } + + public boolean getHexValueEnabled() { + return mHexValueEnabled; + } + + private void updateHexLengthFilter() { + if (getAlphaSliderVisible()) + mHexVal.setFilters(new InputFilter[]{new InputFilter.LengthFilter(9)}); + else + mHexVal.setFilters(new InputFilter[]{new InputFilter.LengthFilter(7)}); + } + + private void updateHexValue(int color) { + if (getAlphaSliderVisible()) { + mHexVal.setText(ColorPickerPreference.convertToARGB(color).toUpperCase(Locale.getDefault())); + } else { + mHexVal.setText(ColorPickerPreference.convertToRGB(color).toUpperCase(Locale.getDefault())); + } + mHexVal.setTextColor(mHexDefaultTextColor); + } + + public void setAlphaSliderVisible(boolean visible) { + mColorPicker.setAlphaSliderVisible(visible); + if (mHexValueEnabled) { + updateHexLengthFilter(); + updateHexValue(getColor()); + } + } + + public boolean getAlphaSliderVisible() { + return mColorPicker.getAlphaSliderVisible(); + } + + /** + * Set a OnColorChangedListener to get notified when the color + * selected by the user has changed. + * + * @param listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener) { + mListener = listener; + } + + public int getColor() { + return mColorPicker.getColor(); + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.new_color_panel) { + if (mListener != null) { + mListener.onColorChanged(mNewColor.getColor()); + } + } + dismiss(); + } + + @Override + public Bundle onSaveInstanceState() { + Bundle state = super.onSaveInstanceState(); + state.putInt("old_color", mOldColor.getColor()); + state.putInt("new_color", mNewColor.getColor()); + return state; + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mOldColor.setColor(savedInstanceState.getInt("old_color")); + mColorPicker.setColor(savedInstanceState.getInt("new_color"), true); + } } diff --git a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPanelView.java b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPanelView.java index 4b305793..60716e6d 100644 --- a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPanelView.java +++ b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPanelView.java @@ -14,6 +14,8 @@ * limitations under the License. */ +// Taken from https://github.com/attenzione/android-ColorPickerPreference/tree/696bb050527d6a7ae14e47b7472a0640a7ff08e6 + package net.margaritov.preference.colorpicker; import android.content.Context; @@ -27,140 +29,148 @@ import android.view.View; * This class draws a panel which which will be filled with a color which can be set. * It can be used to show the currently selected color which you will get from * the {@link ColorPickerView}. + * * @author Daniel Nilsson */ public class ColorPickerPanelView extends View { - /** - * The width in pixels of the border - * surrounding the color panel. - */ - private final static float BORDER_WIDTH_PX = 1; - - private float mDensity = 1f; - - private int mBorderColor = 0xff6E6E6E; - private int mColor = 0xff000000; + /** + * The width in pixels of the border + * surrounding the color panel. + */ + private final static float BORDER_WIDTH_PX = 1; - private Paint mBorderPaint; - private Paint mColorPaint; + private float mDensity = 1f; - private RectF mDrawingRect; - private RectF mColorRect; + private int mBorderColor = 0xff6E6E6E; + private int mColor = 0xff000000; - private AlphaPatternDrawable mAlphaPattern; + private Paint mBorderPaint; + private Paint mColorPaint; + private RectF mDrawingRect; + private RectF mColorRect; - public ColorPickerPanelView(Context context) { - this(context, null); - } + private AlphaPatternDrawable mAlphaPattern; - public ColorPickerPanelView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - public ColorPickerPanelView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } + public ColorPickerPanelView(Context context) { + this(context, null); + } - private void init() { - mBorderPaint = new Paint(); - mColorPaint = new Paint(); - mDensity = getContext().getResources().getDisplayMetrics().density; - } + public ColorPickerPanelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + public ColorPickerPanelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } - @Override - protected void onDraw(Canvas canvas) { + private void init() { + mBorderPaint = new Paint(); + mColorPaint = new Paint(); + mDensity = getContext().getResources().getDisplayMetrics().density; + } - final RectF rect = mColorRect; - if (BORDER_WIDTH_PX > 0) { - mBorderPaint.setColor(mBorderColor); - canvas.drawRect(mDrawingRect, mBorderPaint); - } + @Override + protected void onDraw(Canvas canvas) { - if (mAlphaPattern != null) { - mAlphaPattern.draw(canvas); - } + final RectF rect = mColorRect; - mColorPaint.setColor(mColor); + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect, mBorderPaint); + } - canvas.drawRect(rect, mColorPaint); - } + if (mAlphaPattern != null) { + mAlphaPattern.draw(canvas); + } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mColorPaint.setColor(mColor); - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); + canvas.drawRect(rect, mColorPaint); + } - setMeasuredDimension(width, height); - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); - mDrawingRect = new RectF(); - mDrawingRect.left = getPaddingLeft(); - mDrawingRect.right = w - getPaddingRight(); - mDrawingRect.top = getPaddingTop(); - mDrawingRect.bottom = h - getPaddingBottom(); + setMeasuredDimension(width, height); + } - setUpColorRect(); + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); - } + mDrawingRect = new RectF(); + mDrawingRect.left = getPaddingLeft(); + mDrawingRect.right = w - getPaddingRight(); + mDrawingRect.top = getPaddingTop(); + mDrawingRect.bottom = h - getPaddingBottom(); - private void setUpColorRect() { - final RectF dRect = mDrawingRect; + setUpColorRect(); - float left = dRect.left + BORDER_WIDTH_PX; - float top = dRect.top + BORDER_WIDTH_PX; - float bottom = dRect.bottom - BORDER_WIDTH_PX; - float right = dRect.right - BORDER_WIDTH_PX; + } - mColorRect = new RectF(left, top, right, bottom); + private void setUpColorRect() { + final RectF dRect = mDrawingRect; - mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; - mAlphaPattern.setBounds(Math.round(mColorRect.left), Math.round(mColorRect.top), Math.round(mColorRect.right), - Math.round(mColorRect.bottom)); + mColorRect = new RectF(left, top, right, bottom); - } + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); - /** - * Set the color that should be shown by this view. - * @param color - */ - public void setColor(int color) { - mColor = color; - invalidate(); - } + mAlphaPattern.setBounds( + Math.round(mColorRect.left), + Math.round(mColorRect.top), + Math.round(mColorRect.right), + Math.round(mColorRect.bottom) + ); - /** - * Get the color currently show by this view. - * @return - */ - public int getColor() { - return mColor; - } + } - /** - * Set the color of the border surrounding the panel. - * @param color - */ - public void setBorderColor(int color) { - mBorderColor = color; - invalidate(); - } + /** + * Set the color that should be shown by this view. + * + * @param color + */ + public void setColor(int color) { + mColor = color; + invalidate(); + } - /** - * Get the color of the border surrounding the panel. - */ - public int getBorderColor() { - return mBorderColor; - } + /** + * Get the color currently show by this view. + * + * @return + */ + public int getColor() { + return mColor; + } + + /** + * Set the color of the border surrounding the panel. + * + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding the panel. + */ + public int getBorderColor() { + return mBorderColor; + } -} \ No newline at end of file +} diff --git a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPreference.java b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPreference.java index da9a50fe..ca211059 100644 --- a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPreference.java +++ b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerPreference.java @@ -14,6 +14,8 @@ * limitations under the License. */ +// Taken from https://github.com/attenzione/android-ColorPickerPreference/tree/696bb050527d6a7ae14e47b7472a0640a7ff08e6 + package net.margaritov.preference.colorpicker; import android.content.Context; @@ -24,259 +26,312 @@ import android.graphics.Color; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.preference.Preference; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + /** - * A preference type that allows a user to choose a time + * A preference type that allows a user to choose a color + * * @author Sergey Margaritov */ -public class ColorPickerPreference extends Preference - implements Preference.OnPreferenceClickListener, ColorPickerDialog.OnColorChangedListener { - - View mView; - ColorPickerDialog mDialog; - private int mValue = Color.BLACK; - private float mDensity = 0; - private boolean mAlphaSliderEnabled = false; - - public ColorPickerPreference(Context context) { - super(context); - init(context, null); - } - - public ColorPickerPreference(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); - } - - public ColorPickerPreference(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context, attrs); - } - - @Override - protected Object onGetDefaultValue(TypedArray a, int index) { - return a.getColor(index, Color.BLACK); - } - - @Override - protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { - onColorChanged(restoreValue ? getPersistedInt(mValue) : (Integer) defaultValue); - } - - private void init(Context context, AttributeSet attrs) { - mDensity = getContext().getResources().getDisplayMetrics().density; - setOnPreferenceClickListener(this); - if (attrs != null) { - mAlphaSliderEnabled = attrs.getAttributeBooleanValue(null, "alphaSlider", false); - } - } - - @Override - protected void onBindView(View view) { - super.onBindView(view); - mView = view; - setPreviewColor(); - } - - private void setPreviewColor() { - if (mView == null) { - return; - } - ImageView iView = new ImageView(getContext()); - LinearLayout widgetFrameView = ((LinearLayout) mView.findViewById(android.R.id.widget_frame)); - if (widgetFrameView == null) { - return; - } - widgetFrameView.setVisibility(View.VISIBLE); - widgetFrameView - .setPadding(widgetFrameView.getPaddingLeft(), widgetFrameView.getPaddingTop(), (int) (mDensity * 8), - widgetFrameView.getPaddingBottom()); - // remove already create preview image - int count = widgetFrameView.getChildCount(); - if (count > 0) { - widgetFrameView.removeViews(0, count); - } - widgetFrameView.addView(iView); - widgetFrameView.setMinimumWidth(0); - iView.setBackgroundDrawable(new AlphaPatternDrawable((int) (5 * mDensity))); - iView.setImageBitmap(getPreviewBitmap()); - } - - private Bitmap getPreviewBitmap() { - int d = (int) (mDensity * 31); //30dip - int color = mValue; - Bitmap bm = Bitmap.createBitmap(d, d, Config.ARGB_8888); - int w = bm.getWidth(); - int h = bm.getHeight(); - int c = color; - for (int i = 0; i < w; i++) { - for (int j = i; j < h; j++) { - c = (i <= 1 || j <= 1 || i >= w - 2 || j >= h - 2) ? Color.GRAY : color; - bm.setPixel(i, j, c); - if (i != j) { - bm.setPixel(j, i, c); - } - } - } - - return bm; - } - - @Override - public void onColorChanged(int color) { - if (isPersistent()) { - persistInt(color); - } - mValue = color; - setPreviewColor(); - try { - getOnPreferenceChangeListener().onPreferenceChange(this, color); - } catch (NullPointerException e) { - - } - } - - public boolean onPreferenceClick(Preference preference) { - showDialog(null); - return false; - } - - protected void showDialog(Bundle state) { - mDialog = new ColorPickerDialog(getContext(), mValue); - mDialog.setOnColorChangedListener(this); - if (mAlphaSliderEnabled) { - mDialog.setAlphaSliderVisible(true); - } - if (state != null) { - mDialog.onRestoreInstanceState(state); - } - mDialog.show(); - } - - /** - * Toggle Alpha Slider visibility (by default it's disabled) - * @param enable - */ - public void setAlphaSliderEnabled(boolean enable) { - mAlphaSliderEnabled = enable; - } - - /** - * For custom purposes. Not used by ColorPickerPreferrence - * @param color - * @author Unknown - */ - public static String convertToARGB(int color) { - String alpha = Integer.toHexString(Color.alpha(color)); - String red = Integer.toHexString(Color.red(color)); - String green = Integer.toHexString(Color.green(color)); - String blue = Integer.toHexString(Color.blue(color)); - - if (alpha.length() == 1) { - alpha = "0" + alpha; - } - - if (red.length() == 1) { - red = "0" + red; - } - - if (green.length() == 1) { - green = "0" + green; - } - - if (blue.length() == 1) { - blue = "0" + blue; - } - - return "#" + alpha + red + green + blue; - } - - /** - * For custom purposes. Not used by ColorPickerPreferrence - * @param argb - * @throws NumberFormatException - * @author Unknown - */ - public static int convertToColorInt(String argb) throws NumberFormatException { - - if (argb.startsWith("#")) { - argb = argb.replace("#", ""); - } - - int alpha = -1, red = -1, green = -1, blue = -1; - - if (argb.length() == 8) { - alpha = Integer.parseInt(argb.substring(0, 2), 16); - red = Integer.parseInt(argb.substring(2, 4), 16); - green = Integer.parseInt(argb.substring(4, 6), 16); - blue = Integer.parseInt(argb.substring(6, 8), 16); - } else if (argb.length() == 6) { - alpha = 255; - red = Integer.parseInt(argb.substring(0, 2), 16); - green = Integer.parseInt(argb.substring(2, 4), 16); - blue = Integer.parseInt(argb.substring(4, 6), 16); - } - - return Color.argb(alpha, red, green, blue); - } - - @Override - protected Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - if (mDialog == null || !mDialog.isShowing()) { - return superState; - } - - final SavedState myState = new SavedState(superState); - myState.dialogBundle = mDialog.onSaveInstanceState(); - return myState; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (state == null || !(state instanceof SavedState)) { - // Didn't save state for us in onSaveInstanceState - super.onRestoreInstanceState(state); - return; - } - - SavedState myState = (SavedState) state; - super.onRestoreInstanceState(myState.getSuperState()); - showDialog(myState.dialogBundle); - } - - private static class SavedState extends BaseSavedState { - Bundle dialogBundle; - - public SavedState(Parcel source) { - super(source); - dialogBundle = source.readBundle(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeBundle(dialogBundle); - } - - public SavedState(Parcelable superState) { - super(superState); - } - - @SuppressWarnings("unused") - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } -} \ No newline at end of file +public class ColorPickerPreference + extends + Preference + implements + Preference.OnPreferenceClickListener, + ColorPickerDialog.OnColorChangedListener { + + View mView; + ColorPickerDialog mDialog; + private int mValue = Color.BLACK; + private float mDensity = 0; + private boolean mAlphaSliderEnabled = false; + private boolean mHexValueEnabled = false; + + public ColorPickerPreference(Context context) { + super(context); + init(context, null); + } + + public ColorPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public ColorPickerPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + /**Method edited by + * @author Anna Berkovitch + * added functionality to accept hex string as defaultValue + * and to properly persist resources reference string, such as @color/someColor + * previously persisted 0*/ + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + int colorInt; + String mHexDefaultValue = a.getString(index); + if (mHexDefaultValue != null && mHexDefaultValue.startsWith("#")) { + colorInt = convertToColorInt(mHexDefaultValue); + return colorInt; + + } else { + return a.getColor(index, Color.BLACK); + } + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + onColorChanged(restoreValue ? getPersistedInt(mValue) : (Integer) defaultValue); + } + + private void init(Context context, AttributeSet attrs) { + mDensity = getContext().getResources().getDisplayMetrics().density; + setOnPreferenceClickListener(this); + if (attrs != null) { + mAlphaSliderEnabled = attrs.getAttributeBooleanValue(null, "alphaSlider", false); + mHexValueEnabled = attrs.getAttributeBooleanValue(null, "hexValue", false); + } + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + mView = holder.itemView; + setPreviewColor(); + } + + private void setPreviewColor() { + if (mView == null) return; + ImageView iView = new ImageView(getContext()); + LinearLayout widgetFrameView = ((LinearLayout) mView.findViewById(android.R.id.widget_frame)); + if (widgetFrameView == null) return; + widgetFrameView.setVisibility(View.VISIBLE); + widgetFrameView.setPadding( + widgetFrameView.getPaddingLeft(), + widgetFrameView.getPaddingTop(), + (int) (mDensity * 8), + widgetFrameView.getPaddingBottom() + ); + // remove already create preview image + int count = widgetFrameView.getChildCount(); + if (count > 0) { + widgetFrameView.removeViews(0, count); + } + widgetFrameView.addView(iView); + widgetFrameView.setMinimumWidth(0); + iView.setBackgroundDrawable(new AlphaPatternDrawable((int) (5 * mDensity))); + iView.setImageBitmap(getPreviewBitmap()); + } + + private Bitmap getPreviewBitmap() { + int d = (int) (mDensity * 31); //30dip + int color = mValue; + Bitmap bm = Bitmap.createBitmap(d, d, Config.ARGB_8888); + int w = bm.getWidth(); + int h = bm.getHeight(); + int c = color; + for (int i = 0; i < w; i++) { + for (int j = i; j < h; j++) { + c = (i <= 1 || j <= 1 || i >= w - 2 || j >= h - 2) ? Color.GRAY : color; + bm.setPixel(i, j, c); + if (i != j) { + bm.setPixel(j, i, c); + } + } + } + + return bm; + } + + @Override + public void onColorChanged(int color) { + if (isPersistent()) { + persistInt(color); + } + mValue = color; + setPreviewColor(); + try { + getOnPreferenceChangeListener().onPreferenceChange(this, color); + } catch (NullPointerException e) { + + } + } + + public boolean onPreferenceClick(Preference preference) { + showDialog(null); + return false; + } + + protected void showDialog(Bundle state) { + mDialog = new ColorPickerDialog(getContext(), mValue, getTitle().toString()); + mDialog.setOnColorChangedListener(this); + if (mAlphaSliderEnabled) { + mDialog.setAlphaSliderVisible(true); + } + if (mHexValueEnabled) { + mDialog.setHexValueEnabled(true); + } + if (state != null) { + mDialog.onRestoreInstanceState(state); + } + mDialog.show(); + } + + /** + * Toggle Alpha Slider visibility (by default it's disabled) + * + * @param enable + */ + public void setAlphaSliderEnabled(boolean enable) { + mAlphaSliderEnabled = enable; + } + + /** + * Toggle Hex Value visibility (by default it's disabled) + * + * @param enable + */ + public void setHexValueEnabled(boolean enable) { + mHexValueEnabled = enable; + } + + /** + * For custom purposes. Not used by ColorPickerPreferrence + * + * @param color + * @author Unknown + */ + public static String convertToARGB(int color) { + String alpha = Integer.toHexString(Color.alpha(color)); + String red = Integer.toHexString(Color.red(color)); + String green = Integer.toHexString(Color.green(color)); + String blue = Integer.toHexString(Color.blue(color)); + + if (alpha.length() == 1) { + alpha = "0" + alpha; + } + + if (red.length() == 1) { + red = "0" + red; + } + + if (green.length() == 1) { + green = "0" + green; + } + + if (blue.length() == 1) { + blue = "0" + blue; + } + + return "#" + alpha + red + green + blue; + } + + /** + * Method currently used by onGetDefaultValue method to + * convert hex string provided in android:defaultValue to color integer. + * + * @param color + * @return A string representing the hex value of color, + * without the alpha value + * @author Charles Rosaaen + */ + public static String convertToRGB(int color) { + String red = Integer.toHexString(Color.red(color)); + String green = Integer.toHexString(Color.green(color)); + String blue = Integer.toHexString(Color.blue(color)); + + if (red.length() == 1) { + red = "0" + red; + } + + if (green.length() == 1) { + green = "0" + green; + } + + if (blue.length() == 1) { + blue = "0" + blue; + } + + return "#" + red + green + blue; + } + + /** + * For custom purposes. Not used by ColorPickerPreferrence + * + * @param argb + * @throws NumberFormatException + * @author Unknown + */ + public static int convertToColorInt(String argb) throws IllegalArgumentException { + + if (!argb.startsWith("#")) { + argb = "#" + argb; + } + + return Color.parseColor(argb); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (mDialog == null || !mDialog.isShowing()) { + return superState; + } + + final SavedState myState = new SavedState(superState); + myState.dialogBundle = mDialog.onSaveInstanceState(); + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !(state instanceof SavedState)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + showDialog(myState.dialogBundle); + } + + private static class SavedState extends BaseSavedState { + Bundle dialogBundle; + + public SavedState(Parcel source) { + super(source); + dialogBundle = source.readBundle(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeBundle(dialogBundle); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerView.java b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerView.java index 48d18d61..f2824cfc 100644 --- a/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerView.java +++ b/app/src/main/java/net/margaritov/preference/colorpicker/ColorPickerView.java @@ -14,6 +14,8 @@ * limitations under the License. */ +// Taken from https://github.com/attenzione/android-ColorPickerPreference/tree/696bb050527d6a7ae14e47b7472a0640a7ff08e6 + package net.margaritov.preference.colorpicker; import android.content.Context; @@ -38,895 +40,908 @@ import android.view.View; * to select a color. A slider for the alpha channel is * also available. Enable it by setting * setAlphaSliderVisible(boolean) to true. + * * @author Daniel Nilsson */ public class ColorPickerView extends View { - private final static int PANEL_SAT_VAL = 0; - private final static int PANEL_HUE = 1; - private final static int PANEL_ALPHA = 2; - - /** - * The width in pixels of the border - * surrounding all color panels. - */ - private final static float BORDER_WIDTH_PX = 1; - - /** - * The width in dp of the hue panel. - */ - private float HUE_PANEL_WIDTH = 30f; - /** - * The height in dp of the alpha panel - */ - private float ALPHA_PANEL_HEIGHT = 20f; - /** - * The distance in dp between the different - * color panels. - */ - private float PANEL_SPACING = 10f; - /** - * The radius in dp of the color palette tracker circle. - */ - private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; - /** - * The dp which the tracker of the hue or alpha panel - * will extend outside of its bounds. - */ - private float RECTANGLE_TRACKER_OFFSET = 2f; - - - private float mDensity = 1f; - - private OnColorChangedListener mListener; - - private Paint mSatValPaint; - private Paint mSatValTrackerPaint; - - private Paint mHuePaint; - private Paint mHueTrackerPaint; - - private Paint mAlphaPaint; - private Paint mAlphaTextPaint; - - private Paint mBorderPaint; - - private Shader mValShader; - private Shader mSatShader; - private Shader mHueShader; - private Shader mAlphaShader; - - private int mAlpha = 0xff; - private float mHue = 360f; - private float mSat = 0f; - private float mVal = 0f; - - private String mAlphaSliderText = ""; - private int mSliderTrackerColor = 0xff1c1c1c; - private int mBorderColor = 0xff6E6E6E; - private boolean mShowAlphaPanel = false; - - /* - * To remember which panel that has the "focus" when - * processing hardware button data. - */ - private int mLastTouchedPanel = PANEL_SAT_VAL; - - /** - * Offset from the edge we must have or else - * the finger tracker will get clipped when - * it is drawn outside of the view. - */ - private float mDrawingOffset; - - - /* - * Distance form the edges of the view - * of where we are allowed to draw. - */ - private RectF mDrawingRect; - - private RectF mSatValRect; - private RectF mHueRect; - private RectF mAlphaRect; - - private AlphaPatternDrawable mAlphaPattern; - - private Point mStartTouchPoint = null; + private final static int PANEL_SAT_VAL = 0; + private final static int PANEL_HUE = 1; + private final static int PANEL_ALPHA = 2; + + /** + * The width in pixels of the border + * surrounding all color panels. + */ + private final static float BORDER_WIDTH_PX = 1; + + /** + * The width in dp of the hue panel. + */ + private float HUE_PANEL_WIDTH = 30f; + /** + * The height in dp of the alpha panel + */ + private float ALPHA_PANEL_HEIGHT = 20f; + /** + * The distance in dp between the different + * color panels. + */ + private float PANEL_SPACING = 10f; + /** + * The radius in dp of the color palette tracker circle. + */ + private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; + /** + * The dp which the tracker of the hue or alpha panel + * will extend outside of its bounds. + */ + private float RECTANGLE_TRACKER_OFFSET = 2f; + + + private float mDensity = 1f; + + private OnColorChangedListener mListener; + + private Paint mSatValPaint; + private Paint mSatValTrackerPaint; + + private Paint mHuePaint; + private Paint mHueTrackerPaint; + + private Paint mAlphaPaint; + private Paint mAlphaTextPaint; + + private Paint mBorderPaint; + + private Shader mValShader; + private Shader mSatShader; + private Shader mHueShader; + private Shader mAlphaShader; + + private int mAlpha = 0xff; + private float mHue = 360f; + private float mSat = 0f; + private float mVal = 0f; + + private String mAlphaSliderText = ""; + private int mSliderTrackerColor = 0xff1c1c1c; + private int mBorderColor = 0xff6E6E6E; + private boolean mShowAlphaPanel = false; + + /* + * To remember which panel that has the "focus" when + * processing hardware button data. + */ + private int mLastTouchedPanel = PANEL_SAT_VAL; + + /** + * Offset from the edge we must have or else + * the finger tracker will get clipped when + * it is drawn outside of the view. + */ + private float mDrawingOffset; - public interface OnColorChangedListener { - public void onColorChanged(int color); - } - public ColorPickerView(Context context) { - this(context, null); - } + /* + * Distance form the edges of the view + * of where we are allowed to draw. + */ + private RectF mDrawingRect; - public ColorPickerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + private RectF mSatValRect; + private RectF mHueRect; + private RectF mAlphaRect; - public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } + private AlphaPatternDrawable mAlphaPattern; - private void init() { - mDensity = getContext().getResources().getDisplayMetrics().density; - PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; - RECTANGLE_TRACKER_OFFSET *= mDensity; - HUE_PANEL_WIDTH *= mDensity; - ALPHA_PANEL_HEIGHT *= mDensity; - PANEL_SPACING = PANEL_SPACING * mDensity; + private Point mStartTouchPoint = null; - mDrawingOffset = calculateRequiredOffset(); + public interface OnColorChangedListener { + public void onColorChanged(int color); + } - initPaintTools(); + public ColorPickerView(Context context) { + this(context, null); + } - //Needed for receiving trackball motion events. - setFocusable(true); - setFocusableInTouchMode(true); - } + public ColorPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } - private void initPaintTools() { + public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } - mSatValPaint = new Paint(); - mSatValTrackerPaint = new Paint(); - mHuePaint = new Paint(); - mHueTrackerPaint = new Paint(); - mAlphaPaint = new Paint(); - mAlphaTextPaint = new Paint(); - mBorderPaint = new Paint(); + private void init() { + mDensity = getContext().getResources().getDisplayMetrics().density; + PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; + RECTANGLE_TRACKER_OFFSET *= mDensity; + HUE_PANEL_WIDTH *= mDensity; + ALPHA_PANEL_HEIGHT *= mDensity; + PANEL_SPACING = PANEL_SPACING * mDensity; + mDrawingOffset = calculateRequiredOffset(); - mSatValTrackerPaint.setStyle(Style.STROKE); - mSatValTrackerPaint.setStrokeWidth(2f * mDensity); - mSatValTrackerPaint.setAntiAlias(true); + initPaintTools(); - mHueTrackerPaint.setColor(mSliderTrackerColor); - mHueTrackerPaint.setStyle(Style.STROKE); - mHueTrackerPaint.setStrokeWidth(2f * mDensity); - mHueTrackerPaint.setAntiAlias(true); + //Needed for receiving trackball motion events. + setFocusable(true); + setFocusableInTouchMode(true); + } - mAlphaTextPaint.setColor(0xff1c1c1c); - mAlphaTextPaint.setTextSize(14f * mDensity); - mAlphaTextPaint.setAntiAlias(true); - mAlphaTextPaint.setTextAlign(Align.CENTER); - mAlphaTextPaint.setFakeBoldText(true); + private void initPaintTools() { + mSatValPaint = new Paint(); + mSatValTrackerPaint = new Paint(); + mHuePaint = new Paint(); + mHueTrackerPaint = new Paint(); + mAlphaPaint = new Paint(); + mAlphaTextPaint = new Paint(); + mBorderPaint = new Paint(); - } - private float calculateRequiredOffset() { - float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); - offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + mSatValTrackerPaint.setStyle(Style.STROKE); + mSatValTrackerPaint.setStrokeWidth(2f * mDensity); + mSatValTrackerPaint.setAntiAlias(true); - return offset * 1.5f; - } + mHueTrackerPaint.setColor(mSliderTrackerColor); + mHueTrackerPaint.setStyle(Style.STROKE); + mHueTrackerPaint.setStrokeWidth(2f * mDensity); + mHueTrackerPaint.setAntiAlias(true); - private int[] buildHueColorArray() { + mAlphaTextPaint.setColor(0xff1c1c1c); + mAlphaTextPaint.setTextSize(14f * mDensity); + mAlphaTextPaint.setAntiAlias(true); + mAlphaTextPaint.setTextAlign(Align.CENTER); + mAlphaTextPaint.setFakeBoldText(true); - int[] hue = new int[361]; - int count = 0; - for (int i = hue.length - 1; i >= 0; i--, count++) { - hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f}); - } + } - return hue; - } + private float calculateRequiredOffset() { + float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); + offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + return offset * 1.5f; + } - @Override - protected void onDraw(Canvas canvas) { + private int[] buildHueColorArray() { - if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) { - return; - } + int[] hue = new int[361]; - drawSatValPanel(canvas); - drawHuePanel(canvas); - drawAlphaPanel(canvas); + int count = 0; + for (int i = hue.length - 1; i >= 0; i--, count++) { + hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f}); + } - } + return hue; + } - private void drawSatValPanel(Canvas canvas) { - final RectF rect = mSatValRect; + @Override + protected void onDraw(Canvas canvas) { - if (BORDER_WIDTH_PX > 0) { - mBorderPaint.setColor(mBorderColor); - canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, - rect.bottom + BORDER_WIDTH_PX, mBorderPaint); - } + if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) return; - if (mValShader == null) { - mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, 0xffffffff, 0xff000000, - TileMode.CLAMP); - } + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); - int rgb = Color.HSVToColor(new float[]{mHue, 1f, 1f}); + } - mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 0xffffffff, rgb, TileMode.CLAMP); - ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY); - mSatValPaint.setShader(mShader); + private void drawSatValPanel(Canvas canvas) { - canvas.drawRect(rect, mSatValPaint); + final RectF rect = mSatValRect; - Point p = satValToPoint(mSat, mVal); + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } - mSatValTrackerPaint.setColor(0xff000000); - canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint); + if (mValShader == null) { + mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + 0xffffffff, 0xff000000, TileMode.CLAMP); + } - mSatValTrackerPaint.setColor(0xffdddddd); - canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); + int rgb = Color.HSVToColor(new float[]{mHue, 1f, 1f}); - } + mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + 0xffffffff, rgb, TileMode.CLAMP); + ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY); + mSatValPaint.setShader(mShader); - private void drawHuePanel(Canvas canvas) { + canvas.drawRect(rect, mSatValPaint); - final RectF rect = mHueRect; + Point p = satValToPoint(mSat, mVal); - if (BORDER_WIDTH_PX > 0) { - mBorderPaint.setColor(mBorderColor); - canvas.drawRect(rect.left - BORDER_WIDTH_PX, rect.top - BORDER_WIDTH_PX, rect.right + BORDER_WIDTH_PX, - rect.bottom + BORDER_WIDTH_PX, mBorderPaint); - } + mSatValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint); - if (mHueShader == null) { - mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, buildHueColorArray(), null, - TileMode.CLAMP); - mHuePaint.setShader(mHueShader); - } + mSatValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); - canvas.drawRect(rect, mHuePaint); + } - float rectHeight = 4 * mDensity / 2; + private void drawHuePanel(Canvas canvas) { - Point p = hueToPoint(mHue); + final RectF rect = mHueRect; - RectF r = new RectF(); - r.left = rect.left - RECTANGLE_TRACKER_OFFSET; - r.right = rect.right + RECTANGLE_TRACKER_OFFSET; - r.top = p.y - rectHeight; - r.bottom = p.y + rectHeight; + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + if (mHueShader == null) { + mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, buildHueColorArray(), null, TileMode.CLAMP); + mHuePaint.setShader(mHueShader); + } - canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + canvas.drawRect(rect, mHuePaint); - } + float rectHeight = 4 * mDensity / 2; - private void drawAlphaPanel(Canvas canvas) { + Point p = hueToPoint(mHue); - if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) { - return; - } + RectF r = new RectF(); + r.left = rect.left - RECTANGLE_TRACKER_OFFSET; + r.right = rect.right + RECTANGLE_TRACKER_OFFSET; + r.top = p.y - rectHeight; + r.bottom = p.y + rectHeight; - final RectF rect = mAlphaRect; - if (BORDER_WIDTH_PX > 0) { - mBorderPaint.setColor(mBorderColor); - canvas.drawRect(rect.left - BORDER_WIDTH_PX, rect.top - BORDER_WIDTH_PX, rect.right + BORDER_WIDTH_PX, - rect.bottom + BORDER_WIDTH_PX, mBorderPaint); - } + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + } - mAlphaPattern.draw(canvas); + private void drawAlphaPanel(Canvas canvas) { - float[] hsv = new float[]{mHue, mSat, mVal}; - int color = Color.HSVToColor(hsv); - int acolor = Color.HSVToColor(0, hsv); + if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return; - mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, color, acolor, TileMode.CLAMP); + final RectF rect = mAlphaRect; + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } - mAlphaPaint.setShader(mAlphaShader); - canvas.drawRect(rect, mAlphaPaint); + mAlphaPattern.draw(canvas); - if (mAlphaSliderText != null && mAlphaSliderText != "") { - canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint); - } + float[] hsv = new float[]{mHue, mSat, mVal}; + int color = Color.HSVToColor(hsv); + int acolor = Color.HSVToColor(0, hsv); - float rectWidth = 4 * mDensity / 2; + mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + color, acolor, TileMode.CLAMP); - Point p = alphaToPoint(mAlpha); - RectF r = new RectF(); - r.left = p.x - rectWidth; - r.right = p.x + rectWidth; - r.top = rect.top - RECTANGLE_TRACKER_OFFSET; - r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; + mAlphaPaint.setShader(mAlphaShader); - canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + canvas.drawRect(rect, mAlphaPaint); - } + if (mAlphaSliderText != null && mAlphaSliderText != "") { + canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint); + } + float rectWidth = 4 * mDensity / 2; - private Point hueToPoint(float hue) { + Point p = alphaToPoint(mAlpha); - final RectF rect = mHueRect; - final float height = rect.height(); + RectF r = new RectF(); + r.left = p.x - rectWidth; + r.right = p.x + rectWidth; + r.top = rect.top - RECTANGLE_TRACKER_OFFSET; + r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; - Point p = new Point(); + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); - p.y = (int) (height - (hue * height / 360f) + rect.top); - p.x = (int) rect.left; + } - return p; - } - private Point satValToPoint(float sat, float val) { + private Point hueToPoint(float hue) { - final RectF rect = mSatValRect; - final float height = rect.height(); - final float width = rect.width(); + final RectF rect = mHueRect; + final float height = rect.height(); - Point p = new Point(); + Point p = new Point(); - p.x = (int) (sat * width + rect.left); - p.y = (int) ((1f - val) * height + rect.top); + p.y = (int) (height - (hue * height / 360f) + rect.top); + p.x = (int) rect.left; - return p; - } + return p; + } - private Point alphaToPoint(int alpha) { + private Point satValToPoint(float sat, float val) { - final RectF rect = mAlphaRect; - final float width = rect.width(); + final RectF rect = mSatValRect; + final float height = rect.height(); + final float width = rect.width(); - Point p = new Point(); + Point p = new Point(); - p.x = (int) (width - (alpha * width / 0xff) + rect.left); - p.y = (int) rect.top; + p.x = (int) (sat * width + rect.left); + p.y = (int) ((1f - val) * height + rect.top); - return p; + return p; + } - } + private Point alphaToPoint(int alpha) { - private float[] pointToSatVal(float x, float y) { + final RectF rect = mAlphaRect; + final float width = rect.width(); - final RectF rect = mSatValRect; - float[] result = new float[2]; + Point p = new Point(); - float width = rect.width(); - float height = rect.height(); + p.x = (int) (width - (alpha * width / 0xff) + rect.left); + p.y = (int) rect.top; - if (x < rect.left) { - x = 0f; - } else if (x > rect.right) { - x = width; - } else { - x = x - rect.left; - } + return p; - if (y < rect.top) { - y = 0f; - } else if (y > rect.bottom) { - y = height; - } else { - y = y - rect.top; - } + } + private float[] pointToSatVal(float x, float y) { - result[0] = 1.f / width * x; - result[1] = 1.f - (1.f / height * y); + final RectF rect = mSatValRect; + float[] result = new float[2]; - return result; - } + float width = rect.width(); + float height = rect.height(); - private float pointToHue(float y) { + if (x < rect.left) { + x = 0f; + } else if (x > rect.right) { + x = width; + } else { + x = x - rect.left; + } - final RectF rect = mHueRect; + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } - float height = rect.height(); - if (y < rect.top) { - y = 0f; - } else if (y > rect.bottom) { - y = height; - } else { - y = y - rect.top; - } + result[0] = 1.f / width * x; + result[1] = 1.f - (1.f / height * y); - return 360f - (y * 360f / height); - } + return result; + } - private int pointToAlpha(int x) { + private float pointToHue(float y) { - final RectF rect = mAlphaRect; - final int width = (int) rect.width(); + final RectF rect = mHueRect; - if (x < rect.left) { - x = 0; - } else if (x > rect.right) { - x = width; - } else { - x = x - (int) rect.left; - } + float height = rect.height(); - return 0xff - (x * 0xff / width); + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } - } + return 360f - (y * 360f / height); + } + private int pointToAlpha(int x) { - @Override - public boolean onTrackballEvent(MotionEvent event) { + final RectF rect = mAlphaRect; + final int width = (int) rect.width(); - float x = event.getX(); - float y = event.getY(); + if (x < rect.left) { + x = 0; + } else if (x > rect.right) { + x = width; + } else { + x = x - (int) rect.left; + } - boolean update = false; + return 0xff - (x * 0xff / width); + } - if (event.getAction() == MotionEvent.ACTION_MOVE) { - switch (mLastTouchedPanel) { + @Override + public boolean onTrackballEvent(MotionEvent event) { - case PANEL_SAT_VAL: + float x = event.getX(); + float y = event.getY(); - float sat, val; + boolean update = false; - sat = mSat + x / 50f; - val = mVal - y / 50f; - if (sat < 0f) { - sat = 0f; - } else if (sat > 1f) { - sat = 1f; - } + if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (val < 0f) { - val = 0f; - } else if (val > 1f) { - val = 1f; - } + switch (mLastTouchedPanel) { - mSat = sat; - mVal = val; + case PANEL_SAT_VAL: - update = true; + float sat, val; - break; + sat = mSat + x / 50f; + val = mVal - y / 50f; - case PANEL_HUE: + if (sat < 0f) { + sat = 0f; + } else if (sat > 1f) { + sat = 1f; + } - float hue = mHue - y * 10f; + if (val < 0f) { + val = 0f; + } else if (val > 1f) { + val = 1f; + } - if (hue < 0f) { - hue = 0f; - } else if (hue > 360f) { - hue = 360f; - } + mSat = sat; + mVal = val; - mHue = hue; + update = true; - update = true; + break; - break; + case PANEL_HUE: - case PANEL_ALPHA: + float hue = mHue - y * 10f; - if (!mShowAlphaPanel || mAlphaRect == null) { - update = false; - } else { + if (hue < 0f) { + hue = 0f; + } else if (hue > 360f) { + hue = 360f; + } - int alpha = (int) (mAlpha - x * 10); + mHue = hue; - if (alpha < 0) { - alpha = 0; - } else if (alpha > 0xff) { - alpha = 0xff; - } + update = true; - mAlpha = alpha; + break; + case PANEL_ALPHA: - update = true; - } + if (!mShowAlphaPanel || mAlphaRect == null) { + update = false; + } else { - break; - } + int alpha = (int) (mAlpha - x * 10); + if (alpha < 0) { + alpha = 0; + } else if (alpha > 0xff) { + alpha = 0xff; + } - } + mAlpha = alpha; - if (update) { + update = true; + } - if (mListener != null) { - mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); - } + break; + } - invalidate(); - return true; - } + } - return super.onTrackballEvent(event); - } - @Override - public boolean onTouchEvent(MotionEvent event) { + if (update) { - boolean update = false; + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } - switch (event.getAction()) { + invalidate(); + return true; + } - case MotionEvent.ACTION_DOWN: - mStartTouchPoint = new Point((int) event.getX(), (int) event.getY()); + return super.onTrackballEvent(event); + } - update = moveTrackersIfNeeded(event); + @Override + public boolean onTouchEvent(MotionEvent event) { - break; + boolean update = false; - case MotionEvent.ACTION_MOVE: + switch (event.getAction()) { - update = moveTrackersIfNeeded(event); + case MotionEvent.ACTION_DOWN: - break; + mStartTouchPoint = new Point((int) event.getX(), (int) event.getY()); - case MotionEvent.ACTION_UP: + update = moveTrackersIfNeeded(event); - mStartTouchPoint = null; + break; - update = moveTrackersIfNeeded(event); + case MotionEvent.ACTION_MOVE: - break; + update = moveTrackersIfNeeded(event); - } + break; - if (update) { + case MotionEvent.ACTION_UP: - if (mListener != null) { - mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); - } + mStartTouchPoint = null; - invalidate(); - return true; - } + update = moveTrackersIfNeeded(event); + break; - return super.onTouchEvent(event); - } + } - private boolean moveTrackersIfNeeded(MotionEvent event) { + if (update) { - if (mStartTouchPoint == null) { - return false; - } + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } - boolean update = false; + invalidate(); + return true; + } - int startX = mStartTouchPoint.x; - int startY = mStartTouchPoint.y; + return super.onTouchEvent(event); + } - if (mHueRect.contains(startX, startY)) { - mLastTouchedPanel = PANEL_HUE; + private boolean moveTrackersIfNeeded(MotionEvent event) { - mHue = pointToHue(event.getY()); + if (mStartTouchPoint == null) return false; - update = true; - } else if (mSatValRect.contains(startX, startY)) { + boolean update = false; - mLastTouchedPanel = PANEL_SAT_VAL; + int startX = mStartTouchPoint.x; + int startY = mStartTouchPoint.y; - float[] result = pointToSatVal(event.getX(), event.getY()); - mSat = result[0]; - mVal = result[1]; + if (mHueRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_HUE; - update = true; - } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) { + mHue = pointToHue(event.getY()); - mLastTouchedPanel = PANEL_ALPHA; + update = true; + } else if (mSatValRect.contains(startX, startY)) { - mAlpha = pointToAlpha((int) event.getX()); + mLastTouchedPanel = PANEL_SAT_VAL; - update = true; - } + float[] result = pointToSatVal(event.getX(), event.getY()); + mSat = result[0]; + mVal = result[1]; - return update; - } + update = true; + } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) { - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mLastTouchedPanel = PANEL_ALPHA; - int width = 0; - int height = 0; + mAlpha = pointToAlpha((int) event.getX()); - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); + update = true; + } - int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); - int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); - widthAllowed = chooseWidth(widthMode, widthAllowed); - heightAllowed = chooseHeight(heightMode, heightAllowed); + return update; + } - if (!mShowAlphaPanel) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); + int width = 0; + int height = 0; - //If calculated height (based on the width) is more than the allowed height. - if (height > heightAllowed || getTag().equals("landscape")) { - height = heightAllowed; - width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH); - } else { - width = widthAllowed; - } - } else { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); - width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); + int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); - if (width > widthAllowed) { - width = widthAllowed; - height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); - } else { - height = heightAllowed; - } + widthAllowed = chooseWidth(widthMode, widthAllowed); + heightAllowed = chooseHeight(heightMode, heightAllowed); - } + if (!mShowAlphaPanel) { - setMeasuredDimension(width, height); - } + height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); - private int chooseWidth(int mode, int size) { - if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { - return size; - } else { // (mode == MeasureSpec.UNSPECIFIED) - return getPrefferedWidth(); - } - } + //If calculated height (based on the width) is more than the allowed height. + if (height > heightAllowed || getTag().equals("landscape")) { + height = heightAllowed; + width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH); + } else { + width = widthAllowed; + } + } else { - private int chooseHeight(int mode, int size) { - if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { - return size; - } else { // (mode == MeasureSpec.UNSPECIFIED) - return getPrefferedHeight(); - } - } + width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); - private int getPrefferedWidth() { + if (width > widthAllowed) { + width = widthAllowed; + height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); + } else { + height = heightAllowed; + } - int width = getPrefferedHeight(); + } - if (mShowAlphaPanel) { - width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT); - } + setMeasuredDimension(width, height); + } + private int chooseWidth(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedWidth(); + } + } - return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING); + private int chooseHeight(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedHeight(); + } + } - } + private int getPrefferedWidth() { - private int getPrefferedHeight() { + int width = getPrefferedHeight(); - int height = (int) (200 * mDensity); + if (mShowAlphaPanel) { + width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT); + } - if (mShowAlphaPanel) { - height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; - } - return height; - } + return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING); + } - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); + private int getPrefferedHeight() { - mDrawingRect = new RectF(); - mDrawingRect.left = mDrawingOffset + getPaddingLeft(); - mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); - mDrawingRect.top = mDrawingOffset + getPaddingTop(); - mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); + int height = (int) (200 * mDensity); - setUpSatValRect(); - setUpHueRect(); - setUpAlphaRect(); - } + if (mShowAlphaPanel) { + height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } - private void setUpSatValRect() { + return height; + } - final RectF dRect = mDrawingRect; - float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; - if (mShowAlphaPanel) { - panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; - } + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); - float left = dRect.left + BORDER_WIDTH_PX; - float top = dRect.top + BORDER_WIDTH_PX; - float bottom = top + panelSide; - float right = left + panelSide; + mDrawingRect = new RectF(); + mDrawingRect.left = mDrawingOffset + getPaddingLeft(); + mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); + mDrawingRect.top = mDrawingOffset + getPaddingTop(); + mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); - mSatValRect = new RectF(left, top, right, bottom); - } + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } - private void setUpHueRect() { - final RectF dRect = mDrawingRect; + private void setUpSatValRect() { - float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; - float top = dRect.top + BORDER_WIDTH_PX; - float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0); - float right = dRect.right - BORDER_WIDTH_PX; - - mHueRect = new RectF(left, top, right, bottom); - } - - private void setUpAlphaRect() { - - if (!mShowAlphaPanel) { - return; - } - - final RectF dRect = mDrawingRect; - - float left = dRect.left + BORDER_WIDTH_PX; - float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; - float bottom = dRect.bottom - BORDER_WIDTH_PX; - float right = dRect.right - BORDER_WIDTH_PX; - - mAlphaRect = new RectF(left, top, right, bottom); - - mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); - mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math.round(mAlphaRect.top), Math.round(mAlphaRect.right), - Math.round(mAlphaRect.bottom)); - - } - - - /** - * Set a OnColorChangedListener to get notified when the color - * selected by the user has changed. - * @param listener - */ - public void setOnColorChangedListener(OnColorChangedListener listener) { - mListener = listener; - } + final RectF dRect = mDrawingRect; + float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; + + if (mShowAlphaPanel) { + panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = top + panelSide; + float right = left + panelSide; - /** - * Set the color of the border surrounding all panels. - * @param color - */ - public void setBorderColor(int color) { - mBorderColor = color; - invalidate(); - } + mSatValRect = new RectF(left, top, right, bottom); + } - /** - * Get the color of the border surrounding all panels. - */ - public int getBorderColor() { - return mBorderColor; - } - - /** - * Get the current color this view is showing. - * @return the current color. - */ - public int getColor() { - return Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}); - } - - /** - * Set the color the view should show. - * @param color The color that should be selected. - */ - public void setColor(int color) { - setColor(color, false); - } - - /** - * Set the color this view should show. - * @param color The color that should be selected. - * @param callback If you want to get a callback to - * your OnColorChangedListener. - */ - public void setColor(int color, boolean callback) { - - int alpha = Color.alpha(color); - int red = Color.red(color); - int blue = Color.blue(color); - int green = Color.green(color); - - float[] hsv = new float[3]; - - Color.RGBToHSV(red, green, blue, hsv); - - mAlpha = alpha; - mHue = hsv[0]; - mSat = hsv[1]; - mVal = hsv[2]; - - if (callback && mListener != null) { - mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); - } - - invalidate(); - } - - /** - * Get the drawing offset of the color picker view. - * The drawing offset is the distance from the side of - * a panel to the side of the view minus the padding. - * Useful if you want to have your own panel below showing - * the currently selected color and want to align it perfectly. - * @return The offset in pixels. - */ - public float getDrawingOffset() { - return mDrawingOffset; - } - - /** - * Set if the user is allowed to adjust the alpha panel. Default is false. - * If it is set to false no alpha will be set. - * @param visible - */ - public void setAlphaSliderVisible(boolean visible) { - - if (mShowAlphaPanel != visible) { - mShowAlphaPanel = visible; + private void setUpHueRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0); + float right = dRect.right - BORDER_WIDTH_PX; + + mHueRect = new RectF(left, top, right, bottom); + } + + private void setUpAlphaRect() { + + if (!mShowAlphaPanel) return; + + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mAlphaRect = new RectF(left, top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + mAlphaPattern.setBounds( + Math.round(mAlphaRect.left), + Math.round(mAlphaRect.top), + Math.round(mAlphaRect.right), + Math.round(mAlphaRect.bottom) + ); + + } + + + /** + * Set a OnColorChangedListener to get notified when the color + * selected by the user has changed. + * + * @param listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener) { + mListener = listener; + } + + /** + * Set the color of the border surrounding all panels. + * + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding all panels. + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * Get the current color this view is showing. + * + * @return the current color. + */ + public int getColor() { + return Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal}); + } + + /** + * Set the color the view should show. + * + * @param color The color that should be selected. + */ + public void setColor(int color) { + setColor(color, false); + } + + /** + * Set the color this view should show. + * + * @param color The color that should be selected. + * @param callback If you want to get a callback to + * your OnColorChangedListener. + */ + public void setColor(int color, boolean callback) { + + int alpha = Color.alpha(color); + + float[] hsv = new float[3]; + + Color.colorToHSV(color, hsv); + + mAlpha = alpha; + mHue = hsv[0]; + mSat = hsv[1]; + mVal = hsv[2]; + + if (callback && mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + } + + /** + * Get the drawing offset of the color picker view. + * The drawing offset is the distance from the side of + * a panel to the side of the view minus the padding. + * Useful if you want to have your own panel below showing + * the currently selected color and want to align it perfectly. + * + * @return The offset in pixels. + */ + public float getDrawingOffset() { + return mDrawingOffset; + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + * + * @param visible + */ + public void setAlphaSliderVisible(boolean visible) { + + if (mShowAlphaPanel != visible) { + mShowAlphaPanel = visible; /* - * Reset all shader to force a recreation. + * Reset all shader to force a recreation. * Otherwise they will not look right after * the size of the view has changed. */ - mValShader = null; - mSatShader = null; - mHueShader = null; - mAlphaShader = null; - ; - - requestLayout(); - } - - } - - public void setSliderTrackerColor(int color) { - mSliderTrackerColor = color; - - mHueTrackerPaint.setColor(mSliderTrackerColor); - - invalidate(); - } - - public int getSliderTrackerColor() { - return mSliderTrackerColor; - } - - /** - * Set the text that should be shown in the - * alpha slider. Set to null to disable text. - * @param res string resource id. - */ - public void setAlphaSliderText(int res) { - String text = getContext().getString(res); - setAlphaSliderText(text); - } - - /** - * Set the text that should be shown in the - * alpha slider. Set to null to disable text. - * @param text Text that should be shown. - */ - public void setAlphaSliderText(String text) { - mAlphaSliderText = text; - invalidate(); - } - - /** - * Get the current value of the text - * that will be shown in the alpha - * slider. - * @return - */ - public String getAlphaSliderText() { - return mAlphaSliderText; - } -} \ No newline at end of file + mValShader = null; + mSatShader = null; + mHueShader = null; + mAlphaShader = null; + + requestLayout(); + } + + } + + public boolean getAlphaSliderVisible() { + return mShowAlphaPanel; + } + + public void setSliderTrackerColor(int color) { + mSliderTrackerColor = color; + + mHueTrackerPaint.setColor(mSliderTrackerColor); + + invalidate(); + } + + public int getSliderTrackerColor() { + return mSliderTrackerColor; + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * + * @param res string resource id. + */ + public void setAlphaSliderText(int res) { + String text = getContext().getString(res); + setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setAlphaSliderText(String text) { + mAlphaSliderText = text; + invalidate(); + } + + /** + * Get the current value of the text + * that will be shown in the alpha + * slider. + * + * @return + */ + public String getAlphaSliderText() { + return mAlphaSliderText; + } +} diff --git a/app/src/main/res/layout-land/dialog_color_picker.xml b/app/src/main/res/layout-land/dialog_color_picker.xml index cf567016..f7a2264d 100644 --- a/app/src/main/res/layout-land/dialog_color_picker.xml +++ b/app/src/main/res/layout-land/dialog_color_picker.xml @@ -1,77 +1,82 @@ - - - - - - - - - + android:paddingRight="5dp"> + + + + + + + + + + + + + + + - - - - - - - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_color_picker.xml b/app/src/main/res/layout/dialog_color_picker.xml index 2ddbca41..a5fbaaf2 100644 --- a/app/src/main/res/layout/dialog_color_picker.xml +++ b/app/src/main/res/layout/dialog_color_picker.xml @@ -1,77 +1,88 @@ - - - - - - - - - + android:paddingRight="5dp"> + + + + + + + + + + + + + + + + + + - - - - - - - - - \ No newline at end of file + From 0460303063391612557994b4a53128beefac190c Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 20 Jun 2020 01:58:33 -0400 Subject: [PATCH 07/10] Replace old internal multipart, move bitcomet to transdroid --- .../http/multipart/ByteArrayPartSource.java | 86 ---- .../internalcopy/http/multipart/FilePart.java | 244 --------- .../http/multipart/FilePartSource.java | 131 ----- .../http/multipart/MultipartEntity.java | 225 -------- .../internalcopy/http/multipart/Part.java | 425 ---------------- .../internalcopy/http/multipart/PartBase.java | 150 ------ .../http/multipart/PartSource.java | 72 --- .../http/multipart/StringPart.java | 143 ------ .../daemon/BitComet/BitCometAdapter.java | 16 +- .../daemon/BuffaloNas/BuffaloNasAdapter.java | 28 +- .../DLinkRouterBT/DLinkRouterBTAdapter.java | 46 +- .../daemon/Deluge/DelugeAdapter.java | 8 +- .../daemon/Ktorrent/KtorrentAdapter.java | 14 +- .../Qbittorrent/QbittorrentAdapter.java | 6 +- .../daemon/Synology/SynologyAdapter.java | 16 +- .../daemon/Tfb4rt/Tfb4rtAdapter.java | 14 +- .../daemon/Ttorrent/TtorrentAdapter.java | 14 +- .../daemon/Utorrent/UtorrentAdapter.java | 14 +- .../multipart/BitCometFilePart.java | 480 +++++++++--------- .../transdroid}/multipart/Utf8StringPart.java | 88 ++-- 20 files changed, 375 insertions(+), 1845 deletions(-) delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/ByteArrayPartSource.java delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/FilePart.java delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/FilePartSource.java delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/MultipartEntity.java delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/Part.java delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/PartBase.java delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/PartSource.java delete mode 100644 app/src/main/java/com/android/internalcopy/http/multipart/StringPart.java rename app/src/main/java/{com/android/internalcopy/http => org/transdroid}/multipart/BitCometFilePart.java (89%) rename app/src/main/java/{com/android/internalcopy/http => org/transdroid}/multipart/Utf8StringPart.java (89%) diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/ByteArrayPartSource.java b/app/src/main/java/com/android/internalcopy/http/multipart/ByteArrayPartSource.java deleted file mode 100644 index b143b57e..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/ByteArrayPartSource.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/ByteArrayPartSource.java,v 1.7 2004/04/18 23:51:37 jsdever Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -/** - * A PartSource that reads from a byte array. This class should be used when - * the data to post is already loaded into memory. - * - * @author Michael Becke - * - * @since 2.0 - */ -public class ByteArrayPartSource implements PartSource { - - /** Name of the source file. */ - private String fileName; - - /** Byte array of the source file. */ - private byte[] bytes; - - /** - * Constructor for ByteArrayPartSource. - * - * @param fileName the name of the file these bytes represent - * @param bytes the content of this part - */ - public ByteArrayPartSource(String fileName, byte[] bytes) { - - this.fileName = fileName; - this.bytes = bytes; - - } - - /** - * @see PartSource#getLength() - */ - public long getLength() { - return bytes.length; - } - - /** - * @see PartSource#getFileName() - */ - public String getFileName() { - return fileName; - } - - /** - * @see PartSource#createInputStream() - */ - public InputStream createInputStream() { - return new ByteArrayInputStream(bytes); - } - -} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/FilePart.java b/app/src/main/java/com/android/internalcopy/http/multipart/FilePart.java deleted file mode 100644 index 43f050c1..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/FilePart.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java,v 1.19 2004/04/18 23:51:37 jsdever Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import org.apache.http.util.EncodingUtils; - -/** - * This class implements a part of a Multipart post object that - * consists of a file. - * - * @author Matthew Albright - * @author Jeff Dever - * @author Adrian Sutton - * @author Michael Becke - * @author Mark Diggory - * @author Mike Bowler - * @author Oleg Kalnichevski - * - * @since 2.0 - * - */ -public class FilePart extends PartBase { - - /** Default content encoding of file attachments. */ - public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; - - /** Default charset of file attachments. */ - public static final String DEFAULT_CHARSET = "ISO-8859-1"; - - /** Default transfer encoding of file attachments. */ - public static final String DEFAULT_TRANSFER_ENCODING = "binary"; - - /** Attachment's file name */ - protected static final String FILE_NAME = "; filename="; - - /** Attachment's file name as a byte array */ - private static final byte[] FILE_NAME_BYTES = - EncodingUtils.getAsciiBytes(FILE_NAME); - - /** Source of the file part. */ - private PartSource source; - - /** - * FilePart Constructor. - * - * @param name the name for this part - * @param partSource the source for this part - * @param contentType the content type for this part, if null the - * {@link #DEFAULT_CONTENT_TYPE default} is used - * @param charset the charset encoding for this part, if null the - * {@link #DEFAULT_CHARSET default} is used - */ - public FilePart(String name, PartSource partSource, String contentType, String charset) { - - super( - name, - contentType == null ? DEFAULT_CONTENT_TYPE : contentType, - charset == null ? "ISO-8859-1" : charset, - DEFAULT_TRANSFER_ENCODING - ); - - if (partSource == null) { - throw new IllegalArgumentException("Source may not be null"); - } - this.source = partSource; - } - - /** - * FilePart Constructor. - * - * @param name the name for this part - * @param partSource the source for this part - */ - public FilePart(String name, PartSource partSource) { - this(name, partSource, null, null); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param file the file to post - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public FilePart(String name, File file) - throws FileNotFoundException { - this(name, new FilePartSource(file), null, null); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param file the file to post - * @param contentType the content type for this part, if null the - * {@link #DEFAULT_CONTENT_TYPE default} is used - * @param charset the charset encoding for this part, if null the - * {@link #DEFAULT_CHARSET default} is used - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public FilePart(String name, File file, String contentType, String charset) - throws FileNotFoundException { - this(name, new FilePartSource(file), contentType, charset); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param fileName the file name - * @param file the file to post - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public FilePart(String name, String fileName, File file) - throws FileNotFoundException { - this(name, new FilePartSource(fileName, file), null, null); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param fileName the file name - * @param file the file to post - * @param contentType the content type for this part, if null the - * {@link #DEFAULT_CONTENT_TYPE default} is used - * @param charset the charset encoding for this part, if null the - * {@link #DEFAULT_CHARSET default} is used - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public FilePart(String name, String fileName, File file, String contentType, String charset) - throws FileNotFoundException { - this(name, new FilePartSource(fileName, file), contentType, charset); - } - - /** - * Write the disposition header to the output stream - * @param out The output stream - * @throws IOException If an IO problem occurs - * @see Part#sendDispositionHeader(OutputStream) - */ - @Override - protected void sendDispositionHeader(OutputStream out) - throws IOException { - super.sendDispositionHeader(out); - String filename = this.source.getFileName(); - if (filename != null) { - out.write(FILE_NAME_BYTES); - out.write(QUOTE_BYTES); - out.write(EncodingUtils.getAsciiBytes(filename)); - out.write(QUOTE_BYTES); - } - } - - /** - * Write the data in "source" to the specified stream. - * @param out The output stream. - * @throws IOException if an IO problem occurs. - * @see Part#sendData(OutputStream) - */ - @Override - protected void sendData(OutputStream out) throws IOException { - if (lengthOfData() == 0) { - - // this file contains no data, so there is nothing to send. - // we don't want to create a zero length buffer as this will - // cause an infinite loop when reading. - return; - } - - byte[] tmp = new byte[4096]; - InputStream instream = source.createInputStream(); - try { - int len; - while ((len = instream.read(tmp)) >= 0) { - out.write(tmp, 0, len); - } - } finally { - // we're done with the stream, close it - instream.close(); - } - } - - /** - * Returns the source of the file part. - * - * @return The source. - */ - protected PartSource getSource() { - return this.source; - } - - /** - * Return the length of the data. - * @return The length. - * @see Part#lengthOfData() - */ - @Override - protected long lengthOfData() { - return source.getLength(); - } - -} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/FilePartSource.java b/app/src/main/java/com/android/internalcopy/http/multipart/FilePartSource.java deleted file mode 100644 index 813ccbf3..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/FilePartSource.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePartSource.java,v 1.10 2004/04/18 23:51:37 jsdever Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -/** - * A PartSource that reads from a File. - * - * @author Michael Becke - * @author Mark Diggory - * @author Mike Bowler - * - * @since 2.0 - */ -public class FilePartSource implements PartSource { - - /** File part file. */ - private File file = null; - - /** File part file name. */ - private String fileName = null; - - /** - * Constructor for FilePartSource. - * - * @param file the FilePart source File. - * - * @throws FileNotFoundException if the file does not exist or - * cannot be read - */ - public FilePartSource(File file) throws FileNotFoundException { - this.file = file; - if (file != null) { - if (!file.isFile()) { - throw new FileNotFoundException("File is not a normal file."); - } - if (!file.canRead()) { - throw new FileNotFoundException("File is not readable."); - } - this.fileName = file.getName(); - } - } - - /** - * Constructor for FilePartSource. - * - * @param fileName the file name of the FilePart - * @param file the source File for the FilePart - * - * @throws FileNotFoundException if the file does not exist or - * cannot be read - */ - public FilePartSource(String fileName, File file) - throws FileNotFoundException { - this(file); - if (fileName != null) { - this.fileName = fileName; - } - } - - /** - * Return the length of the file - * @return the length of the file. - * @see PartSource#getLength() - */ - public long getLength() { - if (this.file != null) { - return this.file.length(); - } else { - return 0; - } - } - - /** - * Return the current filename - * @return the filename. - * @see PartSource#getFileName() - */ - public String getFileName() { - return (fileName == null) ? "noname" : fileName; - } - - /** - * Return a new {@link FileInputStream} for the current filename. - * @return the new input stream. - * @throws IOException If an IO problem occurs. - * @see PartSource#createInputStream() - */ - public InputStream createInputStream() throws IOException { - if (this.file != null) { - return new FileInputStream(this.file); - } else { - return new ByteArrayInputStream(new byte[] {}); - } - } - -} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/MultipartEntity.java b/app/src/main/java/com/android/internalcopy/http/multipart/MultipartEntity.java deleted file mode 100644 index 9af47175..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/MultipartEntity.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java,v 1.1 2004/10/06 03:39:59 mbecke Exp $ - * $Revision: 502647 $ - * $Date: 2007-02-02 17:22:54 +0100 (Fri, 02 Feb 2007) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Random; - -import org.apache.http.Header; -import org.apache.http.entity.AbstractHttpEntity; -import org.apache.http.message.BasicHeader; -import org.apache.http.params.HttpParams; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EncodingUtils; - -/** - * Implements a request entity suitable for an HTTP multipart POST method. - *

- * The HTTP multipart POST method is defined in section 3.3 of - * RFC1867: - *

- * The media-type multipart/form-data follows the rules of all multipart - * MIME data streams as outlined in RFC 1521. The multipart/form-data contains - * a series of parts. Each part is expected to contain a content-disposition - * header where the value is "form-data" and a name attribute specifies - * the field name within the form, e.g., 'content-disposition: form-data; - * name="xxxxx"', where xxxxx is the field name corresponding to that field. - * Field names originally in non-ASCII character sets may be encoded using - * the method outlined in RFC 1522. - *
- *

- *

This entity is designed to be used in conjunction with the - * {@link org.apache.http.HttpRequest} to provide - * multipart posts. Example usage:

- *
- *  File f = new File("/path/fileToUpload.txt");
- *  HttpRequest request = new HttpRequest("http://host/some_path");
- *  Part[] parts = {
- *      new StringPart("param_name", "value"),
- *      new FilePart(f.getName(), f)
- *  };
- *  filePost.setEntity(
- *      new MultipartRequestEntity(parts, filePost.getParams())
- *      );
- *  HttpClient client = new HttpClient();
- *  int status = client.executeMethod(filePost);
- * 
- * - * @since 3.0 - */ -public class MultipartEntity extends AbstractHttpEntity { - - /** The Content-Type for multipart/form-data. */ - private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data"; - - /** - * Sets the value to use as the multipart boundary. - *

- * This parameter expects a value if type {@link String}. - *

- */ - public static final String MULTIPART_BOUNDARY = "http.method.multipart.boundary"; - - /** - * The pool of ASCII chars to be used for generating a multipart boundary. - */ - private static byte[] MULTIPART_CHARS = EncodingUtils.getAsciiBytes( - "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); - - /** - * Generates a random multipart boundary string. - */ - private static byte[] generateMultipartBoundary() { - Random rand = new Random(); - byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40 - for (int i = 0; i < bytes.length; i++) { - bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]; - } - return bytes; - } - - /** The MIME parts as set by the constructor */ - protected Part[] parts; - - private byte[] multipartBoundary; - - private HttpParams params; - - private boolean contentConsumed = false; - - /** - * Creates a new multipart entity containing the given parts. - * @param parts The parts to include. - * @param params The params of the HttpMethod using this entity. - */ - public MultipartEntity(Part[] parts, HttpParams params) { - if (parts == null) { - throw new IllegalArgumentException("parts cannot be null"); - } - if (params == null) { - throw new IllegalArgumentException("params cannot be null"); - } - this.parts = parts; - this.params = params; - } - - public MultipartEntity(Part[] parts) { - setContentType(MULTIPART_FORM_CONTENT_TYPE); - if (parts == null) { - throw new IllegalArgumentException("parts cannot be null"); - } - this.parts = parts; - this.params = null; - } - - /** - * Returns the MIME boundary string that is used to demarcate boundaries of - * this part. The first call to this method will implicitly create a new - * boundary string. To create a boundary string first the - * HttpMethodParams.MULTIPART_BOUNDARY parameter is considered. Otherwise - * a random one is generated. - * - * @return The boundary string of this entity in ASCII encoding. - */ - protected byte[] getMultipartBoundary() { - if (multipartBoundary == null) { - String temp = null; - if (params != null) { - temp = (String) params.getParameter(MULTIPART_BOUNDARY); - } - if (temp != null) { - multipartBoundary = EncodingUtils.getAsciiBytes(temp); - } else { - multipartBoundary = generateMultipartBoundary(); - } - } - return multipartBoundary; - } - - /** - * Returns true if all parts are repeatable, false otherwise. - */ - public boolean isRepeatable() { - for (int i = 0; i < parts.length; i++) { - if (!parts[i].isRepeatable()) { - return false; - } - } - return true; - } - - /* (non-Javadoc) - */ - public void writeTo(OutputStream out) throws IOException { - Part.sendParts(out, parts, getMultipartBoundary()); - } - /* (non-Javadoc) - * @see org.apache.commons.http.AbstractHttpEntity.#getContentType() - */ - @Override - public Header getContentType() { - StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE); - buffer.append("; boundary="); - buffer.append(EncodingUtils.getAsciiString(getMultipartBoundary())); - return new BasicHeader(HTTP.CONTENT_TYPE, buffer.toString()); - - } - - /* (non-Javadoc) - */ - public long getContentLength() { - try { - return Part.getLengthOfParts(parts, getMultipartBoundary()); - } catch (Exception e) { - return 0; - } - } - - public InputStream getContent() throws IOException, IllegalStateException { - if(!isRepeatable() && this.contentConsumed ) { - throw new IllegalStateException("Content has been consumed"); - } - this.contentConsumed = true; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Part.sendParts(baos, this.parts, this.multipartBoundary); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - return bais; - } - - public boolean isStreaming() { - return false; - } -} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/Part.java b/app/src/main/java/com/android/internalcopy/http/multipart/Part.java deleted file mode 100644 index aa667715..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/Part.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v 1.16 2005/01/14 21:16:40 olegk Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import org.apache.http.util.EncodingUtils; - -/** - * Abstract class for one Part of a multipart post object. - * - * @author Matthew Albright - * @author Jeff Dever - * @author Adrian Sutton - * @author Mike Bowler - * @author Oleg Kalnichevski - * - * @since 2.0 - */ -public abstract class Part { - - /** - * The boundary - * @deprecated use {@link org.apache.http.client.methods.multipart#MULTIPART_BOUNDARY} - */ - protected static final String BOUNDARY = "----------------314159265358979323846"; - - /** - * The boundary as a byte array. - * @deprecated - */ - protected static final byte[] BOUNDARY_BYTES = EncodingUtils.getAsciiBytes(BOUNDARY); - - /** - * The default boundary to be used if {@link #setPartBoundary(byte[])} has not - * been called. - */ - private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES; - - /** Carriage return/linefeed */ - protected static final String CRLF = "\r\n"; - - /** Carriage return/linefeed as a byte array */ - protected static final byte[] CRLF_BYTES = EncodingUtils.getAsciiBytes(CRLF); - - /** Content dispostion characters */ - protected static final String QUOTE = "\""; - - /** Content dispostion as a byte array */ - protected static final byte[] QUOTE_BYTES = - EncodingUtils.getAsciiBytes(QUOTE); - - /** Extra characters */ - protected static final String EXTRA = "--"; - - /** Extra characters as a byte array */ - protected static final byte[] EXTRA_BYTES = - EncodingUtils.getAsciiBytes(EXTRA); - - /** Content dispostion characters */ - protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name="; - - /** Content dispostion as a byte array */ - protected static final byte[] CONTENT_DISPOSITION_BYTES = - EncodingUtils.getAsciiBytes(CONTENT_DISPOSITION); - - /** Content type header */ - protected static final String CONTENT_TYPE = "Content-Type: "; - - /** Content type header as a byte array */ - protected static final byte[] CONTENT_TYPE_BYTES = - EncodingUtils.getAsciiBytes(CONTENT_TYPE); - - /** Content charset */ - protected static final String CHARSET = "; charset="; - - /** Content charset as a byte array */ - protected static final byte[] CHARSET_BYTES = - EncodingUtils.getAsciiBytes(CHARSET); - - /** Content type header */ - protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: "; - - /** Content type header as a byte array */ - protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES = - EncodingUtils.getAsciiBytes(CONTENT_TRANSFER_ENCODING); - - /** - * Return the boundary string. - * @return the boundary string - * @deprecated uses a constant string. Rather use {@link #getPartBoundary} - */ - public static String getBoundary() { - return BOUNDARY; - } - - /** - * The ASCII bytes to use as the multipart boundary. - */ - private byte[] boundaryBytes; - - /** - * Return the name of this part. - * @return The name. - */ - public abstract String getName(); - - /** - * Returns the content type of this part. - * @return the content type, or null to exclude the content type header - */ - public abstract String getContentType(); - - /** - * Return the character encoding of this part. - * @return the character encoding, or null to exclude the character - * encoding header - */ - public abstract String getCharSet(); - - /** - * Return the transfer encoding of this part. - * @return the transfer encoding, or null to exclude the transfer encoding header - */ - public abstract String getTransferEncoding(); - - /** - * Gets the part boundary to be used. - * @return the part boundary as an array of bytes. - * - * @since 3.0 - */ - protected byte[] getPartBoundary() { - if (boundaryBytes == null) { - // custom boundary bytes have not been set, use the default. - return DEFAULT_BOUNDARY_BYTES; - } else { - return boundaryBytes; - } - } - - /** - * Sets the part boundary. Only meant to be used by - * {@link Part#sendParts(OutputStream, Part[], byte[])} - * and {@link Part#getLengthOfParts(Part[], byte[])} - * @param boundaryBytes An array of ASCII bytes. - * @since 3.0 - */ - void setPartBoundary(byte[] boundaryBytes) { - this.boundaryBytes = boundaryBytes; - } - - /** - * Tests if this part can be sent more than once. - * @return true if {@link #sendData(OutputStream)} can be successfully called - * more than once. - * @since 3.0 - */ - public boolean isRepeatable() { - return true; - } - - /** - * Write the start to the specified output stream - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - protected void sendStart(OutputStream out) throws IOException { - out.write(EXTRA_BYTES); - out.write(getPartBoundary()); - out.write(CRLF_BYTES); - } - - /** - * Write the content disposition header to the specified output stream - * - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - protected void sendDispositionHeader(OutputStream out) throws IOException { - out.write(CONTENT_DISPOSITION_BYTES); - out.write(QUOTE_BYTES); - out.write(EncodingUtils.getAsciiBytes(getName())); - out.write(QUOTE_BYTES); - } - - /** - * Write the content type header to the specified output stream - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - protected void sendContentTypeHeader(OutputStream out) throws IOException { - String contentType = getContentType(); - if (contentType != null) { - out.write(CRLF_BYTES); - out.write(CONTENT_TYPE_BYTES); - out.write(EncodingUtils.getAsciiBytes(contentType)); - String charSet = getCharSet(); - if (charSet != null) { - out.write(CHARSET_BYTES); - out.write(EncodingUtils.getAsciiBytes(charSet)); - } - } - } - - /** - * Write the content transfer encoding header to the specified - * output stream - * - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - protected void sendTransferEncodingHeader(OutputStream out) throws IOException { - String transferEncoding = getTransferEncoding(); - if (transferEncoding != null) { - out.write(CRLF_BYTES); - out.write(CONTENT_TRANSFER_ENCODING_BYTES); - out.write(EncodingUtils.getAsciiBytes(transferEncoding)); - } - } - - /** - * Write the end of the header to the output stream - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - protected void sendEndOfHeader(OutputStream out) throws IOException { - out.write(CRLF_BYTES); - out.write(CRLF_BYTES); - } - - /** - * Write the data to the specified output stream - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - protected abstract void sendData(OutputStream out) throws IOException; - - /** - * Return the length of the main content - * - * @return long The length. - * @throws IOException If an IO problem occurs - */ - protected abstract long lengthOfData() throws IOException; - - /** - * Write the end data to the output stream. - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - protected void sendEnd(OutputStream out) throws IOException { - out.write(CRLF_BYTES); - } - - /** - * Write all the data to the output stream. - * If you override this method make sure to override - * #length() as well - * - * @param out The output stream - * @throws IOException If an IO problem occurs. - */ - public void send(OutputStream out) throws IOException { - sendStart(out); - sendDispositionHeader(out); - sendContentTypeHeader(out); - sendTransferEncodingHeader(out); - sendEndOfHeader(out); - sendData(out); - sendEnd(out); - } - - - /** - * Return the full length of all the data. - * If you override this method make sure to override - * #send(OutputStream) as well - * - * @return long The length. - * @throws IOException If an IO problem occurs - */ - public long length() throws IOException { - if (lengthOfData() < 0) { - return -1; - } - ByteArrayOutputStream overhead = new ByteArrayOutputStream(); - sendStart(overhead); - sendDispositionHeader(overhead); - sendContentTypeHeader(overhead); - sendTransferEncodingHeader(overhead); - sendEndOfHeader(overhead); - sendEnd(overhead); - return overhead.size() + lengthOfData(); - } - - /** - * Return a string representation of this object. - * @return A string representation of this object. - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return this.getName(); - } - - /** - * Write all parts and the last boundary to the specified output stream. - * - * @param out The stream to write to. - * @param parts The parts to write. - * - * @throws IOException If an I/O error occurs while writing the parts. - */ - public static void sendParts(OutputStream out, final Part[] parts) - throws IOException { - sendParts(out, parts, DEFAULT_BOUNDARY_BYTES); - } - - /** - * Write all parts and the last boundary to the specified output stream. - * - * @param out The stream to write to. - * @param parts The parts to write. - * @param partBoundary The ASCII bytes to use as the part boundary. - * - * @throws IOException If an I/O error occurs while writing the parts. - * - * @since 3.0 - */ - public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary) - throws IOException { - - if (parts == null) { - throw new IllegalArgumentException("Parts may not be null"); - } - if (partBoundary == null || partBoundary.length == 0) { - throw new IllegalArgumentException("partBoundary may not be empty"); - } - for (int i = 0; i < parts.length; i++) { - // set the part boundary before the part is sent - parts[i].setPartBoundary(partBoundary); - parts[i].send(out); - } - out.write(EXTRA_BYTES); - out.write(partBoundary); - out.write(EXTRA_BYTES); - out.write(CRLF_BYTES); - } - - /** - * Return the total sum of all parts and that of the last boundary - * - * @param parts The parts. - * @return The total length - * - * @throws IOException If an I/O error occurs while writing the parts. - */ - public static long getLengthOfParts(Part[] parts) - throws IOException { - return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES); - } - - /** - * Gets the length of the multipart message including the given parts. - * - * @param parts The parts. - * @param partBoundary The ASCII bytes to use as the part boundary. - * @return The total length - * - * @throws IOException If an I/O error occurs while writing the parts. - * - * @since 3.0 - */ - public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException { - if (parts == null) { - throw new IllegalArgumentException("Parts may not be null"); - } - long total = 0; - for (int i = 0; i < parts.length; i++) { - // set the part boundary before we calculate the part's length - parts[i].setPartBoundary(partBoundary); - long l = parts[i].length(); - if (l < 0) { - return -1; - } - total += l; - } - total += EXTRA_BYTES.length; - total += partBoundary.length; - total += EXTRA_BYTES.length; - total += CRLF_BYTES.length; - return total; - } -} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/PartBase.java b/app/src/main/java/com/android/internalcopy/http/multipart/PartBase.java deleted file mode 100644 index 82896472..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/PartBase.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartBase.java,v 1.5 2004/04/18 23:51:37 jsdever Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - - -/** - * Provides setters and getters for the basic Part properties. - * - * @author Michael Becke - */ -public abstract class PartBase extends Part { - - /** Name of the file part. */ - private String name; - - /** Content type of the file part. */ - private String contentType; - - /** Content encoding of the file part. */ - private String charSet; - - /** The transfer encoding. */ - private String transferEncoding; - - /** - * Constructor. - * - * @param name The name of the part - * @param contentType The content type, or null - * @param charSet The character encoding, or null - * @param transferEncoding The transfer encoding, or null - */ - public PartBase(String name, String contentType, String charSet, String transferEncoding) { - - if (name == null) { - throw new IllegalArgumentException("Name must not be null"); - } - this.name = name; - this.contentType = contentType; - this.charSet = charSet; - this.transferEncoding = transferEncoding; - } - - /** - * Returns the name. - * @return The name. - * @see Part#getName() - */ - @Override - public String getName() { - return this.name; - } - - /** - * Returns the content type of this part. - * @return String The name. - */ - @Override - public String getContentType() { - return this.contentType; - } - - /** - * Return the character encoding of this part. - * @return String The name. - */ - @Override - public String getCharSet() { - return this.charSet; - } - - /** - * Returns the transfer encoding of this part. - * @return String The name. - */ - @Override - public String getTransferEncoding() { - return transferEncoding; - } - - /** - * Sets the character encoding. - * - * @param charSet the character encoding, or null to exclude the character - * encoding header - */ - public void setCharSet(String charSet) { - this.charSet = charSet; - } - - /** - * Sets the content type. - * - * @param contentType the content type, or null to exclude the content type header - */ - public void setContentType(String contentType) { - this.contentType = contentType; - } - - /** - * Sets the part name. - * - * @param name - */ - public void setName(String name) { - if (name == null) { - throw new IllegalArgumentException("Name must not be null"); - } - this.name = name; - } - - /** - * Sets the transfer encoding. - * - * @param transferEncoding the transfer encoding, or null to exclude the - * transfer encoding header - */ - public void setTransferEncoding(String transferEncoding) { - this.transferEncoding = transferEncoding; - } - -} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/PartSource.java b/app/src/main/java/com/android/internalcopy/http/multipart/PartSource.java deleted file mode 100644 index 8fec3aa9..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/PartSource.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartSource.java,v 1.6 2004/04/18 23:51:37 jsdever Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.IOException; -import java.io.InputStream; - -/** - * An interface for providing access to data when posting MultiPart messages. - * - * @see FilePart - * - * @author Michael Becke - * - * @since 2.0 - */ -public interface PartSource { - - /** - * Gets the number of bytes contained in this source. - * - * @return a value >= 0 - */ - long getLength(); - - /** - * Gets the name of the file this source represents. - * - * @return the fileName used for posting a MultiPart file part - */ - String getFileName(); - - /** - * Gets a new InputStream for reading this source. This method can be - * called more than once and should therefore return a new stream every - * time. - * - * @return a new InputStream - * - * @throws IOException if an error occurs when creating the InputStream - */ - InputStream createInputStream() throws IOException; - -} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/StringPart.java b/app/src/main/java/com/android/internalcopy/http/multipart/StringPart.java deleted file mode 100644 index c613c771..00000000 --- a/app/src/main/java/com/android/internalcopy/http/multipart/StringPart.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/StringPart.java,v 1.11 2004/04/18 23:51:37 jsdever Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.OutputStream; -import java.io.IOException; - -import org.apache.http.util.EncodingUtils; - -/** - * Simple string parameter for a multipart post - * - * @author Matthew Albright - * @author Jeff Dever - * @author Mike Bowler - * @author Oleg Kalnichevski - * - * @since 2.0 - */ -public class StringPart extends PartBase { - - /** Default content encoding of string parameters. */ - public static final String DEFAULT_CONTENT_TYPE = "text/plain"; - - /** Default charset of string parameters*/ - public static final String DEFAULT_CHARSET = "US-ASCII"; - - /** Default transfer encoding of string parameters*/ - public static final String DEFAULT_TRANSFER_ENCODING = "8bit"; - - /** Contents of this StringPart. */ - private byte[] content; - - /** The String value of this part. */ - private String value; - - /** - * Constructor. - * - * @param name The name of the part - * @param value the string to post - * @param charset the charset to be used to encode the string, if null - * the {@link #DEFAULT_CHARSET default} is used - */ - public StringPart(String name, String value, String charset) { - - super( - name, - DEFAULT_CONTENT_TYPE, - charset == null ? DEFAULT_CHARSET : charset, - DEFAULT_TRANSFER_ENCODING - ); - if (value == null) { - throw new IllegalArgumentException("Value may not be null"); - } - if (value.indexOf(0) != -1) { - // See RFC 2048, 2.8. "8bit Data" - throw new IllegalArgumentException("NULs may not be present in string parts"); - } - this.value = value; - } - - /** - * Constructor. - * - * @param name The name of the part - * @param value the string to post - */ - public StringPart(String name, String value) { - this(name, value, null); - } - - /** - * Gets the content in bytes. Bytes are lazily created to allow the charset to be changed - * after the part is created. - * - * @return the content in bytes - */ - private byte[] getContent() { - if (content == null) { - content = EncodingUtils.getBytes(value, getCharSet()); - } - return content; - } - - /** - * Writes the data to the given OutputStream. - * @param out the OutputStream to write to - * @throws IOException if there is a write error - */ - @Override - protected void sendData(OutputStream out) throws IOException { - out.write(getContent()); - } - - /** - * Return the length of the data. - * @return The length of the data. - * @see Part#lengthOfData() - */ - @Override - protected long lengthOfData() { - return getContent().length; - } - - /* (non-Javadoc) - * @see org.apache.commons.httpclient.methods.multipart.BasePart#setCharSet(java.lang.String) - */ - @Override - public void setCharSet(String charSet) { - super.setCharSet(charSet); - this.content = null; - } - -} diff --git a/app/src/main/java/org/transdroid/daemon/BitComet/BitCometAdapter.java b/app/src/main/java/org/transdroid/daemon/BitComet/BitCometAdapter.java index ba313bf7..3d758188 100644 --- a/app/src/main/java/org/transdroid/daemon/BitComet/BitCometAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/BitComet/BitCometAdapter.java @@ -1,26 +1,26 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.BitComet; -import com.android.internalcopy.http.multipart.BitCometFilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; -import com.android.internalcopy.http.multipart.Utf8StringPart; +import org.transdroid.multipart.BitCometFilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; +import org.transdroid.multipart.Utf8StringPart; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; diff --git a/app/src/main/java/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java b/app/src/main/java/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java index e054db1a..80211e74 100644 --- a/app/src/main/java/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/BuffaloNas/BuffaloNasAdapter.java @@ -1,25 +1,25 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.BuffaloNas; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -108,8 +108,8 @@ public class BuffaloNasAdapter implements IDaemonAdapter { // Request to add a torrent by URL String url = ((AddByUrlTask) task).getUrl(); // @formatter:off - makeRequest(log, "/api/torrent-add", - new BasicNameValuePair("url", url), + makeRequest(log, "/api/torrent-add", + new BasicNameValuePair("url", url), new BasicNameValuePair("start", "yes")); // @formatter:on return new DaemonTaskSuccessResult(task); @@ -119,9 +119,9 @@ public class BuffaloNasAdapter implements IDaemonAdapter { // Remove a torrent RemoveTask removeTask = (RemoveTask) task; // @formatter:off - makeRequest(log, "/api/torrent-remove", - new BasicNameValuePair("hash", removeTask.getTargetTorrent().getUniqueID()), - new BasicNameValuePair("delete-torrent", "yes"), + makeRequest(log, "/api/torrent-remove", + new BasicNameValuePair("hash", removeTask.getTargetTorrent().getUniqueID()), + new BasicNameValuePair("delete-torrent", "yes"), new BasicNameValuePair("delete-data", (removeTask.includingData() ? "yes" : "no"))); // @formatter:on return new DaemonTaskSuccessResult(task); @@ -149,9 +149,9 @@ public class BuffaloNasAdapter implements IDaemonAdapter { String ul = Integer.toString( (ratesTask.getUploadRate() == null ? -1 : ratesTask.getUploadRate() * 1024)); // @formatter:off - makeRequest(log, "/api/app-settings-set", + makeRequest(log, "/api/app-settings-set", new BasicNameValuePair("auto_bandwidth_management", "0"), - new BasicNameValuePair("max_dl_rate", dl), + new BasicNameValuePair("max_dl_rate", dl), new BasicNameValuePair("max_ul_rate", ul), new BasicNameValuePair("max_ul_rate_seed", ul)); // @formatter:on diff --git a/app/src/main/java/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java b/app/src/main/java/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java index fab3d6fb..8cf3c74e 100644 --- a/app/src/main/java/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/DLinkRouterBT/DLinkRouterBTAdapter.java @@ -1,25 +1,25 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.DLinkRouterBT; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -358,23 +358,23 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter { // @formatter:off Torrent new_t = new Torrent( - i, - tor.getString(BT_HASH), - tor.getString(BT_CAPTION), + i, + tor.getString(BT_HASH), + tor.getString(BT_CAPTION), status, null, // Not supported? - tor.getInt(BT_DOWNLOAD_RATE), - tor.getInt(BT_UPLOAD_RATE), - tor.getInt(BT_PEERS_CONNECTED), - tor.getInt(BT_PEERS_TOTAL), - tor.getInt(BT_SEEDS_CONNECTED), + tor.getInt(BT_DOWNLOAD_RATE), + tor.getInt(BT_UPLOAD_RATE), + tor.getInt(BT_PEERS_CONNECTED), + tor.getInt(BT_PEERS_TOTAL), + tor.getInt(BT_SEEDS_CONNECTED), tor.getInt(BT_SEEDS_TOTAL), eta, - tor.getLong(BT_DONE), - tor.getLong(BT_PAYLOAD_UPLOAD), - tor.getLong(BT_SIZE), + tor.getLong(BT_DONE), + tor.getLong(BT_PAYLOAD_UPLOAD), + tor.getLong(BT_SIZE), tor.getLong(BT_DONE) / (float) tor.getLong(BT_SIZE), - Float.parseFloat(tor.getString(BT_COPYS)), + Float.parseFloat(tor.getString(BT_COPYS)), null, null, null, @@ -402,11 +402,11 @@ public class DLinkRouterBTAdapter implements IDaemonAdapter { // @formatter:off torrentfiles.add(new TorrentFile( String.valueOf(i), - file.getString(BT_FILE_NAME), - file.getString(BT_FILE_NAME), + file.getString(BT_FILE_NAME), + file.getString(BT_FILE_NAME), null, // Not supported? - file.getLong(BT_FILE_SIZE), - file.getLong(BT_FILE_DONE), + file.getLong(BT_FILE_SIZE), + file.getLong(BT_FILE_DONE), convertTransmissionPriority(file.getInt(BT_FILE_PRIORITY)))); // @formatter:on } diff --git a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeAdapter.java b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeAdapter.java index cc62339b..61180a7a 100644 --- a/app/src/main/java/org/transdroid/daemon/Deluge/DelugeAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Deluge/DelugeAdapter.java @@ -17,9 +17,9 @@ */ package org.transdroid.daemon.Deluge; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -418,7 +418,7 @@ public class DelugeAdapter implements IDaemonAdapter { return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.FileAccessError, e.toString())); } } - + /*private JSONArray buildSetTorrentOptions(String torrent, String key, String value) throws JSONException { JSONArray params = new JSONArray(); params.put(new JSONArray().put(torrent)); // torrent_id diff --git a/app/src/main/java/org/transdroid/daemon/Ktorrent/KtorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Ktorrent/KtorrentAdapter.java index 4455b051..287a93e3 100644 --- a/app/src/main/java/org/transdroid/daemon/Ktorrent/KtorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Ktorrent/KtorrentAdapter.java @@ -1,25 +1,25 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Ktorrent; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; diff --git a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java index 2b81edfe..83bb4407 100644 --- a/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java @@ -17,9 +17,9 @@ */ package org.transdroid.daemon.Qbittorrent; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; diff --git a/app/src/main/java/org/transdroid/daemon/Synology/SynologyAdapter.java b/app/src/main/java/org/transdroid/daemon/Synology/SynologyAdapter.java index dca623f1..4bc720e7 100644 --- a/app/src/main/java/org/transdroid/daemon/Synology/SynologyAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Synology/SynologyAdapter.java @@ -1,26 +1,26 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Synology; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; -import com.android.internalcopy.http.multipart.Utf8StringPart; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; +import org.transdroid.multipart.Utf8StringPart; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; diff --git a/app/src/main/java/org/transdroid/daemon/Tfb4rt/Tfb4rtAdapter.java b/app/src/main/java/org/transdroid/daemon/Tfb4rt/Tfb4rtAdapter.java index ddfa5bed..f75ac761 100644 --- a/app/src/main/java/org/transdroid/daemon/Tfb4rt/Tfb4rtAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Tfb4rt/Tfb4rtAdapter.java @@ -1,19 +1,19 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Tfb4rt; @@ -47,9 +47,9 @@ import org.transdroid.daemon.task.RemoveTask; import org.transdroid.daemon.task.RetrieveTask; import org.transdroid.daemon.task.RetrieveTaskSuccessResult; import org.transdroid.daemon.util.HttpHelper; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; /** * An adapter that allows for easy access to Torrentflux-b4rt installs. Communication is handled via HTTP GET requests diff --git a/app/src/main/java/org/transdroid/daemon/Ttorrent/TtorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Ttorrent/TtorrentAdapter.java index 53fa05d3..3f4ea1de 100644 --- a/app/src/main/java/org/transdroid/daemon/Ttorrent/TtorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Ttorrent/TtorrentAdapter.java @@ -1,25 +1,25 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Ttorrent; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; diff --git a/app/src/main/java/org/transdroid/daemon/Utorrent/UtorrentAdapter.java b/app/src/main/java/org/transdroid/daemon/Utorrent/UtorrentAdapter.java index 5f2e7b7e..ed43d52e 100644 --- a/app/src/main/java/org/transdroid/daemon/Utorrent/UtorrentAdapter.java +++ b/app/src/main/java/org/transdroid/daemon/Utorrent/UtorrentAdapter.java @@ -1,25 +1,25 @@ /* * This file is part of Transdroid - * + * * Transdroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Transdroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Transdroid. If not, see . - * + * */ package org.transdroid.daemon.Utorrent; -import com.android.internalcopy.http.multipart.FilePart; -import com.android.internalcopy.http.multipart.MultipartEntity; -import com.android.internalcopy.http.multipart.Part; +import com.android.internal.http.multipart.FilePart; +import com.android.internal.http.multipart.MultipartEntity; +import com.android.internal.http.multipart.Part; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/BitCometFilePart.java b/app/src/main/java/org/transdroid/multipart/BitCometFilePart.java similarity index 89% rename from app/src/main/java/com/android/internalcopy/http/multipart/BitCometFilePart.java rename to app/src/main/java/org/transdroid/multipart/BitCometFilePart.java index bc75ef38..d20cea0e 100644 --- a/app/src/main/java/com/android/internalcopy/http/multipart/BitCometFilePart.java +++ b/app/src/main/java/org/transdroid/multipart/BitCometFilePart.java @@ -1,238 +1,242 @@ -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java,v 1.19 2004/04/18 23:51:37 jsdever Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package com.android.internalcopy.http.multipart; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import org.apache.http.util.EncodingUtils; - -/** - * This class implements a part of a Multipart post object that - * consists of a file. - * - * @author Matthew Albright - * @author Jeff Dever - * @author Adrian Sutton - * @author Michael Becke - * @author Mark Diggory - * @author Mike Bowler - * @author Oleg Kalnichevski - * - * @since 2.0 - * - */ -public class BitCometFilePart extends PartBase { - - /** Default content encoding of file attachments. */ - public static final String DEFAULT_CONTENT_TYPE = "application/x-bittorrent"; - - /** Attachment's file name */ - protected static final String FILE_NAME = "; filename="; - - /** Attachment's file name as a byte array */ - private static final byte[] FILE_NAME_BYTES = - EncodingUtils.getAsciiBytes(FILE_NAME); - - /** Source of the file part. */ - private PartSource source; - - /** - * FilePart Constructor. - * - * @param name the name for this part - * @param partSource the source for this part - * @param contentType the content type for this part, if null the - * {@link #DEFAULT_CONTENT_TYPE default} is used - * @param charset the charset encoding for this part, if null the - * {@link #DEFAULT_CHARSET default} is used - */ - public BitCometFilePart(String name, PartSource partSource, String contentType, String charset) { - - super( - name, - contentType == null ? DEFAULT_CONTENT_TYPE : contentType, - charset == null ? "ISO-8859-1" : charset, - null - ); - - if (partSource == null) { - throw new IllegalArgumentException("Source may not be null"); - } - this.source = partSource; - } - - /** - * FilePart Constructor. - * - * @param name the name for this part - * @param partSource the source for this part - */ - public BitCometFilePart(String name, PartSource partSource) { - this(name, partSource, null, null); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param file the file to post - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public BitCometFilePart(String name, File file) - throws FileNotFoundException { - this(name, new FilePartSource(file), null, null); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param file the file to post - * @param contentType the content type for this part, if null the - * {@link #DEFAULT_CONTENT_TYPE default} is used - * @param charset the charset encoding for this part, if null the - * {@link #DEFAULT_CHARSET default} is used - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public BitCometFilePart(String name, File file, String contentType, String charset) - throws FileNotFoundException { - this(name, new FilePartSource(file), contentType, charset); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param fileName the file name - * @param file the file to post - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public BitCometFilePart(String name, String fileName, File file) - throws FileNotFoundException { - this(name, new FilePartSource(fileName, file), null, null); - } - - /** - * FilePart Constructor. - * - * @param name the name of the file part - * @param fileName the file name - * @param file the file to post - * @param contentType the content type for this part, if null the - * {@link #DEFAULT_CONTENT_TYPE default} is used - * @param charset the charset encoding for this part, if null the - * {@link #DEFAULT_CHARSET default} is used - * - * @throws FileNotFoundException if the file is not a normal - * file or if it is not readable. - */ - public BitCometFilePart(String name, String fileName, File file, String contentType, String charset) - throws FileNotFoundException { - this(name, new FilePartSource(fileName, file), contentType, charset); - } - - /** - * Write the disposition header to the output stream - * @param out The output stream - * @throws IOException If an IO problem occurs - * @see Part#sendDispositionHeader(OutputStream) - */ - @Override - protected void sendDispositionHeader(OutputStream out) - throws IOException { - super.sendDispositionHeader(out); - String filename = this.source.getFileName(); - if (filename != null) { - out.write(FILE_NAME_BYTES); - out.write(QUOTE_BYTES); - out.write(EncodingUtils.getAsciiBytes(filename)); - out.write(QUOTE_BYTES); - } - } - - /** - * Write the data in "source" to the specified stream. - * @param out The output stream. - * @throws IOException if an IO problem occurs. - * @see Part#sendData(OutputStream) - */ - @Override - protected void sendData(OutputStream out) throws IOException { - if (lengthOfData() == 0) { - - // this file contains no data, so there is nothing to send. - // we don't want to create a zero length buffer as this will - // cause an infinite loop when reading. - return; - } - - byte[] tmp = new byte[4096]; - InputStream instream = source.createInputStream(); - try { - int len; - while ((len = instream.read(tmp)) >= 0) { - out.write(tmp, 0, len); - } - } finally { - // we're done with the stream, close it - instream.close(); - } - } - - /** - * Returns the source of the file part. - * - * @return The source. - */ - protected PartSource getSource() { - return this.source; - } - - /** - * Return the length of the data. - * @return The length. - * @see Part#lengthOfData() - */ - @Override - protected long lengthOfData() { - return source.getLength(); - } - -} +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java,v 1.19 2004/04/18 23:51:37 jsdever Exp $ + * $Revision: 480424 $ + * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.transdroid.multipart; + +import com.android.internal.http.multipart.FilePartSource; +import com.android.internal.http.multipart.PartBase; +import com.android.internal.http.multipart.PartSource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.http.util.EncodingUtils; + +/** + * This class implements a part of a Multipart post object that + * consists of a file. + * + * @author Matthew Albright + * @author Jeff Dever + * @author Adrian Sutton + * @author Michael Becke + * @author Mark Diggory + * @author Mike Bowler + * @author Oleg Kalnichevski + * + * @since 2.0 + * + */ +public class BitCometFilePart extends PartBase { + + /** Default content encoding of file attachments. */ + public static final String DEFAULT_CONTENT_TYPE = "application/x-bittorrent"; + + /** Attachment's file name */ + protected static final String FILE_NAME = "; filename="; + + /** Attachment's file name as a byte array */ + private static final byte[] FILE_NAME_BYTES = + EncodingUtils.getAsciiBytes(FILE_NAME); + + /** Source of the file part. */ + private PartSource source; + + /** + * FilePart Constructor. + * + * @param name the name for this part + * @param partSource the source for this part + * @param contentType the content type for this part, if null the + * {@link #DEFAULT_CONTENT_TYPE default} is used + * @param charset the charset encoding for this part, if null the + * {@link #DEFAULT_CHARSET default} is used + */ + public BitCometFilePart(String name, PartSource partSource, String contentType, String charset) { + + super( + name, + contentType == null ? DEFAULT_CONTENT_TYPE : contentType, + charset == null ? "ISO-8859-1" : charset, + null + ); + + if (partSource == null) { + throw new IllegalArgumentException("Source may not be null"); + } + this.source = partSource; + } + + /** + * FilePart Constructor. + * + * @param name the name for this part + * @param partSource the source for this part + */ + public BitCometFilePart(String name, PartSource partSource) { + this(name, partSource, null, null); + } + + /** + * FilePart Constructor. + * + * @param name the name of the file part + * @param file the file to post + * + * @throws FileNotFoundException if the file is not a normal + * file or if it is not readable. + */ + public BitCometFilePart(String name, File file) + throws FileNotFoundException { + this(name, new FilePartSource(file), null, null); + } + + /** + * FilePart Constructor. + * + * @param name the name of the file part + * @param file the file to post + * @param contentType the content type for this part, if null the + * {@link #DEFAULT_CONTENT_TYPE default} is used + * @param charset the charset encoding for this part, if null the + * {@link #DEFAULT_CHARSET default} is used + * + * @throws FileNotFoundException if the file is not a normal + * file or if it is not readable. + */ + public BitCometFilePart(String name, File file, String contentType, String charset) + throws FileNotFoundException { + this(name, new FilePartSource(file), contentType, charset); + } + + /** + * FilePart Constructor. + * + * @param name the name of the file part + * @param fileName the file name + * @param file the file to post + * + * @throws FileNotFoundException if the file is not a normal + * file or if it is not readable. + */ + public BitCometFilePart(String name, String fileName, File file) + throws FileNotFoundException { + this(name, new FilePartSource(fileName, file), null, null); + } + + /** + * FilePart Constructor. + * + * @param name the name of the file part + * @param fileName the file name + * @param file the file to post + * @param contentType the content type for this part, if null the + * {@link #DEFAULT_CONTENT_TYPE default} is used + * @param charset the charset encoding for this part, if null the + * {@link #DEFAULT_CHARSET default} is used + * + * @throws FileNotFoundException if the file is not a normal + * file or if it is not readable. + */ + public BitCometFilePart(String name, String fileName, File file, String contentType, String charset) + throws FileNotFoundException { + this(name, new FilePartSource(fileName, file), contentType, charset); + } + + /** + * Write the disposition header to the output stream + * @param out The output stream + * @throws IOException If an IO problem occurs + * @see Part#sendDispositionHeader(OutputStream) + */ + @Override + protected void sendDispositionHeader(OutputStream out) + throws IOException { + super.sendDispositionHeader(out); + String filename = this.source.getFileName(); + if (filename != null) { + out.write(FILE_NAME_BYTES); + out.write(QUOTE_BYTES); + out.write(EncodingUtils.getAsciiBytes(filename)); + out.write(QUOTE_BYTES); + } + } + + /** + * Write the data in "source" to the specified stream. + * @param out The output stream. + * @throws IOException if an IO problem occurs. + * @see Part#sendData(OutputStream) + */ + @Override + protected void sendData(OutputStream out) throws IOException { + if (lengthOfData() == 0) { + + // this file contains no data, so there is nothing to send. + // we don't want to create a zero length buffer as this will + // cause an infinite loop when reading. + return; + } + + byte[] tmp = new byte[4096]; + InputStream instream = source.createInputStream(); + try { + int len; + while ((len = instream.read(tmp)) >= 0) { + out.write(tmp, 0, len); + } + } finally { + // we're done with the stream, close it + instream.close(); + } + } + + /** + * Returns the source of the file part. + * + * @return The source. + */ + protected PartSource getSource() { + return this.source; + } + + /** + * Return the length of the data. + * @return The length. + * @see Part#lengthOfData() + */ + @Override + protected long lengthOfData() { + return source.getLength(); + } + +} diff --git a/app/src/main/java/com/android/internalcopy/http/multipart/Utf8StringPart.java b/app/src/main/java/org/transdroid/multipart/Utf8StringPart.java similarity index 89% rename from app/src/main/java/com/android/internalcopy/http/multipart/Utf8StringPart.java rename to app/src/main/java/org/transdroid/multipart/Utf8StringPart.java index 3a2f28ba..6b4c3ab6 100644 --- a/app/src/main/java/com/android/internalcopy/http/multipart/Utf8StringPart.java +++ b/app/src/main/java/org/transdroid/multipart/Utf8StringPart.java @@ -1,43 +1,45 @@ -package com.android.internalcopy.http.multipart; - -import java.io.IOException; -import java.io.OutputStream; - -import org.apache.http.util.EncodingUtils; - -public class Utf8StringPart extends PartBase { - - /** Contents of this StringPart. */ - private byte[] content; - - /** The String value of this part. */ - private String value; - - public Utf8StringPart(String name, String value) { - super(name, null, null, null); - this.value = value; - } - - /** - * Gets the content in bytes. Bytes are lazily created to allow the charset to be changed - * after the part is created. - * - * @return the content in bytes - */ - private byte[] getContent() { - if (content == null) { - content = EncodingUtils.getBytes(value, "utf-8"); - } - return content; - } - - @Override - protected void sendData(OutputStream out) throws IOException { - out.write(getContent()); - } - - @Override - protected long lengthOfData() throws IOException { - return getContent().length; - } -} +package org.transdroid.multipart; + +import com.android.internal.http.multipart.PartBase; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.util.EncodingUtils; + +public class Utf8StringPart extends PartBase { + + /** Contents of this StringPart. */ + private byte[] content; + + /** The String value of this part. */ + private String value; + + public Utf8StringPart(String name, String value) { + super(name, null, null, null); + this.value = value; + } + + /** + * Gets the content in bytes. Bytes are lazily created to allow the charset to be changed + * after the part is created. + * + * @return the content in bytes + */ + private byte[] getContent() { + if (content == null) { + content = EncodingUtils.getBytes(value, "utf-8"); + } + return content; + } + + @Override + protected void sendData(OutputStream out) throws IOException { + out.write(getContent()); + } + + @Override + protected long lengthOfData() throws IOException { + return getContent().length; + } +} From e756cf05ae6b4655739408879dfde0f52ebcf5e9 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 20 Jun 2020 03:44:23 -0400 Subject: [PATCH 08/10] Update aXMLRPC to 1.12.0 --- app/build.gradle | 1 + .../main/java/de/timroes/axmlrpc/Call.java | 16 ++--- .../de/timroes/axmlrpc/ResponseParser.java | 15 +++-- .../java/de/timroes/axmlrpc/XMLRPCClient.java | 35 ++++++----- .../axmlrpc/XMLRPCServerException.java | 2 +- .../main/java/de/timroes/axmlrpc/XMLUtil.java | 4 +- .../axmlrpc/serializer/ArraySerializer.java | 17 +++++- .../axmlrpc/serializer/BooleanSerializer.java | 4 +- .../serializer/DateTimeSerializer.java | 8 ++- .../axmlrpc/serializer/SerializerHandler.java | 60 ++++++------------- .../axmlrpc/serializer/StringSerializer.java | 7 ++- .../axmlrpc/serializer/StructSerializer.java | 10 +++- .../main/java/de/timroes/base64/Base64.java | 12 ++-- 13 files changed, 103 insertions(+), 88 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6791fdb0..f83738c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -93,6 +93,7 @@ dependencies { implementation 'com.github.aegnor:rencode-java:cb628e824e' implementation 'org.apache.openjpa:openjpa-lib:3.1.1' implementation 'net.iharder:base64:2.3.9' + //implementation 'de.timroes:aXMLRPC:1.12.0' implementation('com.github.afollestad.material-dialogs:core:0.9.6.0@aar') { transitive = true } diff --git a/app/src/main/java/de/timroes/axmlrpc/Call.java b/app/src/main/java/de/timroes/axmlrpc/Call.java index de393ec5..26bfabe4 100644 --- a/app/src/main/java/de/timroes/axmlrpc/Call.java +++ b/app/src/main/java/de/timroes/axmlrpc/Call.java @@ -16,13 +16,14 @@ public class Call { private String method; private Object[] params; + private final SerializerHandler serializerHandler; /** * Create a new method call with the given name and no parameters. * @param method The method to be called. */ - public Call(String method) { - this(method, null); + public Call(SerializerHandler serializerHandler, String method) { + this(serializerHandler, method, null); } /** @@ -30,9 +31,10 @@ public class Call { * @param method The method to be called. * @param params An array of parameters for the method. */ - public Call(String method, Object[] params) { + public Call(SerializerHandler serializerHandler, String method, Object[] params) { this.method = method; this.params = params; + this.serializerHandler = serializerHandler; } /** @@ -57,11 +59,11 @@ public class Call { methodCall.addChildren(methodName); if(params != null && params.length > 0) { - XmlElement params = new XmlElement(XMLRPCClient.PARAMS); - methodCall.addChildren(params); + XmlElement callParams = new XmlElement(XMLRPCClient.PARAMS); + methodCall.addChildren(callParams); for(Object o : this.params) { - params.addChildren(getXMLParam(o)); + callParams.addChildren(getXMLParam(o)); } } @@ -79,7 +81,7 @@ public class Call { XmlElement param = new XmlElement(XMLRPCClient.PARAM); XmlElement value = new XmlElement(XMLRPCClient.VALUE); param.addChildren(value); - value.addChildren(SerializerHandler.getDefault().serialize(o)); + value.addChildren(serializerHandler.serialize(o)); return param; } diff --git a/app/src/main/java/de/timroes/axmlrpc/ResponseParser.java b/app/src/main/java/de/timroes/axmlrpc/ResponseParser.java index c58b13d3..c47d2240 100644 --- a/app/src/main/java/de/timroes/axmlrpc/ResponseParser.java +++ b/app/src/main/java/de/timroes/axmlrpc/ResponseParser.java @@ -16,7 +16,7 @@ import org.xmlpull.v1.XmlPullParserFactory; * * @author Tim Roes */ -class ResponseParser { +public class ResponseParser { private static final String FAULT_CODE = "faultCode"; private static final String FAULT_STRING = "faultString"; @@ -71,13 +71,13 @@ class ResponseParser { pullParser.nextTag(); // TAG_VALUE () // no parser.require() here since its called in XMLRPCSerializer.deserialize() below // deserialize result - Object obj = SerializerHandler.getDefault().deserialize(pullParser); + Object obj = SerializerHandler.deserialize(pullParser); consumeHttpEntity(response, entity); return obj; } else if (tag.equals(XMLRPCClient.FAULT)) { // fault response pullParser.nextTag(); // TAG_VALUE () - Map map = (Map) SerializerHandler.getDefault().deserialize(pullParser); + Map map = (Map) SerializerHandler.deserialize(pullParser); consumeHttpEntity(response, entity); //Check that required tags are in the response @@ -92,12 +92,11 @@ class ResponseParser { } catch (XmlPullParserException ex) { consumeHttpEntity(response, entity); throw new XMLRPCException("Error parsing response.", ex); + } catch(XMLRPCServerException e) { + throw e; } catch (Exception ex) { consumeHttpEntity(response, entity); - if(ex instanceof XMLRPCServerException) - throw (XMLRPCServerException)ex; - else - throw new XMLRPCException("Error getting result from server.", ex); + throw new XMLRPCException("Error getting result from server.", ex); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/timroes/axmlrpc/XMLRPCClient.java b/app/src/main/java/de/timroes/axmlrpc/XMLRPCClient.java index f6003f87..317e3e07 100644 --- a/app/src/main/java/de/timroes/axmlrpc/XMLRPCClient.java +++ b/app/src/main/java/de/timroes/axmlrpc/XMLRPCClient.java @@ -127,6 +127,11 @@ public class XMLRPCClient { */ public static final int FLAGS_NO_STRING_ENCODE = 0x1000; + /** + * Accepts response containing eg: + */ + public static final int FLAGS_ACCEPT_NULL_DATES = 0x4000; + /** * This flag should be used if the server is an apache ws xmlrpc server. * This will set some flags, so that the not standard conform behavior @@ -140,28 +145,30 @@ public class XMLRPCClient { private final int flags; private DefaultHttpClient httpclient; - + private String url; private Map backgroundCalls = new ConcurrentHashMap(); private ResponseParser responseParser; + private final SerializerHandler serializerHandler; + /** * Create a new XMLRPC client for the given URL. - * + * * @param httpclient The already-initialized Apache HttpClient to use for connection. * @param url The URL to send the requests to. * @param flags A combination of flags to be set. */ public XMLRPCClient(DefaultHttpClient httpclient, String url, int flags) { - SerializerHandler.initialize(flags); + this.serializerHandler = new SerializerHandler(flags); this.httpclient = httpclient; this.url = url; this.flags = flags; - + // Create a parser for the http responses. responseParser = new ResponseParser(); @@ -260,7 +267,7 @@ public class XMLRPCClient { throw new XMLRPCRuntimeException("Method name must only contain A-Z a-z . : _ / "); } - return new Call(method, params); + return new Call(serializerHandler, method, params); } @@ -368,7 +375,7 @@ public class XMLRPCClient { try { Call c = createCall(methodName, params); - + // Prepare POST request // FIXME: where creating a new HttpPost so calling #cancel isn't going to do anything HttpPost post = new HttpPost(url); @@ -377,10 +384,10 @@ public class XMLRPCClient { StringEntity entity = new StringEntity(c.getXML(), HTTP.UTF_8); entity.setContentType(TYPE_XML); post.setEntity(entity); - + HttpResponse response = httpclient.execute(post); int statusCode = response.getStatusLine().getStatusCode(); - + InputStream istream; // If status code was 401 or 403 throw exception or if appropriate @@ -406,7 +413,7 @@ public class XMLRPCClient { || statusCode == HttpURLConnection.HTTP_MOVED_TEMP) { // ... do either a foward if(isFlagSet(FLAGS_FORWARD)) { - boolean temporaryForward = (statusCode == HttpURLConnection.HTTP_MOVED_TEMP); + boolean temporaryForward = statusCode == HttpURLConnection.HTTP_MOVED_TEMP; // Get new location from header field. String newLocation = response.getFirstHeader("Location").getValue(); @@ -440,10 +447,8 @@ public class XMLRPCClient { } // Check for strict parameters - if(isFlagSet(FLAGS_STRICT)) { - if(!response.getFirstHeader("Content-Type").getValue().startsWith(TYPE_XML)) { + if(isFlagSet(FLAGS_STRICT) && !response.getFirstHeader("Content-Type").getValue().startsWith(TYPE_XML)) { throw new XMLRPCException("The Content-Type of the response must be text/xml."); - } } return responseParser.parse(istream, entity); @@ -462,9 +467,9 @@ public class XMLRPCClient { } } - + } - + public static class CancelException extends Exception { private static final long serialVersionUID = 9125122307255855136L; } @@ -475,5 +480,5 @@ public class XMLRPCClient { public UnauthorizdException(int statusCode) { this.statusCode = statusCode; } public int getStatusCode() { return statusCode; } } - + } diff --git a/app/src/main/java/de/timroes/axmlrpc/XMLRPCServerException.java b/app/src/main/java/de/timroes/axmlrpc/XMLRPCServerException.java index 0b403e93..20bccca2 100644 --- a/app/src/main/java/de/timroes/axmlrpc/XMLRPCServerException.java +++ b/app/src/main/java/de/timroes/axmlrpc/XMLRPCServerException.java @@ -8,7 +8,7 @@ package de.timroes.axmlrpc; */ public class XMLRPCServerException extends XMLRPCException { - private int errornr; + private final int errornr; public XMLRPCServerException(String ex, int errnr) { super(ex); diff --git a/app/src/main/java/de/timroes/axmlrpc/XMLUtil.java b/app/src/main/java/de/timroes/axmlrpc/XMLUtil.java index 7e444591..6cc97643 100644 --- a/app/src/main/java/de/timroes/axmlrpc/XMLUtil.java +++ b/app/src/main/java/de/timroes/axmlrpc/XMLUtil.java @@ -9,6 +9,8 @@ import de.timroes.axmlrpc.xmlcreator.XmlElement; */ public class XMLUtil { + private XMLUtil() {} + /** * Creates an xml tag with a given type and content. * @@ -22,4 +24,4 @@ public class XMLUtil { return xml; } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/timroes/axmlrpc/serializer/ArraySerializer.java b/app/src/main/java/de/timroes/axmlrpc/serializer/ArraySerializer.java index d1b054fe..8789fa3d 100644 --- a/app/src/main/java/de/timroes/axmlrpc/serializer/ArraySerializer.java +++ b/app/src/main/java/de/timroes/axmlrpc/serializer/ArraySerializer.java @@ -3,6 +3,7 @@ package de.timroes.axmlrpc.serializer; import de.timroes.axmlrpc.XMLRPCException; import de.timroes.axmlrpc.XMLRPCRuntimeException; import de.timroes.axmlrpc.xmlcreator.XmlElement; +import java.util.Arrays; /** * @@ -12,10 +13,20 @@ public class ArraySerializer implements Serializer { private static final String ARRAY_DATA = "data"; private static final String ARRAY_VALUE = "value"; + private final SerializerHandler serializerHandler; + + public ArraySerializer(SerializerHandler serializerHandler){ + this.serializerHandler = serializerHandler; + } public XmlElement serialize(Object object) { - Iterable iter = (Iterable)object; + Iterable iter; + if ( object instanceof Iterable){ + iter = (Iterable)object; + } else { + iter = Arrays.asList((Object[]) object); + } XmlElement array = new XmlElement(SerializerHandler.TYPE_ARRAY); XmlElement data = new XmlElement(ARRAY_DATA); array.addChildren(data); @@ -25,7 +36,7 @@ public class ArraySerializer implements Serializer { XmlElement e; for(Object obj : iter) { e = new XmlElement(ARRAY_VALUE); - e.addChildren(SerializerHandler.getDefault().serialize(obj)); + e.addChildren(serializerHandler.serialize(obj)); data.addChildren(e); } @@ -37,4 +48,4 @@ public class ArraySerializer implements Serializer { } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/timroes/axmlrpc/serializer/BooleanSerializer.java b/app/src/main/java/de/timroes/axmlrpc/serializer/BooleanSerializer.java index f64a7f4e..c85162cd 100644 --- a/app/src/main/java/de/timroes/axmlrpc/serializer/BooleanSerializer.java +++ b/app/src/main/java/de/timroes/axmlrpc/serializer/BooleanSerializer.java @@ -11,7 +11,7 @@ public class BooleanSerializer implements Serializer { public XmlElement serialize(Object object) { return XMLUtil.makeXmlTag(SerializerHandler.TYPE_BOOLEAN, - ((Boolean)object == true) ? "1" : "0"); + (Boolean) object ? "1" : "0"); } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/timroes/axmlrpc/serializer/DateTimeSerializer.java b/app/src/main/java/de/timroes/axmlrpc/serializer/DateTimeSerializer.java index 8c19f0f3..7cb67c6e 100644 --- a/app/src/main/java/de/timroes/axmlrpc/serializer/DateTimeSerializer.java +++ b/app/src/main/java/de/timroes/axmlrpc/serializer/DateTimeSerializer.java @@ -10,9 +10,15 @@ import java.text.SimpleDateFormat; */ public class DateTimeSerializer implements Serializer { - public static final String DATETIME_FORMAT = "yyyyMMdd'T'HH:mm:ss"; + public static final String DATETIME_FORMAT = "yyyyMMdd'T'HHmmss"; public static final SimpleDateFormat DATE_FORMATER = new SimpleDateFormat(DATETIME_FORMAT); + public final boolean accepts_null_input; + + public DateTimeSerializer(boolean accepts_null_input) { + this.accepts_null_input = accepts_null_input; + } + public XmlElement serialize(Object object) { return XMLUtil.makeXmlTag(SerializerHandler.TYPE_DATETIME, DATE_FORMATER.format(object)); diff --git a/app/src/main/java/de/timroes/axmlrpc/serializer/SerializerHandler.java b/app/src/main/java/de/timroes/axmlrpc/serializer/SerializerHandler.java index 8666183e..9f15e556 100644 --- a/app/src/main/java/de/timroes/axmlrpc/serializer/SerializerHandler.java +++ b/app/src/main/java/de/timroes/axmlrpc/serializer/SerializerHandler.java @@ -2,7 +2,6 @@ package de.timroes.axmlrpc.serializer; import de.timroes.axmlrpc.XMLRPCClient; import de.timroes.axmlrpc.XMLRPCException; -import de.timroes.axmlrpc.XMLRPCRuntimeException; import de.timroes.axmlrpc.xmlcreator.XmlElement; import java.io.BufferedReader; @@ -60,54 +59,29 @@ public class SerializerHandler { public static final String TYPE_BASE64 = "base64"; public static final String TYPE_NULL = "nil"; - private static SerializerHandler instance; - - /** - * Initialize the serialization handler. This method must be called before - * the get method returns any object. - * - * @param flags The flags that has been set in the XMLRPCClient. - * @see XMLRPCClient - */ - public static void initialize(int flags) { - instance = new SerializerHandler(flags); - } - - /** - * Return the instance of the SerializerHandler. - * It must have been initialized with initialize() before. - * - * @return The instance of the SerializerHandler. - */ - public static SerializerHandler getDefault() { - if(instance == null) { - throw new XMLRPCRuntimeException("The SerializerHandler has not been initialized."); - } - return instance; - } - private StringSerializer string; private BooleanSerializer bool = new BooleanSerializer(); private IntSerializer integer = new IntSerializer(); private LongSerializer long8 = new LongSerializer(); - private StructSerializer struct = new StructSerializer(); + private StructSerializer struct; private DoubleSerializer floating = new DoubleSerializer(); - private DateTimeSerializer datetime = new DateTimeSerializer(); - private ArraySerializer array = new ArraySerializer(); + private DateTimeSerializer datetime; + public static boolean accepts_null_input; + public SerializerHandler(boolean accepts_null_input) { + SerializerHandler.accepts_null_input = accepts_null_input; + } + private ArraySerializer array; private Base64Serializer base64 = new Base64Serializer(); private NullSerializer nil = new NullSerializer(); - + private int flags; - /** - * Generates the SerializerHandler. - * This method can only called from within the class (the initialize method). - * - * @param flags The flags to use. - */ - private SerializerHandler(int flags) { + public SerializerHandler(int flags) { this.flags = flags; string = new StringSerializer((flags & XMLRPCClient.FLAGS_NO_STRING_ENCODE) == 0); + struct = new StructSerializer(this); + array = new ArraySerializer(this); + datetime = new DateTimeSerializer((flags & XMLRPCClient.FLAGS_ACCEPT_NULL_DATES) != 0); } /** @@ -152,8 +126,12 @@ public class SerializerHandler { obj = parser.nextText(); } else if (typeNodeName.equals(TYPE_DATE_TIME_ISO8601)) { + dateFormat.setCalendar(cal); String value = parser.nextText(); + if (accepts_null_input && (value==null || value.trim().length()==0)) { + return null; + } try { obj = dateFormat.parseObject(value); } catch (ParseException e) { @@ -232,7 +210,7 @@ public class SerializerHandler { */ public XmlElement serialize(Object object) throws XMLRPCException { - Serializer s = null; + Serializer s; if((flags & XMLRPCClient.FLAGS_NIL) != 0 && object == null) { s = nil; @@ -277,7 +255,7 @@ public class SerializerHandler { s = base64; } else if(object instanceof Byte[]) { s = base64; - } else if(object instanceof Iterable) { + } else if(object instanceof Iterable || object instanceof Object[]) { s = array; } else { throw new XMLRPCException("No serializer found for type '" @@ -288,4 +266,4 @@ public class SerializerHandler { } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/timroes/axmlrpc/serializer/StringSerializer.java b/app/src/main/java/de/timroes/axmlrpc/serializer/StringSerializer.java index 823edad6..c16daff2 100644 --- a/app/src/main/java/de/timroes/axmlrpc/serializer/StringSerializer.java +++ b/app/src/main/java/de/timroes/axmlrpc/serializer/StringSerializer.java @@ -19,9 +19,12 @@ public class StringSerializer implements Serializer { public XmlElement serialize(Object object) { String content = object.toString(); if(encodeStrings) { - content = content.replaceAll("&", "&").replaceAll("<", "<"); + content = content + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll("]]>", "]]>"); } return XMLUtil.makeXmlTag(SerializerHandler.TYPE_STRING, content); } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/timroes/axmlrpc/serializer/StructSerializer.java b/app/src/main/java/de/timroes/axmlrpc/serializer/StructSerializer.java index 722f54a3..34ee8c0d 100644 --- a/app/src/main/java/de/timroes/axmlrpc/serializer/StructSerializer.java +++ b/app/src/main/java/de/timroes/axmlrpc/serializer/StructSerializer.java @@ -15,6 +15,12 @@ public class StructSerializer implements Serializer { private static final String STRUCT_NAME = "name"; private static final String STRUCT_VALUE = "value"; + private final SerializerHandler serializerHandler; + + public StructSerializer(SerializerHandler serializerHandler) { + this.serializerHandler = serializerHandler; + } + public XmlElement serialize(Object object) { XmlElement struct = new XmlElement(SerializerHandler.TYPE_STRUCT); @@ -33,7 +39,7 @@ public class StructSerializer implements Serializer { name = new XmlElement(STRUCT_NAME); value = new XmlElement(STRUCT_VALUE); name.setContent(member.getKey()); - value.addChildren(SerializerHandler.getDefault().serialize(member.getValue())); + value.addChildren(serializerHandler.serialize(member.getValue())); entry.addChildren(name); entry.addChildren(value); struct.addChildren(entry); @@ -46,4 +52,4 @@ public class StructSerializer implements Serializer { return struct; } -} \ No newline at end of file +} diff --git a/app/src/main/java/de/timroes/base64/Base64.java b/app/src/main/java/de/timroes/base64/Base64.java index 24612791..68bc1f35 100644 --- a/app/src/main/java/de/timroes/base64/Base64.java +++ b/app/src/main/java/de/timroes/base64/Base64.java @@ -14,6 +14,8 @@ public class Base64 { private static final HashMap map = new HashMap(); + private Base64() {} + static { for(int i = 0; i < code.length; i++) { map.put(code[i], (byte)i); @@ -44,10 +46,10 @@ public class Base64 { int outi = 0; int b1, b2, b3, b4; for(int i = 0; i < input.length; i+=4) { - b1 = (map.get(input[i]) - 1); - b2 = (map.get(input[i+1]) - 1); - b3 = (map.get(input[i+2]) - 1); - b4 = (map.get(input[i+3]) - 1); + b1 = map.get(input[i]) - 1; + b2 = map.get(input[i+1]) - 1; + b3 = map.get(input[i+2]) - 1; + b4 = map.get(input[i+3]) - 1; out[outi++] = (byte)(b1 << 2 | b2 >>> 4); out[outi++] = (byte)((b2 & 0x0F) << 4 | b3 >>> 2); out[outi++] = (byte)((b3 & 0x03) << 6 | (b4 & 0x3F)); @@ -158,4 +160,4 @@ public class Base64 { return out; } -} \ No newline at end of file +} From 17795e6de0cc876344e29061aa49840d4cce699f Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 20 Jun 2020 04:03:50 -0400 Subject: [PATCH 09/10] Update ORMLite to 5.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f83738c3..b482d9c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -85,8 +85,8 @@ dependencies { // Other implementation 'org.androidannotations:androidannotations-api:4.7.0' implementation 'org.androidannotations:ormlite-api:4.7.0' - implementation 'com.j256.ormlite:ormlite-core:4.48' - implementation 'com.j256.ormlite:ormlite-android:4.48' + implementation 'com.j256.ormlite:ormlite-core:5.1' + implementation 'com.j256.ormlite:ormlite-android:5.1' implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.nispok:snackbar:2.11.0' From 0be480980ecdb10d4c3166d263d8c424563b717d Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sat, 20 Jun 2020 04:19:09 -0400 Subject: [PATCH 10/10] Fix spacing in build.gradle --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b482d9c1..5d4f42ed 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,7 +78,7 @@ android { dependencies { // Android support implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' implementation 'com.google.android.material:material:1.1.0' @@ -91,9 +91,9 @@ dependencies { implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.nispok:snackbar:2.11.0' implementation 'com.github.aegnor:rencode-java:cb628e824e' - implementation 'org.apache.openjpa:openjpa-lib:3.1.1' - implementation 'net.iharder:base64:2.3.9' - //implementation 'de.timroes:aXMLRPC:1.12.0' + implementation 'org.apache.openjpa:openjpa-lib:3.1.1' + implementation 'net.iharder:base64:2.3.9' + //implementation 'fr.turri:aXMLRPC:1.12.0' implementation('com.github.afollestad.material-dialogs:core:0.9.6.0@aar') { transitive = true }